From 215e46d392e8adbffe62b8f7ee53a9ee9a20b48f Mon Sep 17 00:00:00 2001 From: maneatingape <44142177+maneatingape@users.noreply.github.com> Date: Sun, 2 Jun 2024 00:24:51 +0200 Subject: [PATCH] Year 2017 Day 16 --- README.md | 1 + benches/benchmark.rs | 1 + src/lib.rs | 1 + src/main.rs | 1 + src/year2017/day16.rs | 113 +++++++++++++++++++++++++++++++++++ tests/test.rs | 1 + tests/year2017/day16_test.rs | 9 +++ 7 files changed, 127 insertions(+) create mode 100644 src/year2017/day16.rs create mode 100644 tests/year2017/day16_test.rs diff --git a/README.md b/README.md index 168c839d..bde1a741 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro | 13 | [Packet Scanners](https://adventofcode.com/2017/day/13) | [Source](src/year2017/day13.rs) | 2 | | 14 | [Disk Defragmentation](https://adventofcode.com/2017/day/14) | [Source](src/year2017/day14.rs) | 422 | | 15 | [Dueling Generators](https://adventofcode.com/2017/day/15) | [Source](src/year2017/day15.rs) | 425000 | +| 16 | [Permutation Promenade](https://adventofcode.com/2017/day/16) | [Source](src/year2017/day16.rs) | 66 | ## 2016 diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 8b6f235d..fb1f2ca3 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -116,6 +116,7 @@ mod year2017 { benchmark!(year2017, day13); benchmark!(year2017, day14); benchmark!(year2017, day15); + benchmark!(year2017, day16); } mod year2019 { diff --git a/src/lib.rs b/src/lib.rs index f87888bb..720346fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,7 @@ pub mod year2017 { pub mod day13; pub mod day14; pub mod day15; + pub mod day16; } /// # Rescue Santa from deep space with a solar system adventure. diff --git a/src/main.rs b/src/main.rs index d83d4b44..282d4905 100644 --- a/src/main.rs +++ b/src/main.rs @@ -163,6 +163,7 @@ fn year2017() -> Vec { solution!(year2017, day13), solution!(year2017, day14), solution!(year2017, day15), + solution!(year2017, day16), ] } diff --git a/src/year2017/day16.rs b/src/year2017/day16.rs new file mode 100644 index 00000000..cabf0667 --- /dev/null +++ b/src/year2017/day16.rs @@ -0,0 +1,113 @@ +//! # Permutation Promenade +//! +//! The key insight is that a complete dance can be represented by just two transformations. +//! The spin and exchange moves compose into a single transformation and the partner swaps compose +//! into a second independent transformation. +//! +//! Each transformation can then be applied to itself to double the effect. For example a single +//! complete dance turns into two dances, then doubles to four dances and so on. +//! +//! This allows us to compute part two with a similar approach to +//! [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). +use crate::util::parse::*; +use std::array::from_fn; + +#[derive(Copy, Clone)] +pub struct Dance { + /// The letter in each position from left to right + /// with `a` represented by 0, `b` by 1 and so on. + position: [usize; 16], + /// A map of initial letter to final letter taking into account all partner swaps. + /// `a` is at index 0, `b` at index 1. For convenience letters are represented by 0..15. + exchange: [usize; 16], +} + +impl Dance { + /// Creates a new Dance that represents the identity transformation. + fn new() -> Dance { + Dance { position: from_fn(|i| i), exchange: from_fn(|i| i) } + } + + /// Converts a Dance into a string representation. + fn apply(self) -> String { + self.position.iter().map(|&i| to_char(self.exchange[i])).collect() + } + + /// Combines two Dances into a new Dance. + fn compose(self, other: Dance) -> Dance { + let position = self.position.map(|i| other.position[i]); + let exchange = self.exchange.map(|i| other.exchange[i]); + Dance { position, exchange } + } +} + +/// Reduces all 10,000 individual dance moves into just two independent transformations. +pub fn parse(input: &str) -> Dance { + // Tokenize the input into two parallel iterators + let mut letters = input.bytes().filter(u8::is_ascii_lowercase); + let mut numbers = input.iter_unsigned::(); + + let mut offset = 0; + let mut lookup: [usize; 16] = from_fn(|i| i); + let Dance { mut position, mut exchange } = Dance::new(); + + while let Some(op) = letters.next() { + match op { + // Increasing the offset has the same effect as rotating elements to the right. + b's' => { + offset += 16 - numbers.next().unwrap(); + } + // Swap two elements taking into account the offset when calculating indices. + b'x' => { + let first = numbers.next().unwrap(); + let second = numbers.next().unwrap(); + position.swap((first + offset) % 16, (second + offset) % 16); + } + // First lookup the index of each letter, then swap the mapping. + b'p' => { + let first = from_byte(letters.next().unwrap()); + let second = from_byte(letters.next().unwrap()); + lookup.swap(first, second); + exchange.swap(lookup[first], lookup[second]); + } + _ => unreachable!(), + } + } + + // Rotate the array once to apply all spins. + position.rotate_left(offset % 16); + + Dance { position, exchange } +} + +/// Apply the transformation once. +pub fn part1(input: &Dance) -> String { + input.apply() +} + +/// If a bit is set in the binary representation of 1 billion apply the current transformation, +/// then apply the transformation to itself to double the number of complete dances. +pub fn part2(input: &Dance) -> String { + let mut e = 1_000_000_000; + let mut dance = *input; + let mut result = Dance::new(); + + while e > 0 { + if e & 1 == 1 { + result = result.compose(dance); + } + + e >>= 1; + dance = dance.compose(dance); + } + + result.apply() +} + +fn from_byte(b: u8) -> usize { + (b - b'a') as usize +} + +fn to_char(i: usize) -> char { + ((i as u8) + b'a') as char +} diff --git a/tests/test.rs b/tests/test.rs index 6edbd847..b9449eae 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -100,6 +100,7 @@ mod year2017 { mod day13_test; mod day14_test; mod day15_test; + mod day16_test; } mod year2019 { diff --git a/tests/year2017/day16_test.rs b/tests/year2017/day16_test.rs new file mode 100644 index 00000000..3ee4e0aa --- /dev/null +++ b/tests/year2017/day16_test.rs @@ -0,0 +1,9 @@ +#[test] +fn part1_test() { + // No example data +} + +#[test] +fn part2_test() { + // No example data +}