Day 1: Inverse Captcha

Rust solution to AoC|2017|1.

Get access by solving a captcha to give evidence I am not a human…​

Star 1

Zipping iterators comes handy here…​

1
2
3
4
5
6
7
8
9
pub fn star_1(input: &&str) -> SolType {
    let input = input.trim();
    input
        .bytes()
        .zip(input.bytes().cycle().skip(1))
        .filter(|(a, b)| a == b)
        .map(|(a, _)| (a - b'0') as SolType)
        .sum()
}

Star 2

Still zipping iterators, just with another offset…​

1
2
3
4
5
6
7
8
pub fn star_2(input: &&str) -> SolType {
    let b = input.trim().as_bytes();
    b.iter()
        .zip(b[b.len() >> 1..].iter().chain(b.iter()))
        .filter(|(a, b)| a == b)
        .map(|(a, _)| (a - b'0') as SolType)
        .sum()
}

Tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn test_star_1() {
        assert_eq!(3, star_1(&"1122"));
        assert_eq!(4, star_1(&"1111"));
        assert_eq!(0, star_1(&"1234"));
        assert_eq!(9, star_1(&"91212129"));
    }

    #[test]
    pub fn test_star_2() {
        assert_eq!(6, star_2(&"1212"));
        assert_eq!(0, star_2(&"1221"));
        assert_eq!(4, star_2(&"123425"));
        assert_eq!(12, star_2(&"123123"));
        assert_eq!(4, star_2(&"12131415"));
    }
}

Day 2: Corruption Checksum

Rust solution to AoC|2017|2.

Another day of nested iterators, filters and sums.

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData(pub Vec<Vec<usize>>);

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            Self(
                s.lines()
                    .map(|l| {
                        l.split_ascii_whitespace()
                            .map(str::parse)
                            .collect::<Result<_, _>>()
                            .unwrap()
                    })
                    .collect(),
            )
        }
    }
}

Star 1

Min and max are calculated in one pass with a fold.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pub fn star_1(PuzzleData(rows): &PuzzleData) -> SolType1 {
    rows.iter()
        .map(|row| {
            row.iter().fold((usize::MAX, usize::MIN), |(mn, mx), &v| {
                (mn.min(v), mx.max(v))
            })
        })
        .map(|(mn, mx)| mx - mn)
        .sum()
}

Star 2

Use nested iterators to find divisible pairs and sum the quotients in another, outer iterator.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
pub fn star_2(PuzzleData(rows): &PuzzleData) -> SolType2 {
    rows.iter()
        .map(|row| {
            row.iter()
                .enumerate()
                .filter_map(|(k1, &v1)| {
                    row[k1 + 1..]
                        .iter()
                        .map(|&v2| (v1.max(v2), v1.min(v2)))
                        .filter(|(a, b)| a % b == 0)
                        .map(|(a, b)| a / b)
                        .next()
                })
                .next()
                .unwrap()
        })
        .sum()
}

Day 3: Spiral Memory

Rust solution to AoC|2017|3.

The challenge is to find a transformation for two-dimensional coordinates to spiral indices and vice versa.

Star 1

To convert from spiral index to 2D coordinate, first determine the layer, than calculate the coordinate depending on which side of the spiral we are located on.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
pub fn coord(idx: isize) -> (isize, isize) {
    assert!(idx > 0);
    if idx == 1 {
        return (0, 0);
    }

    let n = (0..).find(|n| 1 + 4 * n * (n + 1) >= idx).unwrap();
    let m = idx - 2 - 4 * (n - 1) * n;
    match m / (2 * n) {
        0 => (n, (n - 1) - m % (2 * n)),  // right
        1 => ((n - 1) - m % (2 * n), -n), // top
        2 => (-n, m % (2 * n) - (n - 1)), // left
        3 => (m % (2 * n) - (n - 1), n),  // bottom
        _ => unreachable!("v = {}, m = {}, n = {}", idx, m, n),
    }
}

pub fn star_1(input: &&str) -> SolType1 {
    let (x, y) = coord(input.trim().parse().unwrap());
    (x.abs() + y.abs()) as _
}

Star 2

To convert from 2D coordinate to spiral index, first determine the layer, than calculate the index depending on which side of the spiral we are located on.

 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
pub fn spiral_idx((x, y): (isize, isize)) -> isize {
    if (x, y) == (0, 0) {
        return 1;
    }

    let n = x.abs().max(y.abs());
    (4 * n * n + 1)
        + if x == n && y < n {
            -3 * n - y // right
        } else if y == -n && x < n {
            -n - x // top
        } else if x == -n && y > -n {
            n + y // left
        } else if y == n && x > -n {
            3 * n + x // bottom
        } else {
            unreachable!()
        }
}

pub fn star_2(input: &&str) -> SolType2 {
    let lim = input.trim().parse().unwrap();
    const D: [(isize, isize); 8] = [
        (1, 0),
        (1, -1),
        (0, -1),
        (-1, -1),
        (-1, 0),
        (-1, 1),
        (0, 1),
        (1, 1),
    ];
    (2..)
        .map(coord)
        .scan(Vec::from([1]), |list, (x, y)| {
            let v = D
                .iter()
                .map(|(dx, dy)| spiral_idx((x + dx, y + dy)))
                .filter_map(|idx| list.get((idx - 1) as usize))
                .sum();
            list.push(v);
            Some(v)
        })
        .find(|&v| v > lim)
        .unwrap()
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn test_coord() {
        assert_eq!((0, 0), coord(1));

        assert_eq!((1, 0), coord(2));
        assert_eq!((1, -1), coord(3));
        assert_eq!((0, -1), coord(4));
        assert_eq!((-1, -1), coord(5));
        assert_eq!((-1, 0), coord(6));
        assert_eq!((-1, 1), coord(7));
        assert_eq!((0, 1), coord(8));
        assert_eq!((1, 1), coord(9));

        assert_eq!((2, 1), coord(10));
        assert_eq!((0, -2), coord(15));
        assert_eq!((-2, 1), coord(20));
        assert_eq!((2, 2), coord(25));

        assert_eq!((4, 3), coord(50));
        assert_eq!((1, -4), coord(60));
        assert_eq!((-4, 1), coord(70));
        assert_eq!((3, 4), coord(80));
    }

    #[test]
    pub fn test_value() {
        assert_eq!(spiral_idx((0, 0)), 1);

        assert_eq!(spiral_idx((1, 0)), 2);
        assert_eq!(spiral_idx((1, -1)), 3);
        assert_eq!(spiral_idx((0, -1)), 4);
        assert_eq!(spiral_idx((-1, -1)), 5);
        assert_eq!(spiral_idx((-1, 0)), 6);
        assert_eq!(spiral_idx((-1, 1)), 7);
        assert_eq!(spiral_idx((0, 1)), 8);
        assert_eq!(spiral_idx((1, 1)), 9);

        assert_eq!(spiral_idx((2, 1)), 10);
        assert_eq!(spiral_idx((0, -2)), 15);
        assert_eq!(spiral_idx((-2, 1)), 20);
        assert_eq!(spiral_idx((2, 2)), 25);

        assert_eq!(spiral_idx((4, 3)), 50);
        assert_eq!(spiral_idx((1, -4)), 60);
        assert_eq!(spiral_idx((-4, 1)), 70);
        assert_eq!(spiral_idx((3, 4)), 80);
    }
}

Day 4: High-Entropy Passphrases

Rust solution to AoC|2017|4.

Passphrase policy verification

Star 1

Passphrase validity is checked by adding words to a set. If the set already contains a word, the passphrase is invalid.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pub fn star_1(input: &&str) -> SolType {
    input
        .lines()
        .filter(|l| {
            l.split_ascii_whitespace()
                .scan(HashSet::new(), |set, word| Some(set.insert(word)))
                .all(|v| v)
        })
        .count()
}

Star 2

Instead of adding the words to the set, we now add a 'canonical representation' of the word given by the sorted bytes of the word.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
pub fn star_2(input: &&str) -> SolType {
    input
        .lines()
        .filter(|l| {
            l.split_ascii_whitespace()
                .map(|word| {
                    let mut word = word.bytes().collect::<Vec<_>>();
                    word.sort_unstable();
                    word
                })
                .scan(HashSet::new(), |set, word| Some(set.insert(word)))
                .all(|v| v)
        })
        .count()
}

Day 5: A Maze of Twisty Trampolines, All Alike

Rust solution to AoC|2017|5.

Jump, jump, …​

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData(pub Vec<isize>);

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            Self(s.lines().map(str::parse).collect::<Result<_, _>>().unwrap())
        }
    }
}

Star 1

Do the simulation using iter-scan-find.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pub fn star_1(PuzzleData(offsets): &PuzzleData) -> SolType1 {
    (1..)
        .scan((0, offsets.to_owned()), |(pos, offsets), steps| {
            let off = &mut offsets[*pos as usize];
            (*pos, *off) = (*pos + *off, *off + 1);
            Some((steps, *pos))
        })
        .find(|&(_, pos)| pos < 0 || pos >= offsets.len() as isize)
        .unwrap()
        .0
}

Star 2

Update the update rule in the iter-scan-find.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pub fn star_2(PuzzleData(offsets): &PuzzleData) -> SolType2 {
    (1..)
        .scan((0, offsets.to_owned()), |(pos, offsets), steps| {
            let off = &mut offsets[*pos as usize];
            (*pos, *off) = (*pos + *off, if *off >= 3 { *off - 1 } else { *off + 1 });
            Some((steps, *pos))
        })
        .find(|&(_, pos)| pos < 0 || pos >= offsets.len() as isize)
        .unwrap()
        .0
}

Tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"0
3
0
1
-3
"#;

    #[test]
    pub fn test_star_1() {
        assert_eq!(5, star_1(&CONTENT.into()));
    }

    #[test]
    pub fn test_star_2() {
        assert_eq!(10, star_2(&CONTENT.into()));
    }
}

Day 6: Memory Reallocation

Rust solution to AoC|2017|6.

Find when a sequence of block arrangements repeats

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData(pub Vec<usize>);

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            Self(
                s.trim()
                    .split_ascii_whitespace()
                    .map(|v| v.parse().unwrap())
                    .collect(),
            )
        }
    }
}

Star 1 & 2

