diff --git a/2023/src/common.rs b/2023/src/common.rs index 6fd7af5..4bebc2d 100644 --- a/2023/src/common.rs +++ b/2023/src/common.rs @@ -16,6 +16,14 @@ use nom::IResult; use nom::InputLength; use nom::Parser; +pub fn convert_nom_error(e: nom::error::Error<&[u8]>) -> anyhow::Error { + anyhow::anyhow!( + "Parser error {:?} to parse at {}", + e.code, + String::from_utf8_lossy(e.input) + ) +} + /// Parse input from some nom parser and return as an anyhow result /// /// This method exists as a convenience because nom's errors cannot otherwise be easily converted to @@ -24,13 +32,7 @@ pub fn parse_input<'a, O>( input: &'a [u8], mut parser: impl Parser<&'a [u8], O, nom::error::Error<&'a [u8]>>, ) -> Result { - let (unparsed, output) = parser.parse(input).finish().map_err(|e| { - anyhow::anyhow!( - "Parser error {:?} to parse at {}", - e.code, - String::from_utf8_lossy(e.input) - ) - })?; + let (unparsed, output) = parser.parse(input).finish().map_err(convert_nom_error)?; if !unparsed.is_empty() { Err(anyhow::anyhow!( diff --git a/2023/src/day02.rs b/2023/src/day02.rs index 7c1760f..50fb387 100644 --- a/2023/src/day02.rs +++ b/2023/src/day02.rs @@ -1,5 +1,78 @@ -pub fn part1(_input: &[u8]) -> anyhow::Result { - anyhow::bail!("Not implemented") +use nom::branch::alt; +use nom::bytes::complete::tag; +use nom::character::complete::newline; +use nom::combinator::iterator; +use nom::combinator::opt; +use nom::combinator::value; +use nom::multi::fold_many1; +use nom::multi::many1; +use nom::sequence::preceded; +use nom::sequence::separated_pair; +use nom::sequence::terminated; +use nom::IResult; + +use crate::common::convert_nom_error; +use crate::common::parse_input; + +#[derive(Clone, Copy)] +#[repr(usize)] +enum Color { + Red, + Green, + Blue, +} + +fn parse_game(i: &[u8]) -> IResult<&[u8], (u8, [u8; 3])> { + let parse_color = terminated( + separated_pair( + nom::character::complete::u8, + tag(" "), + alt(( + value(Color::Red, tag("red")), + value(Color::Green, tag("green")), + value(Color::Blue, tag("blue")), + )), + ), + opt(alt((tag(", "), tag("; ")))), + ); + + separated_pair( + preceded(tag("Game "), nom::character::complete::u8), + tag(": "), + terminated( + fold_many1( + parse_color, + || [0u8; 3], + |mut cur, (value, color)| { + cur[color as usize] = Ord::max(cur[color as usize], value); + cur + }, + ), + newline, + ), + )(i) +} + +pub fn part1(input: &[u8]) -> anyhow::Result { + let mut game_it = iterator(input, parse_game); + + let total: u32 = game_it + .into_iter() + .filter_map(|(id, colors)| { + if colors[0] <= 12 && colors[1] <= 13 && colors[2] <= 14 { + Some(u32::from(id)) + } else { + None + } + }) + .sum(); + + game_it.finish().map_err(|e| match e { + nom::Err::Incomplete(_) => anyhow::anyhow!("unreachable"), + nom::Err::Failure(e) | nom::Err::Error(e) => convert_nom_error(e), + })?; + + Ok(total.to_string()) } pub fn part2(_input: &[u8]) -> anyhow::Result { diff --git a/2023/src/samples/02.txt b/2023/src/samples/02.txt new file mode 100644 index 0000000..295c36d --- /dev/null +++ b/2023/src/samples/02.txt @@ -0,0 +1,5 @@ +Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green +Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue +Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red +Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red +Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green