Skip to content

Commit 215e46d

Browse files
committed
Year 2017 Day 16
1 parent 6755684 commit 215e46d

File tree

7 files changed

+127
-0
lines changed

7 files changed

+127
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
251251
| 13 | [Packet Scanners](https://adventofcode.com/2017/day/13) | [Source](src/year2017/day13.rs) | 2 |
252252
| 14 | [Disk Defragmentation](https://adventofcode.com/2017/day/14) | [Source](src/year2017/day14.rs) | 422 |
253253
| 15 | [Dueling Generators](https://adventofcode.com/2017/day/15) | [Source](src/year2017/day15.rs) | 425000 |
254+
| 16 | [Permutation Promenade](https://adventofcode.com/2017/day/16) | [Source](src/year2017/day16.rs) | 66 |
254255

255256
## 2016
256257

benches/benchmark.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ mod year2017 {
116116
benchmark!(year2017, day13);
117117
benchmark!(year2017, day14);
118118
benchmark!(year2017, day15);
119+
benchmark!(year2017, day16);
119120
}
120121

121122
mod year2019 {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub mod year2017 {
9898
pub mod day13;
9999
pub mod day14;
100100
pub mod day15;
101+
pub mod day16;
101102
}
102103

103104
/// # Rescue Santa from deep space with a solar system adventure.

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ fn year2017() -> Vec<Solution> {
163163
solution!(year2017, day13),
164164
solution!(year2017, day14),
165165
solution!(year2017, day15),
166+
solution!(year2017, day16),
166167
]
167168
}
168169

src/year2017/day16.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//! # Permutation Promenade
2+
//!
3+
//! The key insight is that a complete dance can be represented by just two transformations.
4+
//! The spin and exchange moves compose into a single transformation and the partner swaps compose
5+
//! into a second independent transformation.
6+
//!
7+
//! Each transformation can then be applied to itself to double the effect. For example a single
8+
//! complete dance turns into two dances, then doubles to four dances and so on.
9+
//!
10+
//! This allows us to compute part two with a similar approach to
11+
//! [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring).
12+
use crate::util::parse::*;
13+
use std::array::from_fn;
14+
15+
#[derive(Copy, Clone)]
16+
pub struct Dance {
17+
/// The letter in each position from left to right
18+
/// with `a` represented by 0, `b` by 1 and so on.
19+
position: [usize; 16],
20+
/// A map of initial letter to final letter taking into account all partner swaps.
21+
/// `a` is at index 0, `b` at index 1. For convenience letters are represented by 0..15.
22+
exchange: [usize; 16],
23+
}
24+
25+
impl Dance {
26+
/// Creates a new Dance that represents the identity transformation.
27+
fn new() -> Dance {
28+
Dance { position: from_fn(|i| i), exchange: from_fn(|i| i) }
29+
}
30+
31+
/// Converts a Dance into a string representation.
32+
fn apply(self) -> String {
33+
self.position.iter().map(|&i| to_char(self.exchange[i])).collect()
34+
}
35+
36+
/// Combines two Dances into a new Dance.
37+
fn compose(self, other: Dance) -> Dance {
38+
let position = self.position.map(|i| other.position[i]);
39+
let exchange = self.exchange.map(|i| other.exchange[i]);
40+
Dance { position, exchange }
41+
}
42+
}
43+
44+
/// Reduces all 10,000 individual dance moves into just two independent transformations.
45+
pub fn parse(input: &str) -> Dance {
46+
// Tokenize the input into two parallel iterators
47+
let mut letters = input.bytes().filter(u8::is_ascii_lowercase);
48+
let mut numbers = input.iter_unsigned::<usize>();
49+
50+
let mut offset = 0;
51+
let mut lookup: [usize; 16] = from_fn(|i| i);
52+
let Dance { mut position, mut exchange } = Dance::new();
53+
54+
while let Some(op) = letters.next() {
55+
match op {
56+
// Increasing the offset has the same effect as rotating elements to the right.
57+
b's' => {
58+
offset += 16 - numbers.next().unwrap();
59+
}
60+
// Swap two elements taking into account the offset when calculating indices.
61+
b'x' => {
62+
let first = numbers.next().unwrap();
63+
let second = numbers.next().unwrap();
64+
position.swap((first + offset) % 16, (second + offset) % 16);
65+
}
66+
// First lookup the index of each letter, then swap the mapping.
67+
b'p' => {
68+
let first = from_byte(letters.next().unwrap());
69+
let second = from_byte(letters.next().unwrap());
70+
lookup.swap(first, second);
71+
exchange.swap(lookup[first], lookup[second]);
72+
}
73+
_ => unreachable!(),
74+
}
75+
}
76+
77+
// Rotate the array once to apply all spins.
78+
position.rotate_left(offset % 16);
79+
80+
Dance { position, exchange }
81+
}
82+
83+
/// Apply the transformation once.
84+
pub fn part1(input: &Dance) -> String {
85+
input.apply()
86+
}
87+
88+
/// If a bit is set in the binary representation of 1 billion apply the current transformation,
89+
/// then apply the transformation to itself to double the number of complete dances.
90+
pub fn part2(input: &Dance) -> String {
91+
let mut e = 1_000_000_000;
92+
let mut dance = *input;
93+
let mut result = Dance::new();
94+
95+
while e > 0 {
96+
if e & 1 == 1 {
97+
result = result.compose(dance);
98+
}
99+
100+
e >>= 1;
101+
dance = dance.compose(dance);
102+
}
103+
104+
result.apply()
105+
}
106+
107+
fn from_byte(b: u8) -> usize {
108+
(b - b'a') as usize
109+
}
110+
111+
fn to_char(i: usize) -> char {
112+
((i as u8) + b'a') as char
113+
}

tests/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ mod year2017 {
100100
mod day13_test;
101101
mod day14_test;
102102
mod day15_test;
103+
mod day16_test;
103104
}
104105

105106
mod year2019 {

tests/year2017/day16_test.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#[test]
2+
fn part1_test() {
3+
// No example data
4+
}
5+
6+
#[test]
7+
fn part2_test() {
8+
// No example data
9+
}

0 commit comments

Comments
 (0)