The solutions are so close, that it really makes no sense to do the calculation twice…​

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pub fn star_1_2(PuzzleData(blocks): &PuzzleData) -> SolType {
    let len = blocks.len();
    let mut seen = HashMap::from([(blocks.to_owned(), 0)]);
    let mut blocks = blocks.to_owned();
    let (mut prev_cycles, mut cycles) = (0, 0);
    while prev_cycles == cycles {
        cycles += 1;

        let (k, &v) = blocks
            .iter()
            .enumerate()
            .max_by_key(|&(k, &v)| (v, !k))
            .unwrap();

        blocks[k] = 0;
        for j in k + 1..=k + v {
            blocks[j % len] += 1;
        }

        prev_cycles = *seen.entry(blocks.clone()).or_insert(cycles);
    }
    (cycles, cycles - prev_cycles).into()
}

Tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"0 2 7 0"#;

    #[test]
    pub fn test_from() {
        let PuzzleData(blocks) = PuzzleData::from(CONTENT);
        assert_eq!(vec![0, 2, 7, 0], blocks);
    }

    #[test]
    pub fn test_star_1_2() {
        assert_eq!(Solutions::from((5, 4)), star_1_2(&CONTENT.into()));
    }
}

Day 7: Recursive Circus

Rust solution to AoC|2017|7.

Balance disks.

I used this puzzle the experiment with the use of RefCell to implement simple caching of values.

Input

 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
pub mod input {
    use crate::{Program, Tower};

    impl<'a> From<&'a str> for Program<'a> {
        fn from(s: &'a str) -> Self {
            let mut words = s.split_ascii_whitespace();
            let name = words.next().unwrap();
            let weight = words
                .next()
                .unwrap()
                .trim_matches::<&[char]>(&['(', ')'])
                .parse()
                .unwrap();
            words.next(); // skip " -> "
            let children = words.map(|word| word.trim_end_matches(',')).collect();
            Self {
                name,
                weight,
                children,
                total_weight: Default::default(),
            }
        }
    }

    impl<'a> From<&'a str> for Tower<'a> {
        fn from(s: &'a str) -> Self {
            Self {
                programs: s.lines().map(Program::from).map(|p| (p.name, p)).collect(),
                root: Default::default(),
            }
        }
    }
}

Star 1

Iterate over nodes to find the one which is not a child of any other node.

 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
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Program<'a> {
    name: &'a str,
    weight: SolType2,
    children: HashSet<&'a str>,
    total_weight: RefCell<Option<SolType2>>,
}

#[derive(Debug)]
pub struct Tower<'a> {
    programs: HashMap<&'a str, Program<'a>>,
    root: RefCell<Option<&'a str>>,
}

impl Tower<'_> {
    pub fn root(&self) -> &str {
        if let Some(&root) = self.root.borrow().as_ref() {
            return root;
        }

        let root = *self
            .programs
            .keys()
            .find(|&name| self.programs.values().all(|p| !p.children.contains(name)))
            .unwrap();
        *self.root.borrow_mut() = Some(root);
        root
    }
}

pub fn star_1(tower: &Tower) -> SolType1 {
    tower.root().into()
}

Star 2

Recursively move up the tower along the imbalanced nodes.

Since exactly one node has a wrong weight, the root must be imbalanced.

An imbalanced node has one unique child whose total weight differs from the common total weight of all other children.

If this child’s children all have the same weight, i.e., if this child is balanced, its own weight needs to be corrected. Otherwise the child is imbalanced and the recursion proceeds.

 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
impl Program<'_> {
    pub fn total_weight(&self, tower: &Tower) -> SolType2 {
        if let Some(&total_weight) = self.total_weight.borrow().as_ref() {
            return total_weight;
        }

        // calculate total weight and store for later use
        let total_weight = self.weight
            + self
                .children
                .iter()
                .map(|child| tower.programs[child].total_weight(tower))
                .sum::<SolType2>();
        *self.total_weight.borrow_mut() = Some(total_weight);
        total_weight
    }
}

pub fn fix_wrong(name: &str, tower: &Tower) -> Option<SolType2> {
    let program = &tower.programs[name];

    // with less than 3 children, there cannot be a single child which has
    // the wrong weight
    if program.children.len() < 3 {
        return None;
    }

    // get children sorted by total weight
    let mut ws = program
        .children
        .iter()
        .map(|child| &tower.programs[child])
        .collect::<Vec<_>>();
    ws.sort_unstable_by_key(|child| child.total_weight(tower));

    // get min, max and mid, the latter has total weight equal
    // to either min's or max's total weight
    let min = ws[0];
    let mid = ws[1];
    let max = ws[ws.len() - 1];

    // if min and max have equal total weight, there is no imbalance
    if min.total_weight(tower) == max.total_weight(tower) {
        return None;
    }

    let (ok, nok) = match mid.total_weight(tower) == min.total_weight(tower) {
        true => (min, max),
        false => (max, min),
    };

    // recurse; if children are balanced, I am wrong
    fix_wrong(nok.name, tower)
        .or_else(|| Some(nok.weight + ok.total_weight(tower) - nok.total_weight(tower)))
}

pub fn star_2(tower: &Tower) -> SolType2 {
    fix_wrong(tower.root(), tower).unwrap()
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"pbga (66)
xhth (57)
ebii (61)
havc (66)
ktlj (57)
fwft (72) -> ktlj, cntj, xhth
qoyq (66)
padx (45) -> pbga, havc, qoyq
tknk (41) -> ugml, padx, fwft
jptl (61)
ugml (68) -> gyxo, ebii, jptl
gyxo (61)
cntj (57)
"#;

    #[test]
    pub fn test_from() {
        let tower = Tower::from(CONTENT);
        println!("{:?}", tower);
    }

    #[test]
    pub fn test_star_1() {
        assert_eq!("tknk", star_1(&CONTENT.into()));
    }

    #[test]
    pub fn test_star_2() {
        assert_eq!(60, star_2(&CONTENT.into()));
    }
}

Day 8: I Heard You Like Registers

Rust solution to AoC|2017|8.

Manipulate registers and keep track of max values.

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData<'a>(pub Vec<(&'a str, i32, &'a str, &'a str, i32)>);

    impl<'a> From<&'a str> for PuzzleData<'a> {
        fn from(s: &'a str) -> Self {
            Self(
                s.lines()
                    .map(str::split_ascii_whitespace)
                    .map(|mut w| {
                        (
                            w.next().unwrap(),
                            if Some("inc") == w.next() { 1 } else { -1 }
                                * w.next().unwrap().parse::<i32>().unwrap(),
                            w.nth(1).unwrap(), // skip "if"
                            w.next().unwrap(),
                            w.next().unwrap().parse().unwrap(),
                        )
                    })
                    .collect(),
            )
        }
    }
}

Star 1

Use a HashMap for the registers. Iterate over instructions to build final register values, then iterate over values to find maximum.

 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
pub fn star_1(PuzzleData(instructions): &PuzzleData) -> SolType {
    instructions
        .iter()
        .fold(
            HashMap::new(),
            |mut regs, &(r_tar, val_tar, r_pred, cmp, val_pred)| {
                let a = *regs.get(r_pred).unwrap_or(&0);
                if match cmp {
                    "<" => a < val_pred,
                    "<=" => a <= val_pred,
                    "==" => a == val_pred,
                    "!=" => a != val_pred,
                    ">=" => a >= val_pred,
                    ">" => a > val_pred,
                    _ => panic!(),
                } {
                    *regs.entry(r_tar).or_insert(0) += val_tar;
                }
                regs
            },
        )
        .values()
        .max()
        .copied()
        .unwrap()
}

Star 2

Extend part 1 to keep track of maximum while iterating. The iteration over the values at the end of the process is not required.

Comparing the time it takes to solve parts 1 and 2, it appears that part 1 spends most of the time iterating over the register values to find the largest value.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pub fn star_2(PuzzleData(instructions): &PuzzleData) -> SolType {
    let (mx, _) = instructions.iter().fold(
        (0, HashMap::new()),
        |(mut mx, mut regs), &(r_tar, val_tar, r_pred, cmp, val_pred)| {
            let a = *regs.get(r_pred).unwrap_or(&0);
            if match cmp {
                "<" => a < val_pred,
                "<=" => a <= val_pred,
                "==" => a == val_pred,
                "!=" => a != val_pred,
                ">=" => a >= val_pred,
                ">" => a > val_pred,
                _ => panic!(),
            } {
                let v = regs.entry(r_tar).or_insert(0);
                *v += val_tar;
                mx = mx.max(*v);
            }
            (mx, regs)
        },
    );
    mx
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"b inc 5 if a > 1
a inc 1 if b < 5
c dec -10 if a >= 1
c inc -20 if c == 10
"#;

    #[test]
    pub fn test_from() {
        let PuzzleData(instructions) = PuzzleData::from(CONTENT);
        assert_eq!(
            vec![
                ("b", 5, "a", ">", 1),
                ("a", 1, "b", "<", 5),
                ("c", 10, "a", ">=", 1),
                ("c", -20, "c", "==", 10),
            ],
            instructions,
        );
    }

    #[test]
    pub fn test_star_1() {
        assert_eq!(1, star_1(&CONTENT.into()));
    }

    #[test]
    pub fn test_star_2() {
        assert_eq!(10, star_2(&CONTENT.into()));
    }
}

Day 9: Stream Processing

Rust solution to AoC|2017|9.

Clean garbage from character stream.

Star 1

Recursively process stream groups (the solution is obviously refactored to also work for part 2).

 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
pub fn process<B, T, A, G, F>(st: B, init: &T, acc: A, gc: G, fin: F) -> (T, usize)
where
    B: AsRef<[u8]>,          // stream type, convertible to &[u8]
    T: Clone,                // value type
    A: Fn(T, T) -> T + Copy, // accumulate to values
    G: Fn(T) -> T + Copy,    // update value to collect garbage
    F: Fn(T) -> T + Copy,    // finalize value before returning
{
    let st = st.as_ref();
    assert!(st[0] == b'{');
    let (mut pos, mut gb, mut v) = (1, false, init.to_owned());
    loop {
        (pos, gb, v) = match st[pos] {
            b'!' if gb => (pos + 2, gb, v),
            b'>' if gb => (pos + 1, false, v),
            b'<' if !gb => (pos + 1, true, v),
            b'}' if !gb => break,
            b'{' if !gb => {
                let (sub_v, sub_len) = process(&st[pos..], init, acc, gc, fin);
                (pos + sub_len, false, acc(v, sub_v))
            }
            _ if gb => (pos + 1, gb, gc(v)),
            _ => (pos + 1, gb, v),
        };
    }
    (fin(v), pos + 1)
}

