Solve day 8
This commit is contained in:
parent
f16fae23ab
commit
9dbfc6043a
4 changed files with 207 additions and 2 deletions
5
input/08-test-1
Normal file
5
input/08-test-1
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
LLR
|
||||||
|
|
||||||
|
AAA = (BBB, BBB)
|
||||||
|
BBB = (AAA, ZZZ)
|
||||||
|
ZZZ = (ZZZ, ZZZ)
|
10
input/08-test-2
Normal file
10
input/08-test-2
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
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)
|
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::{
|
||||||
use std::str::FromStr;
|
fmt::{Debug, Display},
|
||||||
|
ops::{Div, Mul, Rem},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
/// Parse a whitespace separated list of things.
|
/// Parse a whitespace separated list of things.
|
||||||
///
|
///
|
||||||
|
@ -27,3 +30,46 @@ where
|
||||||
{
|
{
|
||||||
format!("{a}{b}").parse().unwrap()
|
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