Skip to content

Commit

Permalink
Year 2018 Day 18
Browse files Browse the repository at this point in the history
  • Loading branch information
maneatingape committed Aug 25, 2024
1 parent 1472208 commit 786108b
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
| 15 | [Beverage Bandits](https://adventofcode.com/2018/day/15) | [Source](src/year2018/day15.rs) | 584 |
| 16 | [Chronal Classification](https://adventofcode.com/2018/day/16) | [Source](src/year2018/day16.rs) | 36 |
| 17 | [Reservoir Research ](https://adventofcode.com/2018/day/17) | [Source](src/year2018/day17.rs) | 145 |
| 18 | [Settlers of The North Pole](https://adventofcode.com/2018/day/18) | [Source](src/year2018/day18.rs) | 387 |

## 2017

Expand Down
1 change: 1 addition & 0 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ mod year2018 {
benchmark!(year2018, day15);
benchmark!(year2018, day16);
benchmark!(year2018, day17);
benchmark!(year2018, day18);
}

mod year2019 {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pub mod year2018 {
pub mod day15;
pub mod day16;
pub mod day17;
pub mod day18;
}

/// # Rescue Santa from deep space with a solar system voyage.
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ fn year2018() -> Vec<Solution> {
solution!(year2018, day15),
solution!(year2018, day16),
solution!(year2018, day17),
solution!(year2018, day18),
]
}

Expand Down
176 changes: 176 additions & 0 deletions src/year2018/day18.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
//! # Settlers of The North Pole
//!
//! This problem is a cellular automaton similar to the well known
//! [Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life). To solve part two
//! we look for a [cycle](https://en.wikipedia.org/wiki/Cycle_detection) then
//! extrapolate forward a billion generations.
//!
//! To efficiently compute the next generation a [SWAR](https://en.wikipedia.org/wiki/SWAR)
//! approach is used. The count of trees and lumberyards is packed into a `u64` so that we can
//! process 8 acres at a time. Lumberyards are stored in the high nibble of each byte
//! and trees in the low nibble. For example:
//!
//! ```none
//! .#.#...|
//! .....#|# => 11 11 21 11 21 02 21 02 => 0x1111211121022102
//! .|..|...
//! ```
//!
//! The total number of adjacent trees or lumberyards is then calculated in two passes.
//! First the horizontal sum of each row is computed by bit shifting left and right by 8.
//! Then the vertical sum of 3 horizontal sums gives the total.
//!
//! Bitwise logic then computes the next generation in batches of 8 acres at a time.
use crate::util::hash::*;
use std::hash::{Hash, Hasher};

/// Bitwise logic galore.
const OPEN: u64 = 0x00;
const TREE: u64 = 0x01;
const LUMBERYARD: u64 = 0x10;
const EDGE: u64 = 0xffff000000000000;
const LOWER: u64 = 0x0f0f0f0f0f0f0f0f;
const UPPER: u64 = 0xf0f0f0f0f0f0f0f0;
const THIRTEENS: u64 = 0x0d0d0d0d0d0d0d0d;
const FIFTEENS: u64 = 0x0f0f0f0f0f0f0f0f;

/// New type wrapper so that we can use a custom hash function.
#[derive(PartialEq, Eq)]
pub struct Key {
area: [u64; 350],
}

/// Hash only two cells as a reasonable tradeoff between speed and collision resistance.
impl Hash for Key {
fn hash<H: Hasher>(&self, state: &mut H) {
self.area[100].hash(state);
self.area[200].hash(state);
}
}

/// Pack the input into an array of `u64`.
/// The input is 50 acres wide, so requires `ceil(50 / 8) = 7` elements for each row.
pub fn parse(input: &str) -> Key {
let mut area = [0; 350];

for (y, line) in input.lines().map(str::as_bytes).enumerate() {
for (x, byte) in line.iter().enumerate() {
let acre = match byte {
b'|' => TREE,
b'#' => LUMBERYARD,
_ => OPEN,
};
let index = (y * 7) + (x / 8);
let offset = 56 - 8 * (x % 8);
area[index] |= acre << offset;
}
}

Key { area }
}

/// Compute 10 generations.
pub fn part1(input: &Key) -> u32 {
let mut area = input.area;
let mut rows = [0; 364];

for _ in 0..10 {
step(&mut area, &mut rows);
}

resource_value(&area)
}

/// Compute generations until a cycle is detected.
pub fn part2(input: &Key) -> u32 {
let mut area = input.area;
let mut rows = [0; 364];
let mut seen = FastMap::with_capacity(1_000);

for minute in 1.. {
step(&mut area, &mut rows);

if let Some(previous) = seen.insert(Key { area }, minute) {
// Find the index of the state after 1 billion repetitions.
let offset = 1_000_000_000 - previous;
let cycle_width = minute - previous;
let remainder = offset % cycle_width;
let target = previous + remainder;

let (result, _) = seen.iter().find(|(_, &i)| i == target).unwrap();
return resource_value(&result.area);
}
}

unreachable!()
}

fn step(area: &mut [u64], rows: &mut [u64]) {
// Compute the horizontal sum of each column with its immediate neighbors.
for y in 0..50 {
// Shadow slices at correct starting offset for convenience. We pad `rows` on the top and
// bottom then shift index by 7 to avoid having to check for edge conditions.
let area = &area[7 * y..];
let rows = &mut rows[7 * (y + 1)..];

rows[0] = horizontal_sum(0, area[0], area[1]);
rows[1] = horizontal_sum(area[0], area[1], area[2]);
rows[2] = horizontal_sum(area[1], area[2], area[3]);
rows[3] = horizontal_sum(area[2], area[3], area[4]);
rows[4] = horizontal_sum(area[3], area[4], area[5]);
rows[5] = horizontal_sum(area[4], area[5], area[6]);
rows[6] = horizontal_sum(area[5], area[6], 0);

// The grid is 50 wide so the last 6 bytes in each row are unused and must be set to zero.
rows[6] &= EDGE;
}

for i in 0..350 {
// Sum of all adjacent trees and lumberyards, not including center acre.
let acre = area[i];
let sum = rows[i] + rows[i + 7] + rows[i + 14] - acre;

// Add 13 so that any values 3 and higher overflow into high nibble.
let mut to_tree = (sum & LOWER) + THIRTEENS;
// Clear low nibble as this is irrelevant.
to_tree &= UPPER;
// To become a tree, we must be open space.
to_tree &= !(acre | (acre << 4));
// Shift result back to low nibble.
to_tree >>= 4;

// Check for any values 3 or higher.
let mut to_lumberyard = ((sum >> 4) & LOWER) + THIRTEENS;
// Clear low nibble.
to_lumberyard &= UPPER;
// To become a lumberyard, we must already be a tree.
to_lumberyard &= acre << 4;
// Spread result to both high and low nibble. We will later XOR this to flip correct bits.
to_lumberyard |= to_lumberyard >> 4;

// We must be a lumberyard.
let mut to_open = acre & UPPER;
// Check for at least one adjacent tree.
to_open &= (sum & LOWER) + FIFTEENS;
// Check for at least one adjacent lumberyard.
to_open &= ((sum >> 4) & LOWER) + FIFTEENS;
// Flip bit as we will later XOR.
to_open ^= acre & UPPER;

// Flip relevant bits to transition to next state.
area[i] = acre ^ (to_tree | to_lumberyard | to_open);
}
}

/// Convenience method that also takes correct byte from left and right neighbors.
#[inline]
fn horizontal_sum(left: u64, middle: u64, right: u64) -> u64 {
(left << 56) + (middle >> 8) + middle + (middle << 8) + (right >> 56)
}

/// Each tree or lumberyard is represented by a single bit.
fn resource_value(area: &[u64]) -> u32 {
let trees: u32 = area.iter().map(|n| (n & LOWER).count_ones()).sum();
let lumberyards: u32 = area.iter().map(|n| (n & UPPER).count_ones()).sum();
trees * lumberyards
}
1 change: 1 addition & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ mod year2018 {
mod day15_test;
mod day16_test;
mod day17_test;
mod day18_test;
}

mod year2019 {
Expand Down
9 changes: 9 additions & 0 deletions tests/year2018/day18_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#[test]
fn part1_test() {
// No example data
}

#[test]
fn part2_test() {
// No example data
}

0 comments on commit 786108b

Please sign in to comment.