pub fn star_1(st: &&str) -> SolType {
    // value type is a tuple of score and nested group count

    // add scores and nested groups count
    let acc = |(s0, n0), (s1, n1)| (s0 + s1, n0 + n1);
    // identity - do not collect garbage
    let gc = |v| v;
    // include self by adding it to nested groups counts and
    // adding 1 for each nested group to score
    let fin = |(s, n)| (s + n + 1, n + 1);

    let ((s, _), _) = process(st, &(0, 0), acc, gc, fin);
    s
}

Star 2

Another recursion. A trigger to make the solution for part 1 generic.

1
2
3
4
5
6
7
pub fn star_2(st: &&str) -> SolType {
    // value type is a scalar representing collected garbage
    // gc increments by 1, final processing is identity

    let (cl, _) = process(st, &0, |v1, v2| v1 + v2, |v| v + 1, |v| v);
    cl
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn test_star_1() {
        assert_eq!(1, star_1(&"{}"));
        assert_eq!(6, star_1(&"{{{}}}"));
        assert_eq!(5, star_1(&"{{},{}}"));
        assert_eq!(16, star_1(&"{{{},{},{{}}}}"));
        assert_eq!(1, star_1(&"{<a>,<a>,<a>,<a>}"));
        assert_eq!(9, star_1(&"{{<ab>},{<ab>},{<ab>},{<ab>}}"));
        assert_eq!(9, star_1(&"{{<!!>},{<!!>},{<!!>},{<!!>}}"));
        assert_eq!(3, star_1(&"{{<a!>},{<a!>},{<a!>},{<ab>}}"));
    }

    #[test]
    pub fn test_star_2() {
        assert_eq!(0, star_2(&"{<>}"));
        assert_eq!(17, star_2(&"{<random characters>}"));
        assert_eq!(3, star_2(&"{<<<<>}"));
        assert_eq!(2, star_2(&"{<{!>}>}"));
        assert_eq!(0, star_2(&"{<!!>}"));
        assert_eq!(0, star_2(&"{<!!!>>}"));
        assert_eq!(10, star_2(&r#"{<{o"i!a,<{i<a>}"#));
    }
}

Day 10: Knot Hash

Rust solution to AoC|2017|10.

Very special hashing function

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData<'a>(pub &'a str, pub Vec<u8>);

    impl<'a> From<&'a str> for PuzzleData<'a> {
        fn from(s: &'a str) -> Self {
            Self(
                s.trim(),
                s.trim()
                    .split(',')
                    .filter(|v| !v.is_empty())
                    .map(|v| v.parse().unwrap())
                    .collect(),
            )
        }
    }
}

Star 1

Get the basics for the hashing right.

In case the ranges to reverse wrap around, I copy the data into a temporary buffer, reverse the order in the temporary buffer and copy back to the buffer.

Otherwise, the slice can be reversed in place.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fn knot(buf: Vec<u8>, pos: usize, skip: usize, lengths: &[u8]) -> (Vec<u8>, usize, usize) {
    let buf_len = buf.len();
    lengths
        .iter()
        .map(|&len| len as usize)
        .fold((buf, pos, skip), |(mut buf, pos, skip), len| {
            if pos + len > buf.len() {
                let mut temp = vec![0; len];
                temp[..buf_len - pos].copy_from_slice(&buf[pos..]);
                temp[buf_len - pos..].copy_from_slice(&buf[..len + pos - buf_len]);
                temp.reverse();
                buf[pos..].copy_from_slice(&temp[..buf_len - pos]);
                buf[..len + pos - buf_len].copy_from_slice(&temp[buf_len - pos..]);
            } else {
                buf[pos..pos + len].reverse();
            }
            (buf, (pos + len + skip) % buf_len, skip + 1)
        })
}

pub fn star_1(PuzzleData(_, lengths): &PuzzleData) -> SolType1 {
    let (buf, _, _) = knot((0..=255).collect(), 0, 0, lengths);
    buf[0] as u32 * buf[1] as u32
}

Star 2

Do exactly what the puzzle description says:

  • Interpret the input as ASCII bytes and append the suffix.

  • Apply 64 rounds of knots (first iter-fold)

  • XOR blocks of 16 values and output as hex string (second iter-map-fold with nested iter-fold for XORs)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const DIGITS: [char; 16] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];

pub fn knot_hash<T: AsRef<[u8]>>(text: T) -> String {
    let lengths = [text.as_ref(), &[17, 31, 73, 47, 23]].concat();

    let (buf, _, _) = (0..64).fold(((0..=255).collect(), 0, 0), |(buf, pos, skip), _| {
        knot(buf, pos, skip, &lengths)
    });

    (0..=255)
        .step_by(16)
        .map(|k| (k..k + 16).fold(0, |v, k| v ^ buf[k]))
        .fold(String::with_capacity(32), |mut s, v| {
            s.push(DIGITS[(v >> 4) as usize]);
            s.push(DIGITS[(v & 15) as usize]);
            s
        })
}

pub fn star_2(PuzzleData(text, _): &PuzzleData) -> SolType2 {
    knot_hash(text)
}

Tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"3,4,1,5"#;

    #[test]
    pub fn test_knot() {
        let PuzzleData(_, lengths) = &CONTENT.into();
        let (buf, pos, skip) = knot((0..5).collect(), 0, 0, lengths);
        assert_eq!(vec![3, 4, 2, 1, 0], buf);
        assert_eq!((4, 4), (pos, skip));
    }

    #[test]
    pub fn test_knot_hash() {
        assert_eq!("a2582a3a0e66e6e86e3812dcb672a272", knot_hash(""));
        assert_eq!("33efeb34ea91902bb2f59c9920caa6cd", knot_hash("AoC 2017"));
        assert_eq!("3efbe78a8d82f29979031a4aa0b16a9d", knot_hash("1,2,3"));
        assert_eq!("63960835bcdc130f0b66d7ff4f6a5a8e", knot_hash("1,2,4"));
    }
}

Day 11: Hex Ed

Rust solution to AoC|2017|11.

Rescue a child process which got lost in a hex grid.

Star 1

A position in the grid can be represented by x and y value where steps to the north or south decrement or increment the y value by 2 and steps to the north-east, north-west, south-west or south-east move diagonally.

The position of the child is determined with an iter-fold and the maximum number of steps equals the absolute value of the x coordinate plus the excess of the absolute value of the y coordinate (if any) divided by 2.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
fn go(step: &str, (x, y): (SolType, SolType)) -> (SolType, SolType) {
    match step {
        "ne" => (x + 1, y - 1),
        "n" => (x, y - 2),
        "nw" => (x - 1, y - 1),
        "sw" => (x - 1, y + 1),
        "s" => (x, y + 2),
        "se" => (x + 1, y + 1),
        _ => panic!("Unknown: '{}'", step),
    }
}

pub fn star_1(path: &&str) -> SolType {
    let (x, y) = path
        .trim()
        .split(',')
        .fold((0, 0), |(x, y), step| go(step, (x, y)));

    x.abs() + (y.abs() - x.abs()).max(0) / 2
}

Star 2

Replace the fold by a scan which updates the position and yields the distance, and take the maximum.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pub fn star_2(path: &&str) -> SolType {
    path.trim()
        .split(',')
        .scan((0, 0), |(x, y), step| {
            (*x, *y) = go(step, (*x, *y));
            Some(x.abs() + (y.abs() - x.abs()).max(0) / 2)
        })
        .max()
        .unwrap()
}

Tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn test_star_1() {
        assert_eq!(3, star_1(&"ne,ne,ne"));
        assert_eq!(0, star_1(&"ne,ne,sw,sw"));
        assert_eq!(2, star_1(&"ne,ne,s,s"));
        assert_eq!(3, star_1(&"se,sw,se,sw,sw"));
    }
}

Day 12: Digital Plumber

Rust solution to AoC|2017|12.

Find connected parts of a graph.

Input

Parse input into a map with elements as keys and a vec of adjacents as values.

 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
pub mod input {
    use std::collections::HashMap;

    use crate::SolType;

    #[derive(Debug)]
    pub struct PuzzleData {
        pub map: HashMap<SolType, Vec<SolType>>,
    }

    impl From<&str> for PuzzleData {
        /// parse the puzzle input
        fn from(s: &str) -> Self {
            let map = s
                .lines()
                .map(|l| l.split_once(" <-> ").unwrap())
                .map(|(a, b)| {
                    (
                        a.parse::<SolType>().unwrap(),
                        b.split(", ")
                            .map(|n| n.parse::<SolType>().unwrap())
                            .collect::<Vec<_>>(),
                    )
                })
                .collect();
            Self { map }
        }
    }
}

Star 1

DFS finds all items reachable from 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
pub fn group(n: SolType, map: &HashMap<SolType, Vec<SolType>>) -> HashSet<SolType> {
    let mut group = HashSet::from([n]);
    let mut queue = Vec::from([n]);
    while let Some(n) = queue.pop() {
        for &m in map.get(&n).unwrap() {
            if group.insert(m) {
                queue.push(m);
            }
        }
    }
    group
}

pub fn star_1(data: &PuzzleData) -> SolType {
    group(0, &data.map).len()
}

Star 2

Build groups repeatedly until all items have been visited.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pub fn star_2(data: &PuzzleData) -> SolType {
    let mut numbers: HashSet<SolType> = HashSet::from_iter(data.map.keys().cloned());
    let mut count = 0;
    while !numbers.is_empty() {
        let g = group(*numbers.iter().next().unwrap(), &data.map);
        numbers.retain(|n| !g.contains(n));
        count += 1;
    }
    count
}

Tests

Standard tests.

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"0 <-> 2
1 <-> 1
2 <-> 0, 3, 4
3 <-> 2, 4
4 <-> 2, 3, 6
5 <-> 6
6 <-> 4, 5
"#;

    #[test]
    pub fn test_from() {
        let data = PuzzleData::from(CONTENT);
        println!("{data:?}");
    }

    #[test]
    pub fn test_star_1() {
        let data = PuzzleData::from(CONTENT);
        assert_eq!(6, star_1(&data));
    }

    #[test]
    pub fn test_star_2() {
        let data = PuzzleData::from(CONTENT);
        assert_eq!(2, star_2(&data));
    }
}

Day 13: Packet Scanners

Rust solution to AoC|2017|13.

Determine whether scanners detect me traveling on a packet through a firewall.

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData(pub Vec<(usize, usize)>);

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            Self(
                s.lines()
                    .map(|l| l.split_once(": ").unwrap())
                    .map(|(d, r)| (d.parse().unwrap(), r.parse().unwrap()))
                    .collect(),
            )
        }
    }
}

Star 1

Since scanners move up and down in layers of a given range, they are at the top of the layer (where they potentially catch me) after a any number of steps that is a multiple of 2 range - 2. I will be at a given layer after a number of steps equal to the layer’s depth. With this, the solution is a simple iter-map-sum.

