Advent of Code 2015 Day 7 Java实现

朋友不知道哪里搞来的那么久之前的原题(纯英文),我本来是一个不太在意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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值