diff --git a/2023/day5/.gitignore b/2023/day5/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/2023/day5/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/2023/day5/Cargo.lock b/2023/day5/Cargo.lock new file mode 100644 index 0000000..73006f1 --- /dev/null +++ b/2023/day5/Cargo.lock @@ -0,0 +1,126 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "day5" +version = "0.1.0" +dependencies = [ + "indoc", + "nom", + "rayon", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[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 = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[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", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" diff --git a/2023/day5/Cargo.toml b/2023/day5/Cargo.toml new file mode 100644 index 0000000..ae6f898 --- /dev/null +++ b/2023/day5/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "day5" +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" +rayon = "1.8.0" diff --git a/2023/day5/src/main.rs b/2023/day5/src/main.rs new file mode 100644 index 0000000..7ff0440 --- /dev/null +++ b/2023/day5/src/main.rs @@ -0,0 +1,322 @@ +use std::ops::Range; + +use nom::{ + bytes::complete::{tag, take_until}, + character::complete::{char, digit1, multispace0, multispace1}, + combinator::map, + multi::{many1, separated_list1}, + sequence::{preceded, separated_pair, terminated, tuple}, + IResult, +}; +use rayon::prelude::*; + +fn number(i: &str) -> IResult<&str, usize> { + map(digit1, |f: &str| f.parse::().unwrap())(i) +} + +fn spaces(i: &str) -> IResult<&str, Vec> { + many1(char(' '))(i) +} + +fn ident(i: &str) -> IResult<&str, &str> { + take_until(" ")(i) +} + +#[derive(Debug)] +struct Almanac { + seeds: Box, + map_lists: Vec, +} + +impl> Almanac { + fn parse(s: &str) -> IResult<&str, Self> { + let map_lists = separated_list1(multispace1, MapList::parse); + let (rest, (seeds, map_lists)) = terminated( + separated_pair(::parse, multispace1, map_lists), + multispace0, + )(s)?; + Ok((rest, Self { seeds, map_lists })) + } +} + +#[derive(Debug)] +struct SeedList(Vec); + +#[derive(Debug)] +struct SeedRanges(Vec>); + +trait Seeds { + type Out: Seeds; + fn parse(s: &str) -> IResult<&str, Box>; +} + +impl Seeds for SeedList { + type Out = Self; + fn parse(s: &str) -> IResult<&str, Box> { + let (rest, ids) = preceded(tag("seeds: "), separated_list1(char(' '), number))(s)?; + Ok((rest, Box::new(Self(ids)))) + } +} + +impl Seeds for SeedRanges { + type Out = Self; + fn parse(s: &str) -> IResult<&str, Box> { + let (rest, range) = preceded( + tag("seeds: "), + separated_list1(char(' '), separated_pair(number, spaces, number)), + )(s)?; + Ok(( + rest, + Box::new(Self( + range + .into_iter() + .map(|(start, length)| start..start + length) + .collect(), + )), + )) + } +} + +#[derive(Debug)] +struct Map { + source_range: Range, + destination_range: Range, +} + +impl Map { + fn parse(s: &str) -> IResult<&str, Self> { + let (rest, (destination_start, source_start, length)) = tuple(( + terminated(number, spaces), + terminated(number, spaces), + number, + ))(s)?; + Ok(( + rest, + Self { + source_range: source_start..(source_start + length), + destination_range: destination_start..(destination_start + length), + }, + )) + } + + fn map(&self, value: usize) -> Option { + if self.source_range.contains(&value) { + let index = value - self.source_range.start; + // `nth()` is implemented via a simple addition, there is no actual calling + // of `next()` + Some(self.destination_range.clone().nth(index).unwrap()) + } else { + None + } + } +} + +#[derive(Debug)] +struct MapList { + #[allow(dead_code)] + header: String, + maps: Vec, +} + +impl MapList { + fn parse(s: &str) -> IResult<&str, Self> { + let maps = separated_list1(multispace1, Map::parse); + let header = terminated(ident, tag(" map:")); + + let (rest, (header, maps)) = separated_pair(header, multispace1, maps)(s)?; + + Ok(( + rest, + Self { + header: header.to_owned(), + maps, + }, + )) + } + + fn map(&self, value: usize) -> usize { + for map in &self.maps { + if let Some(mapped_value) = map.map(value) { + return mapped_value; + } + } + value + } +} + +fn part1(input: &str) -> Result { + let (rest, almanac) = Almanac::::parse(input).map_err(|e| e.to_string())?; + if !rest.is_empty() { + println!("parsing rest found: {rest}"); + panic!(); + } + let mut lowest_location = usize::MAX; + for seed in &almanac.seeds.0 { + let mut mapped_value = *seed; + for map_list in &almanac.map_lists { + mapped_value = map_list.map(mapped_value); + } + if mapped_value < lowest_location { + lowest_location = mapped_value; + } + } + Ok(lowest_location) +} + +fn part2(input: &str) -> Result { + let (rest, almanac) = Almanac::::parse(input).map_err(|e| e.to_string())?; + if !rest.is_empty() { + println!("parsing rest found: {rest}"); + panic!(); + } + let lowest_location = almanac + .seeds + .0 + .into_par_iter() + .map(|seed_range| { + println!("{seed_range:?}"); + // let seeds = seed_range.collect::>(); + let result = seed_range + .clone() + .map(|seed| { + let mut mapped_value = seed; + for map_list in &almanac.map_lists { + mapped_value = map_list.map(mapped_value); + } + mapped_value + }) + .min() + .unwrap(); + println!("{seed_range:?} => {result}"); + result + }) + .min() + .unwrap(); + Ok(lowest_location) +} + +fn main() -> Result<(), String> { + let input = include_str!("../input"); + + println!("Part 1 : {}", part1(input)?); + println!("Part 2 : {}", part2(input)?); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + + #[test] + fn example_01() { + let input = indoc! {" + seeds: 79 14 55 13 + + seed-to-soil map: + 50 98 2 + 52 50 48 + + soil-to-fertilizer map: + 0 15 37 + 37 52 2 + 39 0 15 + + fertilizer-to-water map: + 49 53 8 + 0 11 42 + 42 0 7 + 57 7 4 + + water-to-light map: + 88 18 7 + 18 25 70 + + light-to-temperature map: + 45 77 23 + 81 45 19 + 68 64 13 + + temperature-to-humidity map: + 0 69 1 + 1 0 69 + + humidity-to-location map: + 60 56 37 + 56 93 4 + "}; + + assert_eq!(part1(&input).unwrap(), 35); + } + + #[test] + fn test_map() { + let input = indoc! {" + 50 98 2 + "}; + + let (_rest, map) = Map::parse(input).unwrap(); + assert_eq!(map.map(97), None); + assert_eq!(map.map(98), Some(50)); + assert_eq!(map.map(99), Some(51)); + assert_eq!(map.map(100), None); + assert_eq!(map.map(101), None); + } + + #[test] + fn test_map_list() { + let input = indoc! {" + seed-to-soil map: + 50 98 2 + 52 50 48 + "}; + + let (_rest, maplist) = MapList::parse(input).unwrap(); + assert_eq!(maplist.map(79), 81); + assert_eq!(maplist.map(14), 14); + assert_eq!(maplist.map(55), 57); + assert_eq!(maplist.map(13), 13); + } + + #[test] + fn example_02() { + let input = indoc! {" + seeds: 79 14 55 13 + + seed-to-soil map: + 50 98 2 + 52 50 48 + + soil-to-fertilizer map: + 0 15 37 + 37 52 2 + 39 0 15 + + fertilizer-to-water map: + 49 53 8 + 0 11 42 + 42 0 7 + 57 7 4 + + water-to-light map: + 88 18 7 + 18 25 70 + + light-to-temperature map: + 45 77 23 + 81 45 19 + 68 64 13 + + temperature-to-humidity map: + 0 69 1 + 1 0 69 + + humidity-to-location map: + 60 56 37 + 56 93 4 + "}; + + assert_eq!(part2(&input).unwrap(), 46); + } +}