1
2
3
4
5
6
7
pub fn star_1(PuzzleData(layers): &PuzzleData) -> SolType1 {
    layers
        .iter()
        .filter(|&&(depth, range)| depth % (2 * range - 2) == 0)
        .map(|&(depth, range)| depth * range)
        .sum()
}

Star 2

Based on the solution of part 1, solve part 2 using a linear search over possible delays, essentially wrapping the adopted iterator from part 1 in another iterator for the linear search.

1
2
3
4
5
6
7
8
9
pub fn star_2(PuzzleData(layers): &PuzzleData) -> SolType2 {
    (0..)
        .find(|delay| {
            layers
                .iter()
                .all(|(depth, range)| (depth + delay) % (2 * range - 2) != 0)
        })
        .unwrap()
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"0: 3
1: 2
4: 4
6: 4
"#;

    #[test]
    pub fn test_from() {
        let PuzzleData(layers) = PuzzleData::from(CONTENT);
        assert_eq!(vec![(0, 3), (1, 2), (4, 4), (6, 4)], layers);
    }

    #[test]
    pub fn test_star_1() {
        assert_eq!(24, star_1(&CONTENT.into()));
    }

    #[test]
    pub fn test_star_2() {
        assert_eq!(10, star_2(&CONTENT.into()));
    }
}

Day 14: Disk Defragmentation

Rust solution to AoC|2017|14.

The knot hash code is taken from the solution to AoC|2017|10

Star 1

For part 1, we simply need to count bits set in the hashes.

1
2
3
4
5
6
7
pub fn star_1(&prefix: &&str) -> SolType1 {
    (0..128)
        .map(|suffix| {
            knot_hash(&format!("{}-{}", prefix.trim(), suffix)).fold(0, |s, b| s + b.count_ones())
        })
        .sum()
}

Star 2

For part 2, I build a grid represented as a string. Used squares are represented by ones ('1'), unused squares are represented by zeros ('0').

While there is a used square, which is not yet processed, do a BFT to find all used squares reachable from this square. Those build one region.

 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
pub fn star_2(&prefix: &&str) -> SolType2 {
    const USED: u8 = b'1';
    const D: [(isize, isize); 4] = [(1, 0), (0, -1), (-1, 0), (0, 1)];
    const W: usize = 128;

    let s = (0..128).fold(String::new(), |s, suffix| {
        knot_hash(&format!("{}-{}", prefix.trim(), suffix)).fold(s, |mut s, v| {
            let _ = write!(s, "{:08b}", v);
            s
        })
    });
    let grid = s.as_bytes();

    let mut processed = vec![false; W * W];
    let mut regions = 0;
    let mut p0 = 0;
    while let Some(p) = (p0..W * W).find(|&p| grid[p] == USED && !processed[p]) {
        p0 = p;
        regions += 1;

        processed[p] = true;
        let mut queue = VecDeque::from([(p % W, p / W)]);
        while let Some((x, y)) = queue.pop_front() {
            for (x_adj, y_adj) in D
                .iter()
                .map(|&(d_x, d_y)| (x.wrapping_add_signed(d_x), y.wrapping_add_signed(d_y)))
                .filter(|&(x_adj, y_adj)| x_adj < W && y_adj < W && grid[x_adj + W * y_adj] == USED)
            {
                if !processed[x_adj + W * y_adj] {
                    processed[x_adj + W * y_adj] = true;
                    queue.push_back((x_adj, y_adj));
                }
            }
        }
    }
    regions
}

Day 15: Dueling Generators

Rust solution to AoC|2017|15.

Find matching numbers from two sequences.

A nice little one…​

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData(pub u64, pub u64);

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            let mut vals = s.lines().map(|l| l[24..].parse().unwrap());
            Self(vals.next().unwrap(), vals.next().unwrap())
        }
    }
}

Star 1

This calls for iter_a.zip(iter_b). Iterators are constructed using successors.

Take 40 million zipped pairs, filter to keep only those whose lower 16 bits are equal, and count the resulting pairs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const F_A: u64 = 16_807;
const F_B: u64 = 48_271;
const M: u64 = 2_147_483_647;
const MASK: u64 = (1 << 16) - 1;

pub fn star_1(&PuzzleData(init_a, init_b): &PuzzleData) -> SolType {
    successors(Some(init_a), |&a| Some((a * F_A) % M))
        .zip(successors(Some(init_b), |&b| Some((b * F_B) % M)))
        .take(40_000_000)
        .filter(|&(a, b)| a & MASK == b & MASK)
        .count()
}

Star 2

Simple modification of part 1 by adding filters prior to zipping.

1
2
3
4
5
6
7
8
pub fn star_2(&PuzzleData(init_a, init_b): &PuzzleData) -> SolType {
    successors(Some(init_a), |&a| Some((a * F_A) % M))
        .filter(|&a| a & 3 == 0)
        .zip(successors(Some(init_b), |&b| Some((b * F_B) % M)).filter(|&b| b & 7 == 0))
        .take(5_000_000)
        .filter(|&(a, b)| a & MASK == b & MASK)
        .count()
}

Day 16: Permutation Promenade

Rust solution to AoC|2017|16.

Let’s dance!

Input

 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
pub mod input {
    use crate::Move;

    #[derive(Debug)]
    pub struct Dance(pub Vec<Move>);

    impl From<&str> for Move {
        fn from(s: &str) -> Self {
            let mut vals = s[1..].split('/');
            match s.as_bytes()[0] {
                b's' => Self::S(vals.next().unwrap().parse().unwrap()),
                b'x' => Self::X(
                    vals.next().unwrap().parse().unwrap(),
                    vals.next().unwrap().parse().unwrap(),
                ),
                b'p' => Self::P(
                    vals.next().unwrap().as_bytes()[0] as _,
                    vals.next().unwrap().as_bytes()[0] as _,
                ),
                _ => panic!("What should I do with {}", s),
            }
        }
    }

    impl From<&str> for Dance {
        fn from(s: &str) -> Self {
            Self(s.split(',').map(Move::from).collect())
        }
    }
}

Star 1

Straight forward simulation.

 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
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Move {
    S(usize),
    X(usize, usize),
    P(char, char),
}

impl Move {
    pub fn dance(self, mut f: Vec<char>) -> Vec<char> {
        match self {
            Self::S(offset) => f.rotate_right(offset),
            Self::X(a, b) => (f[a], f[b]) = (f[b], f[a]),
            Self::P(a, b) => {
                // single pass linear search
                let (a, b) = f
                    .iter()
                    .enumerate()
                    .scan((None, None), |(c_a, c_b), (k, &v)| {
                        (*c_a, *c_b) = (
                            if v == a { Some(k) } else { *c_a },
                            if v == b { Some(k) } else { *c_b },
                        );
                        Some((*c_a, *c_b))
                    })
                    .filter_map(|(a, b)| a.and_then(|a| b.map(|b| (a, b))))
                    .next()
                    .unwrap();
                (f[a], f[b]) = (f[b], f[a]);
            }
        }
        f
    }
}

pub fn star_1(Dance(moves): &Dance) -> SolType {
    moves
        .iter()
        .fold(('a'..='p').collect::<Vec<_>>(), |f, &m| m.dance(f))
        .iter()
        .collect()
}

Star 2

Simulate until the initial formation appears again. Fast forward and simulate the remaining rounds.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pub fn star_2(Dance(moves): &Dance) -> SolType {
    let f0 = ('a'..='p').collect::<Vec<_>>();
    let mut f = f0.clone();

    let mut cnt = 0;
    loop {
        cnt += 1;
        f = moves.iter().fold(f, |f, m| m.dance(f));
        if f == f0 {
            break;
        }
    }

    // fast forward
    let n = 1_000_000_000;
    let cnt = (n / cnt) * cnt;

    // remaining simulation
    for _ in cnt..n {
        f = moves.iter().fold(f, |f, m| m.dance(f));
    }

    f.iter().collect()
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"s1,x3/4,pe/b"#;

    #[test]
    pub fn test_from() {
        let Dance(moves) = Dance::from(CONTENT);
        assert_eq!(vec![Move::S(1), Move::X(3, 4), Move::P('e', 'b')], moves);
    }

    #[test]
    pub fn test_dance() {
        let Dance(moves) = Dance::from(CONTENT);

        let mut f = ('a'..='e').collect::<Vec<_>>();
        assert_eq!("abcde", f.iter().collect::<String>());

        for m in moves {
            f = m.dance(f);
            println!("{}", f.iter().collect::<String>());
        }

        assert_eq!("baedc", f.iter().collect::<String>());
    }
}

Day 17: Spinlock

Rust solution to AoC|2017|17.

Input

1
2
3
4
5
6
7
8
9
pub mod input {
    pub struct Step(pub usize);

    impl From<&str> for Step {
        fn from(s: &str) -> Self {
            Self(s.trim().parse().unwrap())
        }
    }
}

Star 1

Fill the buffer and see what happens.

1
2
3
4
5
6
7
8
pub fn star_1(&Step(step): &Step) -> SolType {
    let (buf, cur) = (1..=2_017).fold((vec![0], 0), |(mut buf, mut cur), k| {
        cur = (cur + step) % k + 1;
        buf.insert(cur, k);
        (buf, cur)
    });
    buf[(cur + 1) % buf.len()]
}

Star 2

The key observation for the second part is that we do not need a buffer at all. Just store values that end up immediately after 0. The last such value is the answer for part 2.

1
2
3
4
5
6
7
pub fn star_2(&Step(step): &Step) -> SolType {
    let (val, _) = (1..=50_000_000).fold((0, 0), |(val, cur), k| match (cur + step) % k {
        0 => (k, 1),
        pos => (val, pos + 1),
    });
    val
}

Day 18: Duet

Rust solution to AoC|2017|18.

Play sounds or rather send and receive packets…​

Input

 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
pub mod input {
    use crate::{Instr, Value};

    #[derive(Debug)]
    pub struct PuzzleData(pub Vec<Instr>);

    impl From<&str> for Value {
        fn from(s: &str) -> Self {
            let b = s.as_bytes()[0];
            if b.is_ascii_lowercase() {
                Self::Reg((b - b'a') as _)
            } else {
                Self::Val(s.parse().unwrap())
            }
        }
    }

