Skip to content

Commit e3657c8

Browse files
committed
Year 2016 Day 19
1 parent 0426518 commit e3657c8

File tree

8 files changed

+152
-1
lines changed

8 files changed

+152
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ pie
260260
| 15 | [Timing is Everything](https://adventofcode.com/2016/day/15) | [Source](src/year2016/day15.rs) | 1 |
261261
| 16 | [Dragon Checksum](https://adventofcode.com/2016/day/16) | [Source](src/year2016/day16.rs) | 1 |
262262
| 17 | [Two Steps Forward](https://adventofcode.com/2016/day/17) | [Source](src/year2016/day17.rs) | 14254 |
263-
| 18 | [Like a Rogue ](https://adventofcode.com/2016/day/18) | [Source](src/year2016/day18.rs) | 743 |
263+
| 18 | [Like a Rogue](https://adventofcode.com/2016/day/18) | [Source](src/year2016/day18.rs) | 743 |
264+
| 19 | [An Elephant Named Joseph](https://adventofcode.com/2016/day/19) | [Source](src/year2016/day19.rs) | 1 |
264265

265266
## 2015
266267

benches/benchmark.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ mod year2016 {
8282
benchmark!(year2016, day16);
8383
benchmark!(year2016, day17);
8484
benchmark!(year2016, day18);
85+
benchmark!(year2016, day19);
8586
}
8687

8788
mod year2019 {

input/year2016/day19.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3005290

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ pub mod year2016 {
209209
pub mod day16;
210210
pub mod day17;
211211
pub mod day18;
212+
pub mod day19;
212213
}
213214

214215
/// # 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
@@ -120,6 +120,7 @@ fn all_solutions() -> Vec<Solution> {
120120
solution!(year2016, day16),
121121
solution!(year2016, day17),
122122
solution!(year2016, day18),
123+
solution!(year2016, day19),
123124
// 2019
124125
solution!(year2019, day01),
125126
solution!(year2019, day02),

src/year2016/day19.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//! # An Elephant Named Joseph
2+
//!
3+
//! The title is a reference to the [Josephus problem](https://en.wikipedia.org/wiki/Josephus_problem).
4+
//! We can solve both parts efficiently in `O(1)` constant storage without needing any
5+
//! auxiliary data strucutres.
6+
//!
7+
//! ## Part One
8+
//!
9+
//! Part one is exactly the Josephus problem with k = 2. Each round the number of elves is either
10+
//! odd or even. If the number of elves is even then every second elf is eliminated and the starting
11+
//! elf remains the same, for example starting with 8 elves and a step size of 1:
12+
//!
13+
//! 1 ~~2~~ 3 ~~4~~ 5 ~~6~~ 7 ~~8~~
14+
//!
15+
//! The next round we double the step size used to find elves from 1 to 2:
16+
//!
17+
//! 1 ~~2~~ ~~3~~ ~~4~~ 5 ~~6~~ ~~7~~ ~~8~~
18+
//!
19+
//! In the special case that the number of elves is power of two then the starting elf will always
20+
//! win.
21+
//!
22+
//! If the number of elves is odd then the last elf will also eliminate the starting elf,
23+
//! so the new starting elf increases by the step size.
24+
//!
25+
//! ~~1~~ ~~2~~ 3 ~~4~~ 5
26+
//!
27+
//! We can represent this as a loop
28+
//!
29+
//! ```none
30+
//! let mut n = <starting number of elves>
31+
//! let mut step = 1;
32+
//! let mut winner = 1;
33+
//! while n > 1 {
34+
//! if n % 2 == 1 {
35+
//! winner += step * 2;
36+
//! }
37+
//! n /= 2;
38+
//! step *= 2;
39+
//! }
40+
//! ```
41+
//!
42+
//! If we examine the loop we can see that the winner is simply the binary digits of `n` multiplied
43+
//! by two, excluding the highest bit, with one added. For example for 5 elves:
44+
//!
45+
//! ```none
46+
//! n = 5 = 101
47+
//! n * 2 = 10 = 1010
48+
//! n minus high bit = 010
49+
//! n plus one = 011 = 3
50+
//! ```
51+
//!
52+
//! The [`ilog2`] function will return the number of zeroes before the highest one bit, for example
53+
//! `10.ilog2() = 3`. We can then shift a bit by that amount and subtract to get the result in
54+
//! constant `O(1)` time.
55+
//!
56+
//!
57+
//! ## Part Two
58+
//!
59+
//! Part two is a variant of the problem. We solve in `log(n)` time by working *backwards*
60+
//! from the winning elf until we reach the starting number of elves.
61+
//! Starting with the winning elf `a` it must have eliminated its neighbour to the right:
62+
//!
63+
//! `a` => `a b`
64+
//!
65+
//! We then choose the previous elf to the left wrapping around to elf `b` in this case. Elf `b`
66+
//! must have eliminated its neighbour 1 step to the right:
67+
//!
68+
//! `a b` => `a b c`
69+
//!
70+
//! Play passes to the left, in this case elf `a` must have eliminated an elf 2 steps away
71+
//!
72+
//! `a b c` => `a b d c`
73+
//!
74+
//! Play passes to the left, wrapping around to elf `c` that must have eliminated an elf 2 steps
75+
//! away
76+
//!
77+
//! `a b d c` => `a e b d c`
78+
//!
79+
//! Now that we have 5 elves our starting elf `a` is one step away from `c` so the answer is 2.
80+
//!
81+
//! [`ilog2`]: u32::ilog2
82+
use crate::util::parse::*;
83+
84+
pub fn parse(input: &str) -> u32 {
85+
input.unsigned()
86+
}
87+
88+
pub fn part1(input: &u32) -> u32 {
89+
let mut elf = *input;
90+
91+
elf *= 2;
92+
// Remove highest 1 bit
93+
elf -= 1 << elf.ilog2();
94+
// Elves use 1-based indexing
95+
elf += 1;
96+
97+
elf
98+
}
99+
100+
pub fn part2(input: &u32) -> u32 {
101+
let target = *input;
102+
let mut elf = 0;
103+
let mut size = 1;
104+
105+
while size < target {
106+
let remaining = target - size;
107+
108+
// The trick to log(n) time is that we can handle all elves greater than or less than
109+
// the starting point in one pass, greatly increasing efficiency.
110+
if elf > size / 2 {
111+
// If the elf is greater than the half way point, then an elf will be inserted before
112+
// it. This cancels out moving to the previous elf so our position remains the same.
113+
let possible = 2 * elf - size;
114+
size += possible.min(remaining);
115+
} else {
116+
// The next elf will be the one before us. If we are at the start then wrap around
117+
// to the last elf.
118+
if elf >= remaining {
119+
elf -= remaining;
120+
size += remaining;
121+
} else {
122+
elf += size;
123+
size = elf + 1;
124+
}
125+
}
126+
}
127+
128+
// The winning position is at 0, so its number is the distance from the starting elf.
129+
target - elf
130+
}

tests/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ mod year2016 {
7575
mod day16_test;
7676
mod day17_test;
7777
mod day18_test;
78+
mod day19_test;
7879
}
7980

8081
mod year2019 {

tests/year2016/day19_test.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use aoc::year2016::day19::*;
2+
3+
const EXAMPLE: &str = "5";
4+
5+
#[test]
6+
fn part1_test() {
7+
let input = parse(EXAMPLE);
8+
assert_eq!(part1(&input), 3);
9+
}
10+
11+
#[test]
12+
fn part2_test() {
13+
let input = parse(EXAMPLE);
14+
assert_eq!(part2(&input), 2);
15+
}

0 commit comments

Comments
 (0)