summaryrefslogtreecommitdiff
path: root/d12/src/main.rs
blob: d49ec45f87bdca7b53805eb4efbee90ff6ed3971 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::fs;
use std::collections::HashMap;

enum Oper<'a> {
    IncReg,
    DecReg,
    CpyReg(&'a str),
    CpyLit(isize),
    JnzReg(&'a str, isize),
    JnzLit(isize,   isize),
}

fn parse(lines: Vec<&str>) -> Vec<(Oper, &str)> {
    // We emit tuples of type `(Oper, &str)', where the second field
    // is the destination register. Storing the destination this way,
    // instead of inside the `Oper' enum, helps keep `execute()' DRY.
    let mut insts = Vec::new();

    for line in lines {
        let words: Vec<&str> = line.split_ascii_whitespace().collect();

        if words[0] == "inc" {
            // words[1] is register name
            insts.push((Oper::IncReg, words[1]));
        } else if words[0] == "dec" {
            // words[1] is register name
            insts.push((Oper::DecReg, words[1]));
        } else if words[0] == "cpy" {
            // words[1] is source, can be literal number or register name
            // words[2] is destination register name
            if words[1].chars().all(|c| c.is_ascii_digit() || c == '-') { // yes I know
                let val = words[1].parse().unwrap();
                insts.push((Oper::CpyLit(val), words[2]));
            } else {
                insts.push((Oper::CpyReg(words[1]), words[2]));
            }
        } else if words[0] == "jnz" {
            // words[1] is value, can be literal number or register name
            // words[2] is offset to jump by if branch is taken
            let off = words[2].parse().unwrap();
            if words[1].chars().all(|c| c.is_ascii_digit() || c == '-') { // I know
                let val = words[1].parse().unwrap();
                insts.push((Oper::JnzLit(val, off), "_"));
            } else {
                insts.push((Oper::JnzReg(words[1], off), "_"));
            }
        }
    }

    insts
}

fn execute(insts: &Vec<(Oper, &str)>, init: Vec<(&str, isize)>) -> isize {
    // `init' contains initial register values
    let mut state: HashMap<&str, isize> = init.into_iter().collect();

    let mut ip = 0; // instruction pointer
    while ip < insts.len() {
        // Get destination register now, because we'll mess with `ip' later
        let dst = insts[ip].1;

        // Too early for mutable borrow of `state', so write to `tmp' instead
        let mut tmp = state.get(dst).copied().unwrap_or(0);
        match insts[ip].0 {
            Oper::IncReg => {
                tmp += 1;
            },
            Oper::DecReg => {
                tmp -= 1;
            },
            Oper::CpyLit(val) => {
                tmp = val;
            },
            Oper::CpyReg(src) => {
                let val = state.get(&src).copied().unwrap_or(0);
                tmp = val;
            },
            Oper::JnzLit(val, off) => {
                if val != 0 {
                    // Subtract 1 to compensate for `ip += 1;' below
                    ip = ((ip as isize) + off - 1) as usize;
                }
            },
            Oper::JnzReg(src, off) => {
                let val = state.get(&src).copied().unwrap_or(0);
                if val != 0 {
                    // Subtract 1 to compensate for `ip += 1;' below
                    ip = ((ip as isize) + off - 1) as usize;
                }
            },
        };

        // "_" is a dummy destination register used by the "jnz" instructions
        if dst != "_" {
            *state.entry(dst).or_insert(0) = tmp;
        }

        ip += 1;
    }

    state.get("a").copied().unwrap()
}

fn main() {
    // Read assembly from input text file and parse it
    let input = fs::read_to_string("input.txt").unwrap();
    let lines = input.lines().collect();
    let insts = parse(lines);

    // Part 1 gives 318117 for my input
    println!("Part 1 solution: {}", execute(&insts, vec![]));

    // Part 2 gives 9227771 for my input
    println!("Part 2 solution: {}", execute(&insts, vec![("c", 1)]));
}



#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn part1_example1() {
        let lines = vec!["cpy 41 a", "inc a", "inc a", "dec a", "jnz a 2", "dec a"];
        let insts = parse(lines);
        assert_eq!(execute(&insts, vec![]), 42);
    }
}