Solve day 8
This commit is contained in:
parent
f16fae23ab
commit
9dbfc6043a
4 changed files with 207 additions and 2 deletions
144
src/bin/08.rs
Normal file
144
src/bin/08.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use aoc2023::lcm;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const INPUT: &str = include_str!("../../input/08");
|
||||
const TEST_INPUT_1: &str = include_str!("../../input/08-test-1");
|
||||
const TEST_INPUT_2: &str = include_str!("../../input/08-test-2");
|
||||
|
||||
fn main() {
|
||||
assert_eq!(part1(TEST_INPUT_1), 6, "Part 1");
|
||||
println!("Part 1: {}", part1(INPUT));
|
||||
assert_eq!(part2(TEST_INPUT_2), 6, "Part 2");
|
||||
println!("Part 2: {}", part2(INPUT));
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
let start = "AAA".to_string();
|
||||
let end = |location: &str| location == "ZZZ";
|
||||
Map::parse(input).count_steps(start, end)
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
let map = Map::parse(input);
|
||||
let end = |location: &str| location.ends_with('Z');
|
||||
|
||||
map.starting_locations()
|
||||
.map(|start| map.count_steps(start, end))
|
||||
.reduce(lcm)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
struct Map {
|
||||
transitions: HashMap<String, Directions>,
|
||||
instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
impl Map {
|
||||
/// Apply a list of instructions.
|
||||
fn follow(&self, from: String) -> String {
|
||||
self.instructions
|
||||
.iter()
|
||||
.fold(from, |location, &instruction| {
|
||||
self.step(location, instruction)
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply a single instruction.
|
||||
fn step(&self, from: String, instruction: Instruction) -> String {
|
||||
let direction = self.transitions.get(&from).unwrap();
|
||||
match instruction {
|
||||
Instruction::Left => direction.left.clone(),
|
||||
Instruction::Right => direction.right.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn starting_locations(&self) -> impl Iterator<Item = String> + '_ {
|
||||
self.transitions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with('A'))
|
||||
.map(|k| k.to_string())
|
||||
}
|
||||
|
||||
fn count_steps(&self, start: String, end: impl Fn(&str) -> bool) -> usize {
|
||||
let mut location = start;
|
||||
let mut steps = 0;
|
||||
|
||||
while !end(&location) {
|
||||
location = self.follow(location);
|
||||
steps += self.instructions.len();
|
||||
}
|
||||
|
||||
steps
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Self {
|
||||
let mut lines = input.lines();
|
||||
let instructions = lines
|
||||
.next()
|
||||
.unwrap()
|
||||
.chars()
|
||||
.map(Instruction::parse)
|
||||
.collect();
|
||||
let transitions = lines
|
||||
.skip(1)
|
||||
.map(Transition::parse)
|
||||
.map(|t| (t.from, t.directions))
|
||||
.collect();
|
||||
Self {
|
||||
instructions,
|
||||
transitions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Directions {
|
||||
left: String,
|
||||
right: String,
|
||||
}
|
||||
|
||||
impl Directions {
|
||||
fn parse(tuple: &str) -> Self {
|
||||
let (left, right) = tuple
|
||||
.strip_prefix('(')
|
||||
.unwrap()
|
||||
.strip_suffix(')')
|
||||
.unwrap()
|
||||
.split_once(", ")
|
||||
.unwrap();
|
||||
Self {
|
||||
left: left.to_string(),
|
||||
right: right.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Instruction {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
fn parse(c: char) -> Self {
|
||||
match c {
|
||||
'L' => Self::Left,
|
||||
'R' => Self::Right,
|
||||
other => panic!("unknown instruction '{other}'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Transition {
|
||||
from: String,
|
||||
directions: Directions,
|
||||
}
|
||||
|
||||
impl Transition {
|
||||
fn parse(line: &str) -> Self {
|
||||
let (from, directions) = line.split_once(" = ").unwrap();
|
||||
Self {
|
||||
from: from.to_string(),
|
||||
directions: Directions::parse(directions),
|
||||
}
|
||||
}
|
||||
}
|
50
src/lib.rs
50
src/lib.rs
|
@ -1,5 +1,8 @@
|
|||
use std::fmt::{Debug, Display};
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
ops::{Div, Mul, Rem},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// Parse a whitespace separated list of things.
|
||||
///
|
||||
|
@ -27,3 +30,46 @@ where
|
|||
{
|
||||
format!("{a}{b}").parse().unwrap()
|
||||
}
|
||||
|
||||
/// Return the greatest common divisor.
|
||||
///
|
||||
/// https://en.wikipedia.org/wiki/Euclidean_algorithm
|
||||
pub fn gcd<T>(mut a: T, mut b: T) -> T
|
||||
where
|
||||
T: PartialEq + Default + Rem<Output = T> + Copy,
|
||||
{
|
||||
let zero = T::default();
|
||||
while b != zero {
|
||||
(a, b) = (b, a % b);
|
||||
}
|
||||
|
||||
a
|
||||
}
|
||||
|
||||
/// Return the least common multiple.
|
||||
///
|
||||
/// https://en.wikipedia.org/wiki/Least_common_multiple
|
||||
pub fn lcm<T>(a: T, b: T) -> T
|
||||
where
|
||||
T: PartialEq + Default + Rem<Output = T> + Div<Output = T> + Mul<Output = T> + Copy,
|
||||
{
|
||||
let gcd = gcd(a, b);
|
||||
a / gcd * b
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn test_gcd() {
|
||||
assert_eq!(gcd(252, 105), 21);
|
||||
assert_eq!(gcd(105, 252), 21);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lcm() {
|
||||
assert_eq!(lcm(4, 6), 12);
|
||||
assert_eq!(lcm(6, 4), 12);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue