朋友不知道哪里搞来的那么久之前的原题(纯英文),我本来是一个不太在意Java位运算内容的人,但是做完这一题后对Java无符号位运算的实现有了比较深的理解,所以想写个博客记录一下。本题的part2没什么价值,就不说了。
题目翻译
以下为我对问题的翻译(只翻译关键部分):
现有一组导线和一组位运算门。每条导线都有一个标识符(一些小写字母),可以携带16位信号(0到65535之间的数字)。通过门、另一根导线或某个特定值将信号提供给每条导线。每条导线只能从一个源获得信号,但可以将其信号提供给多个端。位运算门不提供信号,直到它的所有输入都有信号为止。
例如:
123 -> x 表示信号123被提供给导线x。
x AND y -> z 表示将导线 x 和 y 连接到与门,然后将其输出连接到导线z。
p LSHIFT 2 -> q 意味着导线 p 的值左移2,然后提供给导线q。
NOT e -> f 表示将来自导线 e 的值的位补码提供给导线 f。
其他可能的门包括OR(按位OR)和RSHIFT(右移)。
比方说,这里有一个简单的电路:
123 -> x
456 -> y
x AND y -> d
x OR y -> e
x LSHIFT 2 -> f
y RSHIFT 2 -> g
NOT x -> h
NOT y -> i
运行后,这些是导线上的信号数值:
d: 72
e: 507
f: 492
g: 114
h: 65412
i: 65079
x: 123
y: 456
这为你的输入,那么最后导线 a 的信号值是多少呢?
代码
import java.io.File;
import java.io.FileNotFoundException;
import java.util.*;
/**
* @author mango3y
* @version 1.0
*/
public class MyAnswer {
//保存结果 电线 : 数值
static HashMap<String, Integer> resultMap = new HashMap<>();
/*
我们用Integer,也就是int保存信号值,题目中强调“16位信号”,int为32位,使用它从空间上看是可行的。
但要注意本题有取反和左移操作。
对于int,我们真正要使用的是后16位,而对于前16位我们采用“清理”的操作,即对应方法clean()。
因为本质上我们此题是“无符号位运算”,所以对于取反运算,int中前16位被置为1了,我们得用 int & 0xffff 将前十六位重新置零。
而对于左移操作,本质上是舍弃了一些有效位,可能会有后16位的1移到前16位,此时也同样要clean()。
*/
//用来将文件的每一行保存,方便遍历、删除
static List<String> list = new ArrayList<>();
public static void main(String[] args) throws FileNotFoundException {
//将文件内容逐行保存到list中
Scanner s = new Scanner(new File("file.txt"));
while (s.hasNextLine()) {
String line = s.nextLine();
list.add(line);
}
int index; //数值为1时,则说明该行可以从list中删除(我的想法是删除之后能提高效率)
String[] strings; //保存分割后的字符串
//一直处理直到list为空,说明所有的输入行都处理完成
while (!list.isEmpty()){
//依次处理list中的元素,注意,由于我们的list长度会变化,所以若list的该份数据是被有效处理的,则i要-1。
//否则会出现list.get(i)为空的错误
for(int i = 0; i < list.size(); i++){
index = -1; //若经过下面的条件判断后仍为-1,则list该行不需要被删除
String str = list.get(i);
strings = str.split(" "); //每次都要分割,感觉可以优化
//1.将最简单的直接为“数字/电线 -> 电线”的行保存到结果Map中,并准备将它们从list中删除
if(strings.length == 3){
//使用正则表达式确定是 数字 -> 电线 还是 电线 -> 电线
if (strings[0].matches("\\d+")) {
//数字 -> 电线则直接放入结果Map
resultMap.put(strings[2], Integer.parseInt(strings[0]));
index = 1; //设置为1表示该行等下需要被删除
}else {
//电线 -> 电线则先 确认结果Map中是否有 输入电路
//若有,则取出信号值后,将 原电路名 与 信号值 存入结果Map
if(resultMap.get(strings[0])!=null){
resultMap.put(strings[2], resultMap.get(strings[0]));
index = 1;
}
}
}
//2.然后处理NOT,NOT 电线 -> 电线
if (index == -1 && strings.length == 4 && strings[0].equals("NOT")){
//若结果Map中有保存源电线的信号值,则可以取反后保存如结果map
Integer value = resultMap.get(strings[1]);
if(value != null){
//注意此处是要clean()的
resultMap.put(strings[3], clean(~value));
index = 1;
}
}
//3.然后处理AND/OR, xx AND/OR yy -> zz
if (index == -1 && strings.length == 5 && (strings[1].equals("AND") || strings[1].equals("OR"))){
Integer value1;
if (strings[0].matches("\\d+")) {
//若为 数值 AND 电线 -> 电线
value1 = Integer.parseInt(strings[0]);
} else {
//若为 电线 AND 电线 -> 电线
value1 = resultMap.get(strings[0]);
}
Integer value2 = resultMap.get(strings[2]);
//第二根操作数电线的信号值也存在于结果表中,则两个操作数都存在
if(value1 != null && value2 != null){
int value;
if(strings[1].equals("AND")){
value = value1 & value2;
}else {
value = value1 | value2;
}
//这里并不需要clean(),因为前16位不会被污染
resultMap.put(strings[4], value);
index = 1;
}
}
//4.然后处理SHIFT
if (index == -1 && strings.length == 5 && (strings[1].equals("LSHIFT") || strings[1].equals("RSHIFT"))){
Integer value = resultMap.get(strings[0]);
if(value != null){
int shift = Integer.parseInt(strings[2]);
int result;
if(strings[1].equals("LSHIFT")){
result = value << shift;
}else {
result = value >> shift;
}
//左移可能会污染前16位,所以clean()一下
resultMap.put(strings[4], clean(result));
index = 1;
}
}
if(index != -1){
//则list的该行需要被删除
System.out.println(str); //可以打印真实的处理过程
list.remove(str);
i--; //非常重要的一步
}
}
}
System.out.println(resultMap.get("a"));
}
private static int clean(int value) {
return value & 0xffff;
}
}