Skip to content

Commit

Permalink
Day 8: Haunted Wasteland
Browse files Browse the repository at this point in the history
  • Loading branch information
ephemient committed Dec 8, 2023
1 parent daaed35 commit 6623fae
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ Development occurs in language-specific directories:
|[Day5.hs](hs/src/Day5.hs)|[Day5.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day5.kt)|[day5.py](py/aoc2023/day5.py)|[day5.rs](rs/src/day5.rs)|
|[Day6.hs](hs/src/Day6.hs)|[Day6.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day6.kt)|[day6.py](py/aoc2023/day6.py)|[day6.rs](rs/src/day6.rs)|
|[Day7.hs](hs/src/Day7.hs)|[Day7.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day7.kt)|[day7.py](py/aoc2023/day7.py)|[day7.rs](rs/src/day7.rs)|
|[Day8.hs](hs/src/Day8.hs)|[Day8.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day8.kt)|[day8.py](py/aoc2023/day8.py)||
|[Day8.hs](hs/src/Day8.hs)|[Day8.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day8.kt)|[day8.py](py/aoc2023/day8.py)|[day8.rs](rs/src/day8.rs)|
8 changes: 7 additions & 1 deletion rs/benches/criterion.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use aoc2023::{day1, day2, day3, day4, day5, day6, day7};
use aoc2023::{day1, day2, day3, day4, day5, day6, day7, day8};
use criterion::{black_box, Criterion};
use std::env;
use std::fs;
Expand Down Expand Up @@ -56,6 +56,12 @@ fn aoc2023_bench(c: &mut Criterion) -> io::Result<()> {
g.bench_function("part 2", |b| b.iter(|| day7::part2(black_box(&data))));
g.finish();

let data = get_day_input(8)?;
let mut g = c.benchmark_group("day 8");
g.bench_function("part 1", |b| b.iter(|| day8::part1(black_box(&data))));
g.bench_function("part 2", |b| b.iter(|| day8::part2(black_box(&data))));
g.finish();

Ok(())
}

Expand Down
122 changes: 122 additions & 0 deletions rs/src/day8.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use crate::math::lcm;
use std::{collections::BTreeMap, iter};

struct Network<'a> {
instructions: Vec<bool>,
table: BTreeMap<&'a str, (&'a str, &'a str)>,
}

impl<'a> Network<'a> {
fn step(&self, start: &'a str) -> Option<&'a str> {
self.instructions
.iter()
.try_fold(start, |node, &instruction| {
let (left, right) = self.table.get(node)?;
Some(*if instruction { left } else { right })
})
}
}

fn parse(data: &str) -> Option<Network> {
let mut iter = data.lines();
let instructions = iter
.next()?
.chars()
.map(|c| match c {
'L' => Some(true),
'R' => Some(false),
_ => None,
})
.collect::<Option<_>>()?;
let table = iter
.filter(|s| !s.is_empty())
.map(|line| {
let (from, to) = line.split_once(" = ")?;
let (left, right) = to.split_once(", ")?;
Some((from, (left.strip_prefix('(')?, right.strip_suffix(')')?)))
})
.collect::<Option<_>>()?;
Some(Network {
instructions,
table,
})
}

pub fn part1(data: &str) -> Option<usize> {
let network = parse(data)?;
Some(
network.instructions.len()
* iter::successors(Some("AAA"), |&node| network.step(node))
.position(|node| node == "ZZZ")?,
)
}

pub fn part2(data: &str) -> Option<usize> {
let network = parse(data)?;
Some(
network.instructions.len()
* network
.table
.keys()
.filter(|node| node.ends_with('A'))
.try_fold(1, |acc, &start| {
let (i, end) = iter::successors(Some(start), |&node| network.step(node))
.enumerate()
.find(|(_, node)| node.ends_with('Z'))?;
if network.step(start) == network.step(end) {
Some(lcm(acc, i))
} else {
None
}
})?,
)
}

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

static EXAMPLE_1: &str = indoc! {"
RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)
"};
static EXAMPLE_2: &str = indoc! {"
LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)
"};
static EXAMPLE_3: &str = indoc! {"
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)
"};

#[test]
fn part1_examples() {
assert_eq!(Some(2), part1(EXAMPLE_1));
assert_eq!(Some(6), part1(EXAMPLE_2));
}

#[test]
fn part2_examples() {
assert_eq!(Some(6), part2(EXAMPLE_3));
}
}
2 changes: 2 additions & 0 deletions rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pub mod day4;
pub mod day5;
pub mod day6;
pub mod day7;
pub mod day8;
mod math;
10 changes: 9 additions & 1 deletion rs/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use aoc2023::{day1, day2, day3, day4, day5, day6, day7};
use aoc2023::{day1, day2, day3, day4, day5, day6, day7, day8};
use std::collections::HashSet;
use std::env;
use std::fs;
Expand Down Expand Up @@ -72,5 +72,13 @@ fn main() -> io::Result<()> {
println!();
}

if args.is_empty() || args.contains("8") {
let data = get_day_input(8)?;
println!("Day 8");
println!("{:?}", day8::part1(&data).expect("error"));
println!("{:?}", day8::part2(&data).expect("error"));
println!();
}

Ok(())
}
17 changes: 17 additions & 0 deletions rs/src/math.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::default::Default;
use std::ops::{Div, Mul, Rem};

pub fn gcd<T: Copy + Default + Eq + Rem<Output = T>>(x: T, y: T) -> T {
let (mut a, mut b) = (x, y);
while b != T::default() {
(b, a) = (a % b, b);
}
a
}

pub fn lcm<T: Copy + Default + Div<Output = T> + Eq + Mul<Output = T> + Rem<Output = T>>(
x: T,
y: T,
) -> T {
x / gcd(x, y) * y
}

0 comments on commit 6623fae

Please sign in to comment.