    impl From<&str> for Instr {
        fn from(s: &str) -> Self {
            let mut words = s.split_ascii_whitespace();
            let instr = words.next().unwrap();
            let x = words.next().unwrap();
            let y = words.next().map(Value::from);
            match instr {
                "snd" => Self::Snd(x.into()),
                "set" => Self::Set((x.as_bytes()[0] - b'a') as _, y.unwrap()),
                "add" => Self::Add((x.as_bytes()[0] - b'a') as _, y.unwrap()),
                "mul" => Self::Mul((x.as_bytes()[0] - b'a') as _, y.unwrap()),
                "mod" => Self::Mod((x.as_bytes()[0] - b'a') as _, y.unwrap()),
                "rcv" => Self::Rcv((x.as_bytes()[0] - b'a') as _),
                "jgz" => Self::Jgz(x.into(), y.unwrap()),
                _ => panic!("Unexpected instruction {}", s),
            }
        }
    }

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            Self(s.trim().lines().map(Instr::from).collect())
        }
    }
}

Star 1

Run the program until it 'recovers' a 'frequency'.

The code is refactored to also work for part 2, therefore the frequency is stored in an outbox and the execute function is passed a closure to use for rcv instructions which yields None if the program is to be terminated (because a frequency is restored).

  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
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Instr {
    Snd(Value),
    Set(usize, Value),
    Add(usize, Value),
    Mul(usize, Value),
    Mod(usize, Value),
    Rcv(usize),
    Jgz(Value, Value),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Value {
    Reg(usize),
    Val(i64),
}

impl Instr {
    pub fn execute<R, F>(
        &self,
        reg: &mut R,
        pos: &mut usize,
        outbox: &mut VecDeque<i64>,
        mut rcv: F,
    ) -> bool
    where
        R: IndexMut<usize, Output = i64>,
        F: FnMut(i64) -> Option<i64>,
    {
        match *self {
            Instr::Set(x, y) => (reg[x], *pos) = (y.get(reg), *pos + 1),
            Instr::Add(x, y) => (reg[x], *pos) = (reg[x] + y.get(reg), *pos + 1),
            Instr::Mul(x, y) => (reg[x], *pos) = (reg[x] * y.get(reg), *pos + 1),
            Instr::Mod(x, y) => (reg[x], *pos) = (reg[x] % y.get(reg), *pos + 1),
            Instr::Snd(x) => {
                outbox.push_back(x.get(reg));
                *pos += 1;
            }
            Instr::Rcv(x) => {
                let Some(v) = rcv(reg[x]) else { return false };
                (reg[x], *pos) = (v, *pos + 1);
            }
            Instr::Jgz(x, y) => {
                *pos = match x.get(reg) {
                    v if v > 0 => pos.wrapping_add_signed(y.get(reg) as _),
                    _ => *pos + 1,
                }
            }
        }
        true
    }
}

impl Value {
    pub fn get<R: Index<usize, Output = i64>>(&self, reg: &R) -> i64 {
        match *self {
            Value::Reg(idx) => reg[idx],
            Value::Val(val) => val,
        }
    }
}

/// Dynamically growing registers
/// This spares us from figuring out the largest register from the input
#[derive(Default)]
pub struct Reg(Vec<i64>);

mod reg {
    use std::ops::{Index, IndexMut};

    use crate::Reg;

    impl Index<usize> for Reg {
        type Output = i64;

        fn index(&self, index: usize) -> &Self::Output {
            if index >= self.0.len() {
                &0
            } else {
                &self.0[index]
            }
        }
    }

    impl IndexMut<usize> for Reg {
        /// Will grow registers as needed to be able to write to the given index
        fn index_mut(&mut self, index: usize) -> &mut Self::Output {
            if index >= self.0.len() {
                self.0.resize(index + 1, 0);
            }
            &mut self.0[index]
        }
    }
}

pub fn star_1(PuzzleData(program): &PuzzleData) -> SolType1 {
    let mut pos = 0;
    let mut reg = Reg::default();
    let mut outbox = VecDeque::new();

    while program[pos].execute(&mut reg, &mut pos, &mut outbox, |v| match v {
        0 => Some(0),
        _ => None,
    }) {}

    outbox.pop_back().unwrap()
}

Star 2

Two programs run 'simultaneously' sending messages to each other through queues.

The rcv closure from part 1 is replaced by a closure which yields the first element from the program’s inbox if any.

In my implementation, both programs run alternately. Each program runs until it terminates (instruction pointer beyond program length) or tries to read a packet from an empty queue.

This is repeated until no program performs any steps.

 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
pub fn star_2(PuzzleData(program): &PuzzleData) -> SolType2 {
    // initialize programs
    let (mut pos_0, mut pos_1) = (0, 0);
    let (mut reg_0, mut reg_1) = (Reg::default(), Reg::default());
    (reg_0[(b'p' - b'a') as _], reg_1[(b'p' - b'a') as _]) = (0, 1);
    let (mut box_0, mut box_1) = (VecDeque::new(), VecDeque::new());

    // counter for sends by program 1
    let mut send_count = 0;

    let mut running = true;
    while running {
        running = false;

        // run program 0
        while pos_0 < program.len()
            && program[pos_0].execute(&mut reg_0, &mut pos_0, &mut box_1, |_| box_0.pop_front())
        {
            running = true;
        }

        // run program 1 and update send count
        send_count -= box_0.len(); // don't count items that remain in queue twice
        while pos_1 < program.len()
            && program[pos_1].execute(&mut reg_1, &mut pos_1, &mut box_0, |_| box_1.pop_front())
        {
            running = true;
        }
        send_count += box_0.len();
    }

    send_count
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT_1: &str = r#"set a 1
add a 2
mul a a
mod a 5
snd a
set a 0
rcv a
jgz a -1
set a 1
jgz a -2
"#;

    #[test]
    pub fn test_from() {
        let PuzzleData(program) = PuzzleData::from(CONTENT_1);
        assert_eq!(
            vec![
                Instr::Set(0, Value::Val(1)),
                Instr::Add(0, Value::Val(2)),
                Instr::Mul(0, Value::Reg(0)),
                Instr::Mod(0, Value::Val(5)),
                Instr::Snd(Value::Reg(0)),
                Instr::Set(0, Value::Val(0)),
                Instr::Rcv(0),
                Instr::Jgz(Value::Reg(0), Value::Val(-1)),
                Instr::Set(0, Value::Val(1)),
                Instr::Jgz(Value::Reg(0), Value::Val(-2)),
            ],
            program
        )
    }

    #[test]
    pub fn test_star_1() {
        assert_eq!(4, star_1(&CONTENT_1.into()));
    }

    const CONTENT_2: &str = r#"snd 1
snd 2
snd p
rcv a
rcv b
rcv c
rcv d
"#;

    #[test]
    pub fn test_star_2() {
        assert_eq!(3, star_2(&CONTENT_2.into()));
    }
}

Day 19: A Series of Tubes

Rust solution to AoC|2017|19.

Move along a path and collect letters

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData {
        pub w: usize,
        pub h: usize,
        pub grid: Vec<u8>,
    }

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            let w = s.find('\n').unwrap();
            let h = s.len() / (w + 1);
            let grid = s.bytes().filter(|&b| b != b'\n').collect();
            Self { w, h, grid }
        }
    }
}

Star 1 & 2

Move along path and collect letters. Nothing special here.

To determine the direction to turn at crossings +, the algorithm looks at the adjacents in any direction and chooses the first that matches.

 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
const D: [(isize, isize); 4] = [(1, 0), (0, -1), (-1, 0), (0, 1)];

pub fn star_1_2(PuzzleData { w, h, grid }: &PuzzleData) -> SolType {
    let (mut d, (mut x, mut y)) = (3, (grid.iter().position(|&b| b == b'|').unwrap(), 0));
    let (mut res, mut count) = (String::new(), 0);

    while x < *w && y < *h && grid[x + w * y] != b' ' {
        count += 1;

        if grid[x + w * y].is_ascii_uppercase() {
            res.push(grid[x + w * y] as char);
        }

        if grid[x + w * y] == b'+' {
            (d, (x, y)) = [d + 1, d + 3]
                .into_iter()
                .map(|d| d & 0b11)
                .map(|d| {
                    (
                        d,
                        (x.wrapping_add_signed(D[d].0), y.wrapping_add_signed(D[d].1)),
                    )
                })
                .find(|&(_, (x, y))| x < *w && y < *h && grid[x + w * y] != b' ')
                .unwrap()
        } else {
            (x, y) = (x.wrapping_add_signed(D[d].0), y.wrapping_add_signed(D[d].1));
        }
    }

    (res, count).into()
}

Tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"     |         
     |  +--+   
     A  |  C   
 F---|----E|--+
     |  |  |  D
     +B-+  +--+
"#;

    #[test]
    pub fn test_star_1_2() {
        assert_eq!(
            Solutions::from(("ABCDEF".into(), 38)),
            star_1_2(&CONTENT.into())
        );
    }
}

Day 20: Particle Swarm

Rust solution to AoC|2017|20.

Input

Parse input into a vec (for each particle) of arrays (for each of position, velocity, acceleration) of arrays (for each dimension x, y, and z)

 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
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData {
        pub list: Vec<[[isize; 3]; 3]>,
    }

    pub fn parse_group(g: &str) -> [isize; 3] {
        let mut g = g[3..g.len() - 1]
            .split(',')
            .map(|v| v.parse::<isize>().unwrap());
        [g.next().unwrap(), g.next().unwrap(), g.next().unwrap()]
    }

    pub fn parse_line(l: &str) -> [[isize; 3]; 3] {
        let mut p = l.split(", ");
        [
            parse_group(p.next().unwrap()),
            parse_group(p.next().unwrap()),
            parse_group(p.next().unwrap()),
        ]
    }

    impl From<&str> for PuzzleData {
        /// parse the puzzle input
        fn from(s: &str) -> Self {
            Self {
                list: s.lines().map(parse_line).collect(),
            }
        }
    }
}

Star 1

That was trivial

1
2
3
4
5
6
7
8
pub fn star_1(data: &PuzzleData) -> SolType1 {
    data.list
        .iter()
        .enumerate()
        .min_by_key(|(_, v)| v[2].iter().map(|w| w.abs()).sum::<isize>())
        .unwrap()
        .0
}

Star 2

That was more complicated than what I expected. I thought it would be possible to somehow efficiently simulate this and come up with a condition when simulation can be stopped because there are no more collisions, but I failed.

My solution now explicitly calculates potential collisions by solving quadratic equations. I don’t really like this…​ would have preferred to have a solution that stays in the integer domain.

  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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#[cfg(not(feature = "sim"))]
#[derive(PartialEq, Eq)]
pub enum Step {
    None,
    One(usize),
    Two(usize, usize),
    Any,
}

