diff --git a/2023/Cargo.toml b/2023/Cargo.toml index 877cfdf..50a2551 100644 --- a/2023/Cargo.toml +++ b/2023/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" aho-corasick = "1.1.2" anyhow = "1.0.75" clap = { version = "4.4.8", features = ["derive"] } +linked-hash-map = "0.5.6" nom = "7.1.3" num-integer = "0.1.45" diff --git a/2023/src/day15.rs b/2023/src/day15.rs index dff9032..b230d1b 100644 --- a/2023/src/day15.rs +++ b/2023/src/day15.rs @@ -1,3 +1,16 @@ +use linked_hash_map::LinkedHashMap; +use nom::branch::alt; +use nom::bytes::complete::tag; +use nom::bytes::complete::take_till; + +use nom::combinator::map; +use nom::multi::separated_list1; +use nom::sequence::separated_pair; +use nom::sequence::terminated; +use nom::IResult; + +use crate::common::parse_input; + fn trim(input: &[u8]) -> &[u8] { let whitespace = input .iter() @@ -8,10 +21,10 @@ fn trim(input: &[u8]) -> &[u8] { &input[..(input.len() - whitespace)] } -fn hash(input: &[u8]) -> u32 { +fn hash(input: &[u8]) -> u8 { input .iter() - .fold(0, |cur, &c| ((cur + u32::from(c)) * 17) % 256) + .fold(0, |cur, &c| cur.wrapping_add(c).wrapping_mul(17)) } pub fn part1(input: &[u8]) -> anyhow::Result { @@ -19,13 +32,57 @@ pub fn part1(input: &[u8]) -> anyhow::Result { Ok(input .split(|&c| c == b',') - .map(hash) + .map(|word| u32::from(hash(word))) .sum::() .to_string()) } -pub fn part2(_input: &[u8]) -> anyhow::Result { - anyhow::bail!("Not implemented") +enum Command<'a> { + Add(&'a [u8], u32), + Remove(&'a [u8]), +} +fn parse_commands(i: &[u8]) -> IResult<&[u8], Vec> { + fn is_op(c: u8) -> bool { + c == b'=' || c == b'-' + } + + separated_list1( + tag(","), + alt(( + map( + separated_pair(take_till(is_op), tag("="), nom::character::complete::u32), + |(a, b)| Command::Add(a, b), + ), + map(terminated(take_till(is_op), tag("-")), Command::Remove), + )), + )(i) +} + +pub fn part2(input: &[u8]) -> anyhow::Result { + let commands = parse_input(trim(input), parse_commands)?; + let mut boxes = [(); 256].map(|_| LinkedHashMap::new()); + + for command in &commands { + match command { + Command::Add(identifier, focal_len) => { + *boxes[hash(identifier) as usize] + .entry(*identifier) + .or_default() = *focal_len; + } + Command::Remove(identifier) => { + boxes[hash(identifier) as usize].remove(identifier); + } + } + } + + let mut total = 0; + for (i, b) in boxes.iter().enumerate() { + for (slot, &focal_len) in b.values().enumerate() { + total += (i as u32 + 1) * (slot as u32 + 1) * focal_len; + } + } + + Ok(total.to_string()) } #[cfg(test)] @@ -43,4 +100,9 @@ mod tests { fn sample_part1() { assert_eq!("1320", part1(SAMPLE).unwrap()); } + + #[test] + fn sample_part2() { + assert_eq!("145", part2(SAMPLE).unwrap()); + } }