aoc/year2015/
day18.rs

1//! # Like a GIF For Your Yard
2//!
3//! To solve this efficiently we use a [SWAR](https://en.wikipedia.org/wiki/SWAR) approach,
4//! packing 50 lights into a `u64` taking 1 bit each, drawing inspiration from a solution from
5//! [u/terje_wiig_mathisen/](https://github.com/TerjeWiigMathisen/aoc-2015-day18).
6//!
7//! But rather than taking the bits consecutively,
8//! split the odd columns into one int and the even into another - that way, when it is time to
9//! check neighbors, all left and right neighbors of the odd lanes are already available with just
10//! one shift of all the even lanes, and vice versa.  We calculate the next generation using no
11//! conditional statements with the following steps.
12//!
13//! 1. Pack the input bytes into register values that can be represented as binary digits, split
14//!    into odd and even lanes.  An extra empty row at the bottom reduces later special-casing.
15//!
16//! ```none
17//!                    .even .odd
18//!             column: 024   135
19//!     #...#.          101   000
20//!     .#.##. =>       001   110
21//!     ###.#.          111   100
22//! ```
23//!
24//! 2. Perform half-adder and full-adder computation of each bit with its vertical neighbors, using
25//!    only bitwise logic.  Just two bit-wise additions provides data for three 2-bit column sums,
26//!    since the left and right neighbors are one bit apart in the opposite parity integer and
27//!    already added in parallel.  Visually, for cell e, we are computing a+b+c, d+f, and g+h+i
28//!    of its neighbors.
29//!
30//! ```none
31//!                   .odd   .even
32//!     ..adg..       ..d..  ..ag..
33//!     ..beh..  =>   ..e..  ..bh..
34//!     ..cfi..       ..f..  ..ci..
35//!                 l,m=d+f  j,k=a+b+c  n,o=g+h+i
36//! ```
37//!
38//! 3. Taking the 3 two-bit column sums learned in the last step, perform two more full-adders to
39//!    form four new bits p, q, r, s, which we could add into a usual four-bit number, but which
40//!    are good enough for our needs as-is.  Bit s is set if there were an odd number of neighbors;
41//!    bit p must be clear or we already know there are more than 3 neighbors; and exactly one of
42//!    bits q and r must be set for the final 4-bit sum to have the second bit set.
43//!
44//! ```none
45//!     a d g
46//!     b - h
47//!   + c f i
48//!   -------
49//!       j k
50//!       l m
51//!   +   n o
52//!   -------
53//!     p q -
54//!       r s
55//! ```
56//!
57//! 4. Apply the rules using only bitwise logic.
58//!
59//! Consider the binary representation of a 4 bit hex digit.
60//! * A cell stays on if it has 2 or 3 neighbors, binary `0010` or binary `0011`.
61//! * A cell turns on if it has exactly 3 neighbors, binary `0011`.
62//!
63//! If we `OR` the neighbor count with the current cell, either `0000` or `0001` then the
64//! binary representation of a lit cell will always be `0011`.
65//!
66//! Using the bits as labelled above, the next cell is `(e|s) & (q^r) & !p`, masked back
67//! to the 50 bits per integer.
68
69const MASK: u64 = (1 << 50) - 1;
70const LEFT_CORNER: u64 = 1 << 49;
71const RIGHT_CORNER: u64 = 1 << 0;
72
73/// 100 lights of a single row, split by column parity.
74#[derive(Clone, Copy, Default)]
75pub struct Row {
76    even: u64,
77    odd: u64,
78}
79
80/// Pack the lights into 1 bit each in [big-endian order](https://en.wikipedia.org/wiki/Endianness).
81pub fn parse(input: &str) -> Vec<Row> {
82    let mut grid = Vec::with_capacity(101);
83    let pack = |acc, b| (acc << 1) | u64::from(b & 1);
84
85    for line in input.lines() {
86        let even = line.bytes().step_by(2).fold(0, pack);
87        let odd = line.bytes().skip(1).step_by(2).fold(0, pack);
88        grid.push(Row { even, odd });
89    }
90
91    grid.push(Row::default());
92    grid
93}
94
95pub fn part1(input: &[Row]) -> u32 {
96    game_of_life(input, false)
97}
98
99pub fn part2(input: &[Row]) -> u32 {
100    game_of_life(input, true)
101}
102
103fn game_of_life(input: &[Row], part_two: bool) -> u32 {
104    let mut grid = input.to_vec();
105
106    for _ in 0..100 {
107        let mut above;
108        let mut row = Row::default();
109        let mut below = grid[0];
110
111        for y in 0..100 {
112            // Advance row
113            (above, row, below) = (row, below, grid[y + 1]);
114
115            // Even columns.
116            let (j, k) = full_adder(above.odd, row.odd, below.odd);
117            let (l, m) = half_adder(above.even, below.even);
118            let (n, o) = (j >> 1, k >> 1);
119
120            let (p, q) = full_adder(j, l, n);
121            let (r, s) = full_adder(k, m, o);
122            grid[y].even = (grid[y].even | s) & (q ^ r) & !p & MASK;
123
124            // Odd columns.
125            let (j, k) = full_adder(above.even, row.even, below.even);
126            let (l, m) = half_adder(above.odd, below.odd);
127            let (n, o) = (j << 1, k << 1);
128
129            let (p, q) = full_adder(j, l, n);
130            let (r, s) = full_adder(k, m, o);
131            grid[y].odd = (grid[y].odd | s) & (q ^ r) & !p & MASK;
132        }
133
134        // Set corner lights to always on.
135        if part_two {
136            grid[0].even |= LEFT_CORNER;
137            grid[0].odd |= RIGHT_CORNER;
138            grid[99].even |= LEFT_CORNER;
139            grid[99].odd |= RIGHT_CORNER;
140        }
141    }
142
143    grid.iter().map(|row| row.even.count_ones() + row.odd.count_ones()).sum()
144}
145
146#[inline]
147fn half_adder(a: u64, b: u64) -> (u64, u64) {
148    (a & b, a ^ b)
149}
150
151#[inline]
152fn full_adder(a: u64, b: u64, c: u64) -> (u64, u64) {
153    (a & b | c & (a ^ b), a ^ b ^ c)
154}