Skip to content

Commit

Permalink
Day 21
Browse files Browse the repository at this point in the history
  • Loading branch information
johnhurt committed Dec 22, 2024
1 parent bd61268 commit 192bfb8
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 22 deletions.
45 changes: 23 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,29 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.

| Day | Part 1 | Part 2 |
| :---: | :---: | :---: |
| [Day 1](./src/bin/01.rs) | `45.3µs` | `52.7µs` |
| [Day 2](./src/bin/02.rs) | `161.6µs` | `228.2µs` |
| [Day 3](./src/bin/03.rs) | `32.4µs` | `33.7µs` |
| [Day 4](./src/bin/04.rs) | `1.4ms` | `893.4µs` |
| [Day 5](./src/bin/05.rs) | `81.4µs` | `189.0µs` |
| [Day 6](./src/bin/06.rs) | `152.6µs` | `66.0ms` |
| [Day 7](./src/bin/07.rs) | `2.1ms` | `129.8ms` |
| [Day 8](./src/bin/08.rs) | `41.7µs` | `113.5µs` |
| [Day 9](./src/bin/09.rs) | `81.6µs` | `292.2ms` |
| [Day 10](./src/bin/10.rs) | `63.1µs` | `56.0µs` |
| [Day 11](./src/bin/11.rs) | `392.3µs` | `20.6ms` |
| [Day 12](./src/bin/12.rs) | `3.6ms` | `5.9ms` |
| [Day 13](./src/bin/13.rs) | `26.7µs` | `24.2µs` |
| [Day 14](./src/bin/14.rs) | `24.3µs` | `12.4ms` |
| [Day 15](./src/bin/15.rs) | `104.2µs` | `1.2ms` |
| [Day 16](./src/bin/16.rs) | `3.3ms` | `571.9ms` |
| [Day 17](./src/bin/17.rs) | `823.0ns` | `14.2ms` |
| [Day 18](./src/bin/18.rs) | `307.7µs` | `328.6µs` |
| [Day 19](./src/bin/19.rs) | `1.8ms` | `4.6ms` |
| [Day 20](./src/bin/20.rs) | `297.4µs` | `283.3ms` |

**Total: 1418.03ms**
| [Day 1](./src/bin/01.rs) | `45.9µs` | `52.4µs` |
| [Day 2](./src/bin/02.rs) | `205.4µs` | `228.0µs` |
| [Day 3](./src/bin/03.rs) | `33.2µs` | `36.2µs` |
| [Day 4](./src/bin/04.rs) | `1.5ms` | `1.0ms` |
| [Day 5](./src/bin/05.rs) | `83.0µs` | `179.8µs` |
| [Day 6](./src/bin/06.rs) | `172.2µs` | `61.4ms` |
| [Day 7](./src/bin/07.rs) | `2.8ms` | `133.8ms` |
| [Day 8](./src/bin/08.rs) | `43.6µs` | `113.9µs` |
| [Day 9](./src/bin/09.rs) | `81.7µs` | `312.4ms` |
| [Day 10](./src/bin/10.rs) | `58.6µs` | `57.9µs` |
| [Day 11](./src/bin/11.rs) | `439.5µs` | `19.6ms` |
| [Day 12](./src/bin/12.rs) | `4.3ms` | `5.7ms` |
| [Day 13](./src/bin/13.rs) | `29.3µs` | `37.0µs` |
| [Day 14](./src/bin/14.rs) | `25.4µs` | `11.7ms` |
| [Day 15](./src/bin/15.rs) | `112.5µs` | `1.2ms` |
| [Day 16](./src/bin/16.rs) | `3.5ms` | `587.4ms` |
| [Day 17](./src/bin/17.rs) | `902.0ns` | `15.4ms` |
| [Day 18](./src/bin/18.rs) | `277.1µs` | `330.6µs` |
| [Day 19](./src/bin/19.rs) | `1.6ms` | `4.7ms` |
| [Day 20](./src/bin/20.rs) | `279.7µs` | `291.3ms` |
| [Day 21](./src/bin/21.rs) | `265.9µs` | `330.9ms` |

**Total: 1793.39ms**
<!--- benchmarking table --->

---
Expand Down
5 changes: 5 additions & 0 deletions data/examples/21.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
029A
980A
179A
456A
379A
281 changes: 281 additions & 0 deletions src/bin/21.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
use std::collections::VecDeque;

use advent_of_code::Grid;
use itertools::Itertools;
use once_cell::sync::Lazy;
use rand::RngCore;
use strum::{Display, EnumIter, EnumString, FromRepr};

advent_of_code::solution!(21);

/// The grid for part 1 is
/// +---+---+---+
/// | 7 | 8 | 9 |
/// +---+---+---+
/// | 4 | 5 | 6 |
/// +---+---+---+
/// | 1 | 2 | 3 |
/// +---+---+---+
/// |0/^| A |
/// +---+---+---+
/// | < | v | > |
/// +---+---+---+
/// 7 is at index 0 and index 9 is not accessible
const BUTTON_COUNT: usize = 15;

static GRID: Lazy<Grid<Button>> = Lazy::new(|| {
Grid::new(
(0..BUTTON_COUNT)
.map(|i| Button::from_repr(i as u8).unwrap())
.collect_vec(),
3,
)
});

#[derive(
EnumString,
FromRepr,
Display,
Debug,
Clone,
Copy,
PartialEq,
Eq,
Default,
EnumIter,
Hash,
)]
#[repr(u8)]
enum Button {
Seven,
Eight,
Nine,
Four,
Five,
Six,
One,
Two,
Three,
None,
ZeroUp,
#[default]
A,
Left,
Down,
Right,
}