#[cfg(not(feature = "sim"))]
impl Step {
    pub fn for_particles(a: &[[isize; 3]; 3], b: &[[isize; 3]; 3]) -> Self {
        Self::for_dim(a, b, 0)
            .and(|| Self::for_dim(a, b, 1))
            .and(|| Self::for_dim(a, b, 2))
    }

    pub fn for_dim(a: &[[isize; 3]; 3], b: &[[isize; 3]; 3], dim: usize) -> Self {
        // 0 = d_p + k * d_v + k * (k + 1) * d_a
        let d_a = a[2][dim] - b[2][dim];
        let d_v = a[1][dim] - b[1][dim];
        let d_p = a[0][dim] - b[0][dim];

        if d_a == 0 {
            // linear
            if d_v == 0 {
                // constant
                if d_p == 0 {
                    Self::Any
                } else {
                    Self::None
                }
            } else if d_p % d_v == 0 && d_p * d_v <= 0 {
                // 0 = d_p + k * d_v
                // k = -d_p / d_v
                Self::One((-d_p / d_v) as _)
            } else {
                Self::None
            }
        } else {
            // quadratic
            // -(2 * d_v + d_a) / (2 * d_a)
            //    +/- sqrt((2 * d_v + d_a)^2 - 8 * d_a * d_p) / (2 * d_a)
            let d = (2 * d_v + d_a).pow(2) - 8 * d_a * d_p;
            match d.cmp(&0) {
                std::cmp::Ordering::Less => Self::None,
                std::cmp::Ordering::Equal => {
                    let num = -(2 * d_v + d_a);
                    let den = 2 * d_a;
                    if num % den == 0 && num * den >= 0 {
                        // double solution
                        Self::One((num / den) as _)
                    } else {
                        // no positive integer solution
                        Self::None
                    }
                }
                std::cmp::Ordering::Greater => {
                    let r = f64::sqrt(d as _) as isize;
                    if r * r != d {
                        // no rational solution
                        Self::None
                    } else {
                        let num_1 = (-(2 * d_v + d_a) - r) * d_a.signum();
                        let num_2 = (-(2 * d_v + d_a) + r) * d_a.signum();
                        let den = 2 * d_a.abs();
                        let (num_1, num_2) = (num_1.min(num_2), num_1.max(num_2));
                        let (rem_1, rem_2) = (num_1 % den, num_2 % den);
                        if rem_1 == 0 && num_1 >= 0 && rem_2 == 0 && num_2 >= 0 {
                            // two positive integer solutions
                            Self::Two((num_1 / den) as _, (num_2 / den) as _)
                        } else if rem_1 == 0 && num_1 >= 0 {
                            // one positive integer solution
                            Self::One((num_1 / den) as _)
                        } else if rem_2 == 0 && num_2 >= 0 {
                            // one positive integer solution
                            Self::One((num_2 / den) as _)
                        } else {
                            // no positive integer solutions
                            Self::None
                        }
                    }
                }
            }
        }
    }

    pub fn and<F>(&self, f: F) -> Self
    where
        F: Fn() -> Self,
    {
        match self {
            Self::Any => f(),
            Self::One(a) => match f() {
                Self::Any => Self::One(*a),
                Self::One(k) if k == *a => Self::One(*a),
                Self::Two(k, l) if k == *a || l == *a => Self::One(*a),
                _ => Self::None,
            },
            Self::Two(a, b) => match f() {
                Self::Any => Self::Two(*a, *b),
                Self::One(k) if k == *a || k == *b => Self::One(k),
                Self::Two(k, l) if k == *a && l == *b => Self::Two(k, l),
                Self::Two(k, l) if k == *a || l == *a => Self::One(*a),
                Self::Two(k, l) if k == *b || l == *b => Self::One(*b),
                _ => Self::None,
            },
            _ => Self::None,
        }
    }

    pub fn get(&self) -> Option<usize> {
        match self {
            Self::None => None,
            Self::One(a) => Some(*a),
            Self::Two(a, _) => Some(*a),
            Self::Any => Some(0),
        }
    }
}

#[cfg(not(feature = "sim"))]
pub fn star_2(data: &PuzzleData) -> SolType2 {
    let mut alive = vec![true; data.list.len()];
    let mut list = Vec::new();
    let mut count = 0;
    for k_1 in 0..data.list.len() {
        if !alive[k_1] {
            continue;
        }

        let mut min_step = usize::MAX;
        list.truncate(0);
        for (k_2, _) in alive
            .iter()
            .enumerate()
            .skip(k_1 + 1)
            .filter(|(_, &alive)| alive)
        {
            if let Some(step) = Step::for_particles(&data.list[k_1], &data.list[k_2]).get() {
                match step.cmp(&min_step) {
                    std::cmp::Ordering::Equal => {
                        list.push(k_2);
                    }
                    std::cmp::Ordering::Less => {
                        list.truncate(0);
                        list.push(k_2);
                        min_step = step;
                    }
                    _ => {}
                }
            }
        }

        if list.is_empty() {
            count += 1;
        } else {
            alive[k_1] = false;
            for &k_2 in &list {
                alive[k_2] = false;
            }
        }
    }

    count
}

Star 2 Simulation based

I also have a simulation based variant now…​

 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
#[cfg(feature = "sim")]
pub fn collides(a: &[[isize; 3]; 3], b: &[[isize; 3]; 3]) -> (bool, bool) {
    let now = (0..3).all(|c| a[0][c] == b[0][c]);
    let ever = now
        || !(0..3).any(|c| {
            ((a[0][c] >= b[0][c] && a[1][c] >= b[1][c] && a[2][c] >= b[2][c])
                || (a[0][c] <= b[0][c] && a[1][c] <= b[1][c] && a[2][c] <= b[2][c]))
                && (a[0][c] != b[0][c] || a[1][c] != b[1][c] && a[2][c] != b[2][c])
        });
    (now, ever)
}

#[cfg(feature = "sim")]
pub fn star_2(data: &PuzzleData) -> SolType1 {
    let mut list = data.list.clone();
    let mut alive = vec![true; list.len()];
    let mut can_collide = true;
    while can_collide {
        can_collide = false;

        for k in (0..list.len()).filter(|&k| alive[k]) {
            for c in 0..3 {
                list[k][1][c] += list[k][2][c];
                list[k][0][c] += list[k][1][c];
            }
        }

        for k_1 in 0..list.len() {
            if !alive[k_1] {
                continue;
            }

            for k_2 in k_1 + 1..list.len() {
                if !alive[k_2] {
                    continue;
                }

                let (now, ever) = collides(&list[k_1], &list[k_2]);
                if now {
                    alive[k_1] = false;
                    alive[k_2] = false;
                } else if ever {
                    can_collide = true;
                }
            }
        }
    }

    alive.iter().filter(|a| **a).count()
}

Tests

Just standard tests.

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT_1: &str = r#"p=<3,0,0>, v=<2,0,0>, a=<-1,0,0>
p=<4,0,0>, v=<0,0,0>, a=<-2,0,0>
"#;

    const CONTENT_2: &str = r#"p=<-6,0,0>, v=<3,0,0>, a=<0,0,0>
p=<-4,0,0>, v=<2,0,0>, a=<0,0,0>
p=<-2,0,0>, v=<1,0,0>, a=<0,0,0>
p=<3,0,0>, v=<-1,0,0>, a=<0,0,0>
"#;

    #[test]
    pub fn test_from() {
        let data = PuzzleData::from(CONTENT_1);
        assert_eq!(
            vec![
                [[3, 0, 0], [2, 0, 0], [-1, 0, 0]],
                [[4, 0, 0], [0, 0, 0], [-2, 0, 0]]
            ],
            data.list
        );
    }

    #[test]
    pub fn test_star_1() {
        let data = PuzzleData::from(CONTENT_1);
        assert_eq!(0, star_1(&data));
    }

    #[test]
    pub fn test_star_2() {
        let data = PuzzleData::from(CONTENT_2);
        assert_eq!(1, star_2(&data));
    }
}

Day 21: Fractal Art

Rust solution to AoC|2017|21.

An image grows block-wise

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pub mod input {
    use std::collections::HashMap;

    #[derive(Debug, PartialEq, Eq)]
    pub struct PuzzleData(pub HashMap<Vec<bool>, Vec<bool>>);

    fn pattern(v: &str) -> Vec<bool> {
        v.bytes()
            .filter(|&b| b != b'/')
            .map(|b| b == b'#')
            .collect()
    }

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            Self(
                s.lines()
                    .map(|l| l.split_once(" => ").unwrap())
                    .map(|(i, o)| (pattern(i), pattern(o)))
                    .collect(),
            )
        }
    }
}

Star 1

Implement a single update step as follows: For each block of the current grid

  • copy the pattern in a buffer

  • look for a rule applying rotations and flips

  • copy the output part of the rule in the updated grid

Apply five update steps.

 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
pub fn rotate(p: &mut [bool]) {
    if p.len() == 9 {
        // 0 1 2
        // 3 4 5
        // 6 7 8
        (p[0], p[1], p[2], p[5], p[8], p[7], p[6], p[3]) =
            (p[2], p[5], p[8], p[7], p[6], p[3], p[0], p[1]);
    } else {
        debug_assert_eq!(4, p.len());
        // 0 1
        // 2 3
        (p[0], p[1], p[3], p[2]) = (p[1], p[3], p[2], p[0]);
    }
}

pub fn flip(p: &mut [bool]) {
    if p.len() == 9 {
        // 0 1 2
        // 3 4 5
        // 6 7 8
        (p[0], p[3], p[6], p[2], p[5], p[8]) = (p[2], p[5], p[8], p[0], p[3], p[6]);
    } else {
        debug_assert_eq!(4, p.len());
        // 0 1
        // 2 3
        (p[0], p[2], p[1], p[3]) = (p[1], p[3], p[0], p[2]);
    }
}

