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 16 lights into a `u64` taking 4 bits each. We calculate the next generation using no
5//! conditional statements with the following steps.
6//!
7//! 1. Pack the input bytes into register values that can be represented as hex digits.
8//!
9//! ```none
10//! #...# 10001
11//! .#.#. => 01010
12//! ###.# 11101
13//! ```
14//!
15//! 2. Add left and right neighbors to each column horizontally, shifting in zeroes at the edge.
16//!
17//! ```none
18//! 11011
19//! 11211
20//! 23221
21//! ```
22//!
23//! 3. Add 3 rows together to give the total sum including the light itself:
24//!
25//! ```none
26//! 45443
27//! ```
28//!
29//! 4. Subtract the middle row to get neighbors only.
30//!
31//! ```none
32//! 44433
33//! ```
34//!
35//! 5. Apply the rules using only bitwise logic.
36//!
37//! Consider the binary representation of a 4 bit hex digit.
38//! * A cell stays on if it has 2 or 3 neigbours, binary `0010` or binary `0011`.
39//! * A cell turns on if it has exactly 3 neighbors, binary `0011`.
40//!
41//! If we `OR` the neighbor count with the current cell, either `0000` or `0001` then the
42//! binary representation of a lit cell will always be `0011`.
43//!
44//! Labelling the bits `abcd` then the next cell is `!a & !b & c & d`.
45type Lights = [[u64; 7]; 100];
46
47/// Pack the lights into 4 bits each in [big-endian order](https://en.wikipedia.org/wiki/Endianness).
48pub fn parse(input: &str) -> Lights {
49 let mut grid = default();
50
51 for (y, row) in input.lines().enumerate() {
52 for (x, col) in row.bytes().enumerate() {
53 let index = x / 16;
54 let offset = 4 * (15 - (x % 16));
55 let bit = (col & 1) as u64;
56 grid[y][index] |= bit << offset;
57 }
58 }
59
60 grid
61}
62
63pub fn part1(input: &Lights) -> u32 {
64 game_of_life(input, false)
65}
66
67pub fn part2(input: &Lights) -> u32 {
68 game_of_life(input, true)
69}
70
71fn game_of_life(input: &Lights, part_two: bool) -> u32 {
72 let mut grid = *input;
73 let mut temp = default();
74 let mut next = default();
75
76 for _ in 0..100 {
77 for y in 0..100 {
78 for x in 0..7 {
79 // Add left and right neighbors from this block.
80 let mut sum = grid[y][x] + (grid[y][x] >> 4) + (grid[y][x] << 4);
81
82 // Add immediate right or left neighbor from previous or next block.
83 if x > 0 {
84 sum += grid[y][x - 1] << 60;
85 }
86 if x < 6 {
87 sum += grid[y][x + 1] >> 60;
88 }
89
90 temp[y][x] = sum;
91 }
92 }
93
94 for y in 0..100 {
95 for x in 0..7 {
96 // Get neighbor count by summing the rows above and below the light
97 // then subtracting the light itself.
98 let mut sum = temp[y][x] - grid[y][x];
99
100 if y > 0 {
101 sum += temp[y - 1][x];
102 }
103 if y < 99 {
104 sum += temp[y + 1][x];
105 }
106
107 // Calculate the next generation with no conditional statements.
108 let a = sum >> 3;
109 let b = sum >> 2;
110 let c = sum >> 1;
111 let d = sum | grid[y][x];
112
113 next[y][x] = (!a & !b & c & d) & 0x1111111111111111;
114 }
115
116 // 100 = 16 * 6 + 4 = so only use the first 4 places of the last element.
117 next[y][6] &= 0x1111000000000000;
118 }
119
120 // Set corner lights to always on.
121 if part_two {
122 next[0][0] |= 1 << 60;
123 next[0][6] |= 1 << 48;
124 next[99][0] |= 1 << 60;
125 next[99][6] |= 1 << 48;
126 }
127
128 (grid, next) = (next, grid);
129 }
130
131 grid.iter().map(|row| row.iter().map(|n| n.count_ones()).sum::<u32>()).sum()
132}
133
134fn default() -> Lights {
135 [[0; 7]; 100]
136}