impl From<u8> for Button {
fn from(c: u8) -> Self {
match c {
b'0' => Button::ZeroUp,
b'1' => Button::One,
b'2' => Button::Two,
b'3' => Button::Three,
b'4' => Button::Four,
b'5' => Button::Five,
b'6' => Button::Six,
b'7' => Button::Seven,
b'8' => Button::Eight,
b'9' => Button::Nine,
b'A' => Button::A,
b'>' => Button::Right,
b'<' => Button::Left,
b'^' => Button::ZeroUp,
b'v' => Button::Down,
_ => unreachable!(),
}
}
}

impl Button {
fn same_class(b1: usize, b2: usize) -> bool {
matches!((b1, b2), (0..=8 | 10 | 11, 0..=8 | 10 | 11) | (10.., 10..))
}
}

type Path = Vec<Button>;

fn seeded_all_paths(mut seed: u64) -> Vec<Path> {
(0..(BUTTON_COUNT.pow(2)))
.map(|i| (i / BUTTON_COUNT, i % BUTTON_COUNT))
.map(|(start, end)| shortest_path(start, end, &mut seed))
.collect_vec()
}

fn count_changes(path: &Path) -> usize {
path.iter()
.fold((0, None), |(acc, prev), curr| {
if let Some(prev) = prev {
(acc + (curr != prev) as usize, Some(curr))
} else {
(0, Some(curr))
}
})
.0
}

fn shortest_path(start: usize, end: usize, seed: &mut u64) -> Path {
let mut result = vec![];

if !Button::same_class(start, end) {
return vec![];
}

let mut queue = [(start, 1 << start, Path::default(), 0)]
.into_iter()
.collect::<VecDeque<_>>();
let mut shortest_opt = None;

while let Some((curr, seen, path, dist)) = queue.pop_front() {
if curr == end {
if let Some(shortest) = shortest_opt {
if shortest < dist {
break;
}
} else {
shortest_opt = Some(dist);
}

if count_changes(&path) < 2 {
result.push(path);
}
continue;
}

queue.extend(
GRID.neighbors(curr)
.filter(|(_, n)| GRID.data[*n] != Button::None)
.map(|(d, n)| (d, n, (1 << n) | seen))
.filter(|(_, _, n_seen)| *n_seen != seen)
.map(|(d, n, n_seen)| {
let mut n_path = path.clone();

n_path.push(d.to_arrow().into());
(n, n_seen, n_path, dist + 1)
}),
);
}

result.iter_mut().for_each(|p| p.push(Button::A));

if result.len() > 1 {
*seed /= 2;
if *seed % 2 == 1 {
result.into_iter().nth(1).unwrap()
} else {
result.into_iter().next().unwrap()
}
} else {
result.into_iter().next().unwrap()
}
}

fn parse_buttons(line: &str) -> (usize, [Button; 4]) {
let value: usize = line[..3].parse().unwrap();

let mut result = [Button::A; 4];

result
.iter_mut()
.take(3)
.enumerate()
.for_each(|(i, t)| *t = line.as_bytes()[i].into());

(value, result)
}

fn fast_single_answer(
buttons: &[Button],
iterations: usize,
all_paths: &[Path],
) -> usize {
let mut pairs = vec![0; BUTTON_COUNT * BUTTON_COUNT];

let pair_contributions = all_paths
.iter()
.map(|path| {
if path.is_empty() {
vec![]
} else {
Some(Button::A)
.into_iter()
.chain(path.iter().copied())
.tuple_windows::<(_, _)>()
.map(|(f, t)| f as usize * BUTTON_COUNT + t as usize)
.collect_vec()
}
})
.collect_vec();

Some(Button::A)
.into_iter()
.chain(buttons.iter().copied())
.tuple_windows::<(_, _)>()
.map(|(f, t)| f as usize * BUTTON_COUNT + t as usize)
.for_each(|i| pairs[i] += 1);

for _ in 0..iterations {
let mut new_pairs = vec![0; BUTTON_COUNT * BUTTON_COUNT];

pairs.drain(..).enumerate().for_each(|(i, count)| {
pair_contributions[i]
.iter()
.for_each(|j| new_pairs[*j] += count)
});

pairs.append(&mut new_pairs);
}

pairs.into_iter().sum()
}

pub fn part_one(input: &str) -> Option<usize> {
let all_paths = seeded_all_paths(u64::MAX);
let result = input
.lines()
.map(parse_buttons)
.map(|(value, buttons)| {
fast_single_answer(&buttons, 3, &all_paths) * value
})
.sum();

Some(result)
}

pub fn part_two(input: &str) -> Option<usize> {
(0..1000)
.map(|_| {
let seed = rand::thread_rng().next_u64();
let all_paths = seeded_all_paths(seed);

input
.lines()
.map(parse_buttons)
.map(|(value, buttons)| {
value * fast_single_answer(&buttons, 26, &all_paths)
})
.sum()
})
.min()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_part_one() {
let result =
part_one(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(126384));
}

#[test]
fn test_part_two() {
let result =
part_two(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(154115708116294));
}
}
10 changes: 10 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ impl Compass {
D::S => D::E,
}
}

pub fn to_arrow(&self) -> u8 {
use Compass as D;
match self {
D::E => b'>',
D::N => b'^',
D::W => b'<',
D::S => b'v',
}
}
}

#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down

0 comments on commit 192bfb8

Please sign in to comment.