pub fn update(
    grid: &[bool],
    size: usize,
    rules: &HashMap<Vec<bool>, Vec<bool>>,
) -> (Vec<bool>, usize) {
    let (size_upd, blk_size) = if size & 1 == 0 {
        (3 * size / 2, 2)
    } else {
        debug_assert_eq!(0, size % 3);
        (4 * size / 3, 3)
    };

    let mut grid_upd = vec![false; size_upd * size_upd];
    let mut p = vec![false; blk_size * blk_size];

    for y in (0..size).step_by(blk_size) {
        for x in (0..size).step_by(blk_size) {
            // get pattern block
            for row in 0..blk_size {
                p[row * blk_size..(row + 1) * blk_size]
                    .copy_from_slice(&grid[x + size * (y + row)..x + size * (y + row) + blk_size]);
            }

            // get update block
            let p_upd = (0..8)
                .filter_map(|k| {
                    match k {
                        0 => (),
                        4 => {
                            rotate(&mut p);
                            flip(&mut p)
                        }
                        _ => rotate(&mut p),
                    }
                    rules.get(&p)
                })
                .next()
                .unwrap();

            // push pattern to grid update
            let x_upd = (x / blk_size) * (blk_size + 1);
            let y_upd = (y / blk_size) * (blk_size + 1);
            for row in 0..blk_size + 1 {
                grid_upd[x_upd + size_upd * (y_upd + row)
                    ..x_upd + size_upd * (y_upd + row) + blk_size + 1]
                    .copy_from_slice(&p_upd[row * (blk_size + 1)..(row + 1) * (blk_size + 1)]);
            }
        }
    }

    (grid_upd, size_upd)
}

pub fn solve(PuzzleData(rules): &PuzzleData, iterations: usize) -> SolType {
    let mut grid = ".#...####".bytes().map(|b| b == b'#').collect::<Vec<_>>();
    let mut size = 3;
    for _ in 0..iterations {
        (grid, size) = update(&grid, size, rules);
    }
    grid.iter().filter(|&&v| v).count()
}

pub fn star_1(data: &PuzzleData) -> SolType {
    solve(data, 5)
}

Star 2

Just apply 18 update steps from part 1.

1
2
3
pub fn star_2(data: &PuzzleData) -> SolType {
    solve(data, 18)
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"../.# => ##./#../...
.#./..#/### => #..#/..../..../#..#
"#;

    #[test]
    pub fn test_from() {
        let data = PuzzleData::from(CONTENT);
        assert_eq!(
            PuzzleData(
                [
                    (
                        "...#".bytes().map(|b| b == b'#').collect(),
                        "##.#.....".bytes().map(|b| b == b'#').collect()
                    ),
                    (
                        ".#...####".bytes().map(|b| b == b'#').collect(),
                        "#..#........#..#".bytes().map(|b| b == b'#').collect()
                    )
                ]
                .into()
            ),
            data
        )
    }

    #[test]
    pub fn test_update() {
        let PuzzleData(rules) = CONTENT.into();
        let mut grid = vec![false, true, false, false, false, true, true, true, true];
        let mut size = 3;
        for _ in 0..2 {
            (grid, size) = update(&grid, size, &rules);
        }

        assert_eq!(
            "##.##.#..#........##.##.#..#........"
                .bytes()
                .map(|b| b == b'#')
                .collect::<Vec<_>>(),
            grid
        );
    }
}

Day 22: Sporifica Virus

Rust solution to AoC|2017|22.

Simulate virus infection bursts.

I created a solution using a dynamically growing list and an alternative solution based on maps (the latter can be used with cargo run --release --features map). As is often the case, the list based solution is significantly faster - by a factor of at least three in this case.

In both cases, the access to the grid is performed using Index and IndexMut trait implementations for the two variants of the grid. In the IndexMut implementation, the grid dynamically grows as needed.

Input

 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
pub mod input {
    #[cfg(not(feature = "map"))]
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct Grid {
        pub width: usize,
        pub grid: Vec<u8>,
        pub off: (isize, isize),
    }

    #[cfg(not(feature = "map"))]
    impl From<&str> for Grid {
        fn from(s: &str) -> Self {
            let width = s.find('\n').unwrap();
            let grid = s.bytes().filter(|&b| b != b'\n').collect::<Vec<_>>();
            let off = ((width / 2) as _, ((grid.len() / width) / 2) as _);
            Self { width, grid, off }
        }
    }

    #[cfg(feature = "map")]
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct Grid {
        pub grid: std::collections::HashMap<(isize, isize), u8>,
    }

    #[cfg(feature = "map")]
    impl From<&str> for Grid {
        fn from(s: &str) -> Self {
            let w = s.find('\n').unwrap() as isize;
            let h = s.len() as isize / (w + 1);
            Self {
                grid: s
                    .lines()
                    .enumerate()
                    .flat_map(|(y, l)| {
                        l.bytes()
                            .enumerate()
                            .map(move |(x, v)| ((x as isize - w / 2, y as isize - h / 2), v))
                    })
                    .filter(|(_, v)| *v == b'#')
                    .collect(),
            }
        }
    }
}

Star 1

The update logic is encoded in the UPD_1 closure.

The rest is in the solve function.

 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
#[cfg(not(feature = "map"))]
impl Index<(isize, isize)> for Grid {
    type Output = u8;

    fn index(&self, (x, y): (isize, isize)) -> &Self::Output {
        let (x, y) = (x + self.off.0, y + self.off.1);
        let (w, h) = (self.width as isize, (self.grid.len() / self.width) as isize);

        if x >= 0 && x < w && y >= 0 && y < h {
            &self.grid[(x + w * y) as usize]
        } else {
            &b'.'
        }
    }
}

#[cfg(not(feature = "map"))]
impl IndexMut<(isize, isize)> for Grid {
    fn index_mut(&mut self, (x, y): (isize, isize)) -> &mut Self::Output {
        let (x, y) = (x + self.off.0, y + self.off.1);
        let (w, h) = (self.width as isize, (self.grid.len() / self.width) as isize);

        if x >= 0 && x < w && y >= 0 && y < h {
            &mut self.grid[(x + w * y) as usize]
        } else {
            // dynamic growth is constrained to grow by width/height,
            // which is enough for the problem at hand

            // new width, height and offsets (increment by w)
            let w_upd = if x < 0 || x >= w { w << 1 } else { w };
            let h_upd = if y < 0 || y >= h { h << 1 } else { h };
            let off_upd = (if x < 0 { w } else { 0 }, if y < 0 { h } else { 0 });

            // allocate new grid
            let mut grid_upd = vec![b'.'; (w_upd * h_upd) as usize];

            // copy old grid by rows
            for y in 0..h {
                let t = (off_upd.0 + w_upd * (y + off_upd.1)) as usize;
                let s = (w * y) as usize;
                grid_upd[t..t + self.width].copy_from_slice(&self.grid[s..s + self.width]);
            }

            // update self
            self.off.0 += off_upd.0;
            self.off.1 += off_upd.1;
            self.width = w_upd as usize;
            self.grid = grid_upd;

            // return mutable reference to entry
            &mut self.grid[(x + off_upd.0 + w_upd * (y + off_upd.1)) as usize]
        }
    }
}

#[cfg(feature = "map")]
impl Index<(isize, isize)> for Grid {
    type Output = u8;

    fn index(&self, p: (isize, isize)) -> &Self::Output {
        self.grid.get(&p).unwrap_or(&b'.')
    }
}

#[cfg(feature = "map")]
impl IndexMut<(isize, isize)> for Grid {
    fn index_mut(&mut self, p: (isize, isize)) -> &mut Self::Output {
        self.grid.entry(p).or_insert(b'.')
    }
}

const D: [(isize, isize); 4] = [(1, 0), (0, -1), (-1, 0), (0, 1)];

fn solve<F: Fn(u8, u8) -> (u8, u8)>(mut grid: Grid, bursts: usize, upd: F) -> SolType {
    let mut dir = 1;
    let mut p = (0, 0);
    let mut count = 0;
    for _ in 0..bursts {
        (dir, grid[p]) = upd(dir, grid[p]);
        count += (grid[p] == b'#') as SolType;
        p = (p.0 + D[dir as usize].0, p.1 + D[dir as usize].1);
    }
    count
}

const UPD_1: fn(u8, u8) -> (u8, u8) = |dir, val| match val {
    b'.' => ((dir + 1) & 3, b'#'),
    b'#' => ((dir + 3) & 3, b'.'),
    _ => panic!(),
};

pub fn star_1(grid: &Grid) -> SolType {
    solve(grid.to_owned(), 10_000, UPD_1)
}

Star 2

The only thing that needs to be changed compared to part 1 is the update closure (now UPD_2) and the number of iterations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const UPD_2: fn(u8, u8) -> (u8, u8) = |dir, val| match val {
    b'.' => ((dir + 1) & 3, b'W'),
    b'W' => (dir, b'#'),
    b'#' => ((dir + 3) & 3, b'F'),
    b'F' => ((dir + 2) & 3, b'.'),
    _ => panic!(),
};

pub fn star_2(grid: &Grid) -> SolType {
    solve(grid.to_owned(), 10_000_000, UPD_2)
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"..#
#..
...
"#;

    #[test]
    #[cfg(not(feature = "map"))]
    pub fn test_from() {
        let data = Grid::from(CONTENT);
        assert_eq!(
            Grid {
                grid: Vec::from("..##.....".as_bytes()),
                width: 3,
                off: (1, 1)
            },
            data
        );
    }

    #[test]
    #[cfg(feature = "map")]
    pub fn test_from() {
        use std::collections::HashMap;

        let data = Grid::from(CONTENT);
        assert_eq!(
            Grid {
                grid: HashMap::from([((1, -1), b'#'), ((-1, 0), b'#')])
            },
            data
        );
    }

    #[test]
    pub fn test_solve_1() {
        assert_eq!(41, solve(CONTENT.into(), 70, UPD_1));
    }

    #[test]
    pub fn test_solve_2() {
        assert_eq!(26, solve(CONTENT.into(), 100, UPD_2));
    }
}

Day 23: Coprocessor Conflagration

Rust solution to AoC|2017|23.

Reverse engineer instructions

Input

 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
pub mod input {
    use crate::{Instr, Value};

    #[derive(Debug)]
    pub struct PuzzleData(pub Vec<Instr>);

    impl From<&str> for Value {
        fn from(s: &str) -> Self {
            let b = s.as_bytes()[0];
            if (b'a'..=b'h').contains(&b) {
                Self::Reg((b - b'a') as _)
            } else {
                Self::Val(s.parse().unwrap())
            }
        }
    }

    impl From<&str> for Instr {
        fn from(s: &str) -> Self {
            let mut words = s.split_ascii_whitespace();
            let instr = words.next().unwrap();
            let x = words.next().unwrap();
            let y = words.next().unwrap().into();
            match instr {
                "set" => Self::Set((x.as_bytes()[0] - b'a') as _, y),
                "sub" => Self::Sub((x.as_bytes()[0] - b'a') as _, y),
                "mul" => Self::Mul((x.as_bytes()[0] - b'a') as _, y),
                "jnz" => Self::Jnz(x.into(), y),
                _ => panic!("Unexpected instruction {}", s),
            }
        }
    }

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            Self(s.trim().lines().map(Instr::from).collect())
        }
    }
}

