Add day 8
This commit is contained in:
@@ -31,6 +31,10 @@ run() {
|
|||||||
echo "day7"
|
echo "day7"
|
||||||
./day7/target/release/day7 1
|
./day7/target/release/day7 1
|
||||||
./day7/target/release/day7 2
|
./day7/target/release/day7 2
|
||||||
|
|
||||||
|
echo "day8"
|
||||||
|
./day8/target/release/day8 1
|
||||||
|
./day8/target/release/day8 2
|
||||||
}
|
}
|
||||||
|
|
||||||
time run
|
time run
|
||||||
|
|||||||
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