Add day 8
This commit is contained in:
1
2023/day8/.gitignore
vendored
Normal file
1
2023/day8/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
39
2023/day8/Cargo.lock
generated
Normal file
39
2023/day8/Cargo.lock
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "day8"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
10
2023/day8/Cargo.toml
Normal file
10
2023/day8/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "day8"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
indoc = "2.0.4"
|
||||
nom = "7.1.3"
|
||||
261
2023/day8/src/main.rs
Normal file
261
2023/day8/src/main.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nom::{
|
||||
character::complete::{anychar, char, multispace0, newline, one_of},
|
||||
combinator::map,
|
||||
multi::{many1, separated_list1},
|
||||
sequence::{delimited, separated_pair, tuple},
|
||||
IResult,
|
||||
};
|
||||
|
||||
fn lcm(numbers: impl Iterator<Item = usize>) -> usize {
|
||||
fn lcm(a: usize, b: usize) -> usize {
|
||||
fn gcd(a: usize, b: usize) -> usize {
|
||||
if b == 0 {
|
||||
a
|
||||
} else if a == 0 {
|
||||
b
|
||||
} else if a > b {
|
||||
gcd(a - b, b)
|
||||
} else {
|
||||
gcd(a, b - a)
|
||||
}
|
||||
}
|
||||
a / gcd(a, b) * b
|
||||
}
|
||||
|
||||
numbers.reduce(lcm).unwrap()
|
||||
}
|
||||
|
||||
trait Parse {
|
||||
type Out;
|
||||
fn parse(s: &str) -> IResult<&str, Self::Out>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
struct Node(char, char, char);
|
||||
|
||||
fn spaces(i: &str) -> IResult<&str, Vec<char>> {
|
||||
many1(char(' '))(i)
|
||||
}
|
||||
|
||||
impl Parse for Node {
|
||||
type Out = Self;
|
||||
|
||||
fn parse(s: &str) -> IResult<&str, Self::Out> {
|
||||
let (rest, element) = tuple((anychar, anychar, anychar))(s)?;
|
||||
Ok((rest, element.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Node {
|
||||
type Error = String;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
let chars = value.chars().take(3).collect::<Vec<char>>();
|
||||
Ok(Self(chars[0], chars[1], chars[2]))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(char, char, char)> for Node {
|
||||
fn from((a, b, c): (char, char, char)) -> Self {
|
||||
Self(a, b, c)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn ends_with(&self, c: char) -> bool {
|
||||
self.2 == c
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NodeEdges(HashMap<Node, (Node, Node)>);
|
||||
|
||||
impl Parse for NodeEdges {
|
||||
type Out = Self;
|
||||
|
||||
fn parse(s: &str) -> IResult<&str, Self::Out> {
|
||||
let (rest, edges) = separated_list1(
|
||||
newline,
|
||||
separated_pair(
|
||||
Node::parse,
|
||||
tuple((spaces, char('='), spaces)),
|
||||
delimited(
|
||||
char('('),
|
||||
separated_pair(Node::parse, tuple((char(','), spaces)), Node::parse),
|
||||
char(')'),
|
||||
),
|
||||
),
|
||||
)(s)?;
|
||||
let edges: HashMap<Node, (Node, Node)> = HashMap::from_iter(edges);
|
||||
Ok((rest, Self(edges)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Direction {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl TryFrom<char> for Direction {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
'R' => Self::Right,
|
||||
'L' => Self::Left,
|
||||
_ => Err(format!("unknow direction {value}"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Direction {
|
||||
type Out = Self;
|
||||
|
||||
fn parse(s: &str) -> IResult<&str, Self::Out> {
|
||||
let (rest, c) = map(one_of("RL"), |f: char| f.try_into().unwrap())(s)?;
|
||||
Ok((rest, c))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Map {
|
||||
directions: Vec<Direction>,
|
||||
edges: NodeEdges,
|
||||
}
|
||||
|
||||
impl Parse for Map {
|
||||
type Out = Self;
|
||||
|
||||
fn parse(s: &str) -> IResult<&str, Self::Out> {
|
||||
let (rest, (directions, edges)) = delimited(
|
||||
multispace0,
|
||||
separated_pair(many1(Direction::parse), many1(newline), NodeEdges::parse),
|
||||
multispace0,
|
||||
)(s)?;
|
||||
Ok((rest, Self { directions, edges }))
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> Result<usize, String> {
|
||||
let (rest, map) = Map::parse(input).map_err(|e| e.to_string())?;
|
||||
assert!(rest.is_empty());
|
||||
|
||||
let start: Node = "AAA".try_into().unwrap();
|
||||
let goal: Node = "ZZZ".try_into().unwrap();
|
||||
|
||||
let mut current_node = start;
|
||||
|
||||
let mut steps = 0;
|
||||
for direction in map.directions.into_iter().cycle() {
|
||||
if current_node == goal {
|
||||
break;
|
||||
}
|
||||
let new_node = match direction {
|
||||
Direction::Left => map.edges.0[¤t_node].0,
|
||||
Direction::Right => map.edges.0[¤t_node].1,
|
||||
};
|
||||
current_node = new_node;
|
||||
steps += 1;
|
||||
}
|
||||
Ok(steps)
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> Result<usize, String> {
|
||||
let (rest, map) = Map::parse(input).map_err(|e| e.to_string())?;
|
||||
assert!(rest.is_empty());
|
||||
|
||||
let node_chain_lengths = map
|
||||
.edges
|
||||
.0
|
||||
.keys()
|
||||
.filter(|node| node.ends_with('A'))
|
||||
.map(|node| {
|
||||
let mut depth = 1;
|
||||
let mut node = *node;
|
||||
for direction in map.directions[..].iter().cycle() {
|
||||
let new_node = match direction {
|
||||
Direction::Left => map.edges.0[&node].0,
|
||||
Direction::Right => map.edges.0[&node].1,
|
||||
};
|
||||
if new_node.ends_with('Z') {
|
||||
break;
|
||||
}
|
||||
node = new_node;
|
||||
depth += 1;
|
||||
}
|
||||
|
||||
depth
|
||||
});
|
||||
|
||||
Ok(lcm(node_chain_lengths))
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let input = include_str!("../input");
|
||||
|
||||
let args = std::env::args().skip(1).collect::<Vec<String>>();
|
||||
let part = args[0].parse::<usize>().unwrap();
|
||||
|
||||
if part == 1 {
|
||||
println!("Part 1 : {}", part1(input)?);
|
||||
} else if part == 2 {
|
||||
println!("Part 2 : {}", part2(input)?);
|
||||
} else {
|
||||
return Err("unknown part".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn part_1_example_1() {
|
||||
let input = indoc! {"
|
||||
RL
|
||||
|
||||
AAA = (BBB, CCC)
|
||||
BBB = (DDD, EEE)
|
||||
CCC = (ZZZ, GGG)
|
||||
DDD = (DDD, DDD)
|
||||
EEE = (EEE, EEE)
|
||||
GGG = (GGG, GGG)
|
||||
ZZZ = (ZZZ, ZZZ)
|
||||
"};
|
||||
assert_eq!(part1(input).unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part_1_example_2() {
|
||||
let input = indoc! {"
|
||||
LLR
|
||||
|
||||
AAA = (BBB, BBB)
|
||||
BBB = (AAA, ZZZ)
|
||||
ZZZ = (ZZZ, ZZZ)
|
||||
"};
|
||||
assert_eq!(part1(input).unwrap(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part_2_example_1() {
|
||||
let input = 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)
|
||||
"};
|
||||
assert_eq!(part2(input).unwrap(), 6);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user