Star 1

Execute instructions until instruction pointer is beyond program length.

 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
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Instr {
    Set(usize, Value),
    Sub(usize, Value),
    Mul(usize, Value),
    Jnz(Value, Value),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Value {
    Reg(usize),
    Val(i64),
}

impl Instr {
    pub fn execute(&self, reg: &mut [i64; 8], pos: &mut usize) {
        match *self {
            Instr::Set(x, y) => (reg[x], *pos) = (y.get(reg), *pos + 1),
            Instr::Sub(x, y) => (reg[x], *pos) = (reg[x] - y.get(reg), *pos + 1),
            Instr::Mul(x, y) => (reg[x], *pos) = (reg[x] * y.get(reg), *pos + 1),
            Instr::Jnz(x, y) => {
                let v = x.get(reg);
                if v != 0 {
                    *pos = pos.wrapping_add_signed(y.get(reg) as _);
                } else {
                    *pos += 1;
                }
            }
        }
    }
}

impl Value {
    pub fn get(&self, reg: &[i64; 8]) -> i64 {
        match *self {
            Value::Reg(idx) => reg[idx],
            Value::Val(val) => val,
        }
    }
}

pub fn star_1(PuzzleData(program): &PuzzleData) -> SolType {
    let mut reg = [0; 8];
    let mut pos = 0;
    let mut cnt = 0;
    while pos < program.len() {
        if let Instr::Mul(_, _) = program[pos] {
            cnt += 1;
        }
        program[pos].execute(&mut reg, &mut pos);
    }
    cnt
}

Star 2

Reverse engineer program - it actually counts non-primes in some set of values:

In pseudo-code:

// instructions 0..=7
b = ... // range start
c = ... // range end (inclusive)
h = 0

// instructions 8..=31
loop { // outer, over b
    // instructions 8..=9
    f = 1
    d = 2

    // instructions 10..=23
    loop { // over d
        // instruction 10
        e = 2

        // instructions 11..=19
        loop { // over e
            // instructions 11..=15
            if d * e == b
                f = 0 // b is not prime

            // instruction 16
            e++

            instructions 17..=19
            if e == b
                break // loop over e
        }

        // instruction 20
        d++

        // instructions 21..=23
        if d == b
            break // loop over d
    }

    // instructions 24..=25
    if f == 0 // b is not prime
        h++

    // instructions 26..=29
    if c == b
        break // outer loop over b

    // operation 30
    b += ... // range increment

    // operation 31: jump to start of outer loop over b
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
pub fn star_2(PuzzleData(program): &PuzzleData) -> SolType {
    let mut reg = [1, 0, 0, 0, 0, 0, 0, 0];
    let mut pos = 0;
    while pos < 8 {
        program[pos].execute(&mut reg, &mut pos);
    }
    let Instr::Sub(_, Value::Val(step)) = program[30] else {
        panic!()
    };
    let step = -step as usize;

    (reg[1]..=reg[2])
        .step_by(step)
        .filter(|&v| {
            once(2)
                .chain((3..).step_by(2))
                .take_while(|&c| c * c <= v)
                .any(|c| v % c == 0)
        })
        .count()
}

Day 24: Electromagnetic Moat

Rust solution to AoC|2017|24.

Build bridges by playing Dominoes

Input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
pub mod input {
    #[derive(Debug)]
    pub struct PuzzleData(pub Vec<(u32, u32)>);

    impl From<&str> for PuzzleData {
        fn from(s: &str) -> Self {
            Self(
                s.lines()
                    .map(|l| l.split_once('/').unwrap())
                    .map(|(p_a, p_b)| (p_a.parse().unwrap(), p_b.parse().unwrap()))
                    .collect(),
            )
        }
    }
}

Star 1

Build the strongest possible bridge, i.e., maximizing the overall points.

The solution is a recursive DFT algorithm. The code does not actually build the bridge. It only stores the elements used so far in a bit field and the port to connect the next piece to.

 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
pub fn build_bridge<T: Ord + Copy, F>(
    pieces: &[(u32, u32)],
    used: u64,
    port: u32,
    init: T,
    acc: F,
) -> T
where
    F: Fn(T, (u32, u32)) -> T + Copy,
{
    pieces
        .iter()
        .enumerate()
        .filter(|(_, (p_a, p_b))| p_a == &port || p_b == &port)
        .filter(|(k, _)| (used >> *k) & 1 == 0)
        .map(|(k, &(p_a, p_b))| {
            acc(
                build_bridge(pieces, used | (1 << k), p_a + p_b - port, init, acc),
                (p_a, p_b),
            )
        })
        .max()
        .unwrap_or(init)
}

pub fn star_1(PuzzleData(components): &PuzzleData) -> SolType {
    assert!(components.len() <= u64::BITS as _);
    build_bridge(components, 0, 0, 0, |strength, (p_a, p_b)| {
        strength + p_a + p_b
    })
}

Star 2

Build the longest possible bridge. Brake ties by choosing the strongest one.

1
2
3
4
5
6
7
pub fn star_2(PuzzleData(pieces): &PuzzleData) -> SolType {
    assert!(pieces.len() <= u64::BITS as _);
    let (_, strength) = build_bridge(pieces, 0, 0, (0, 0), |(len, strength), (p_a, p_b)| {
        (len + 1, p_a + p_b + strength)
    });
    strength
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"0/2
2/2
2/3
3/4
3/5
0/1
10/1
9/10
"#;

    #[test]
    pub fn test_from() {
        let PuzzleData(components) = PuzzleData::from(CONTENT);
        let exp = vec![
            (0, 2),
            (2, 2),
            (2, 3),
            (3, 4),
            (3, 5),
            (0, 1),
            (10, 1),
            (9, 10),
        ];
        assert_eq!(exp, components);
    }

    #[test]
    pub fn test_star_1() {
        assert_eq!(31, star_1(&CONTENT.into()));
    }

    #[test]
    pub fn test_star_2() {
        assert_eq!(19, star_2(&CONTENT.into()));
    }
}

Day 25: The Halting Problem

Rust solution to AoC|2017|25.

Is it a turing machine?

Input

 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
pub mod input {
    #[derive(Debug, PartialEq, Eq)]
    pub struct State {
        pub id: usize,
        pub act: [(bool, isize, usize); 2],
    }

    impl From<&str> for State {
        fn from(s: &str) -> Self {
            const CHK_STATE: &str = "In state ";
            const CHK_VALUE: &str = "  If the current value is ";
            const WRITE_VAL: &str = "    - Write the value ";
            const MOVE_LEFT: &str = "    - Move one slot to the left.";
            const MOVE_RGHT: &str = "    - Move one slot to the right.";
            const UPD_STATE: &str = "    - Continue with state ";

            let mut lines = s.lines();

            let id = (lines.next().unwrap().as_bytes()[CHK_STATE.len()] - b'A') as usize;

            let mut act = [(false, 0, 0); 2];
            let mut cur = 0;
            for line in lines {
                if line.starts_with(CHK_VALUE) {
                    cur = line.as_bytes()[CHK_VALUE.len()] - b'0';
                } else if line.starts_with(WRITE_VAL) {
                    act[cur as usize].0 = line.as_bytes()[WRITE_VAL.len()] - b'0' > 0;
                } else if line == MOVE_LEFT {
                    act[cur as usize].1 = -1;
                } else if line == MOVE_RGHT {
                    act[cur as usize].1 = 1;
                } else if line.starts_with(UPD_STATE) {
                    act[cur as usize].2 = (line.as_bytes()[UPD_STATE.len()] - b'A') as usize;
                } else {
                    panic!("Unexpected line: {}", line);
                }
            }

            Self { id, act }
        }
    }

    #[derive(Debug, PartialEq, Eq)]
    pub struct Machine {
        pub start: usize,
        pub steps: u64,
        pub states: Vec<State>,
    }

    impl From<&str> for Machine {
        fn from(s: &str) -> Self {
            let mut states = s.split("\n\n");

            let header = states.next().unwrap();
            let start = (header.as_bytes()["Begin in state ".len()] - b'A') as usize;
            let steps = header.strip_suffix(" steps.").unwrap()
                ["Begin in state A.\nPerform a diagnostic checksum after ".len()..]
                .parse::<u64>()
                .unwrap();

            Self {
                start,
                steps,
                states: states.map(State::from).collect(),
            }
        }
    }
}

Star 1

The interesting part is to come up with a solution that does not need any sets or maps…​

 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
pub fn star_1(
    Machine {
        start,
        steps,
        states,
    }: &Machine,
) -> SolType {
    let mut tape = vec![false; 2048];

    let mut pos: isize = 0;
    let mut min = 0;
    let mut max = 1;

    let mut state = *start;
    let mut ones = 0;

    for _ in 0..*steps {
        let idx = pos.rem_euclid(tape.len() as _) as usize;
        ones -= tape[idx] as usize;
        let step;
        (tape[idx], step, state) = states[state].act[tape[idx] as usize];
        pos += step;
        ones += tape[idx] as usize;

        min = min.min(pos);
        max = max.max(pos + 1);
        if max - min == tape.len() as isize {
            let len = tape.len() << 1;
            let mut temp = vec![false; len];
            temp[0..max as _].copy_from_slice(&tape[0..max as _]);
            temp[(len as isize + min) as _..]
                .copy_from_slice(&tape[(tape.len() as isize + min) as _..]);
            tape = temp;
        }
    }

    ones
}

Tests

 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
#[cfg(test)]
mod tests {
    use super::*;

    const CONTENT: &str = r#"Begin in state A.
Perform a diagnostic checksum after 6 steps.

In state A:
  If the current value is 0:
    - Write the value 1.
    - Move one slot to the right.
    - Continue with state B.
  If the current value is 1:
    - Write the value 0.
    - Move one slot to the left.
    - Continue with state B.

In state B:
  If the current value is 0:
    - Write the value 1.
    - Move one slot to the left.
    - Continue with state A.
  If the current value is 1:
    - Write the value 1.
    - Move one slot to the right.
    - Continue with state A.
"#;

    #[test]
    pub fn test_from() {
        let data = Machine::from(CONTENT);

        assert_eq!(
            Machine {
                start: 0,
                steps: 6,
                states: vec![
                    State {
                        id: 0,
                        act: [(true, 1, 1), (false, -1, 1)]
                    },
                    State {
                        id: 1,
                        act: [(true, -1, 0), (true, 1, 0)]
                    }
                ]
            },
            data
        );
    }

    #[test]
    pub fn test_star_1() {
        assert_eq!(3, star_1(&CONTENT.into()));
    }
}