Error handling & simpler parsing

This commit is contained in:
2023-12-04 17:56:12 +01:00
committed by Hannes Körber
parent b15b9eb121
commit 0ca0b4ecf5

View File

@@ -2,9 +2,11 @@ use std::cmp::min;
use nom::{ use nom::{
bytes::complete::tag, bytes::complete::tag,
character::complete::char, character::complete::{char, digit1},
combinator::map_res,
multi::{many1, separated_list1}, multi::{many1, separated_list1},
sequence::{preceded, separated_pair, terminated, tuple}, sequence::{preceded, separated_pair, terminated, tuple},
Finish, IResult,
}; };
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@@ -14,89 +16,40 @@ struct Card {
have_numbers: Vec<usize>, have_numbers: Vec<usize>,
} }
mod parser {
use nom::{
branch::alt,
bytes::complete::take_while1,
character::complete::{char, satisfy},
combinator::map,
multi::count,
sequence::preceded,
IResult,
};
pub fn double_digit_number(i: &str) -> IResult<&str, usize> {
let digit = satisfy(|c| c.is_ascii_digit());
let single_digit = preceded(char(' '), count(&digit, 1));
let double_digit = count(&digit, 2);
let digit = alt((single_digit, double_digit));
let mut digit = map(digit, |chars| {
chars
.into_iter()
.collect::<String>()
.parse::<usize>()
.unwrap()
});
digit(i)
}
pub fn number(i: &str) -> IResult<&str, usize> {
map(
take_while1::<_, &str, _>(|c| c.is_ascii_digit()),
|s: &str| s.parse::<usize>().unwrap(),
)(i)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_double_digit() {
assert_eq!(double_digit_number("23").unwrap(), ("", 23));
assert_eq!(double_digit_number("234").unwrap(), ("4", 23));
assert_eq!(double_digit_number(" 234").unwrap(), ("34", 2));
assert_eq!(double_digit_number(" 3").unwrap(), ("", 3));
assert_eq!(double_digit_number(" 3 ").unwrap(), (" ", 3));
assert!(double_digit_number(" 3").is_err());
}
#[test]
fn parse_number() {
assert_eq!(number("23").unwrap(), ("", 23));
assert_eq!(number("2").unwrap(), ("", 2));
assert_eq!(number("2x").unwrap(), ("x", 2));
assert!(number(" 3").is_err());
}
}
}
impl Card { impl Card {
fn parse(input: &str) -> Self { fn parse(input: &str) -> Result<Self, String> {
let card_prefix = tuple((tag("Card"), many1(char(' ')))); fn number(i: &str) -> IResult<&str, usize> {
map_res(digit1, str::parse)(i)
}
let winning_numbers = separated_list1(char(' '), parser::double_digit_number); let card_prefix = tuple((
let have_numbers = separated_list1(char(' '), parser::double_digit_number); tag("Card"),
many1(char::<&str, nom::error::Error<&str>>(' ')),
));
let numbers = separated_pair(winning_numbers, tag(" | "), have_numbers); let winning_numbers = separated_list1(many1(char(' ')), number);
let have_numbers = separated_list1(many1(char(' ')), number);
let card_info = terminated(preceded(card_prefix, parser::number), char(':')); let numbers = separated_pair(
winning_numbers,
tuple((many1(char(' ')), char('|'), many1(char(' ')))),
have_numbers,
);
let mut card = separated_pair(card_info, char(' '), numbers); let card_info = terminated(preceded(card_prefix, number), char(':'));
let (rest, (card_id, (winning_numbers, have_numbers))) = card(input).unwrap(); let mut card = separated_pair(card_info, many1(char(' ')), numbers);
let (rest, (card_id, (winning_numbers, have_numbers))) =
card(input).finish().map_err(|e| e.to_string())?;
assert!(rest.is_empty()); assert!(rest.is_empty());
Self { Ok(Self {
id: card_id, id: card_id,
winning_numbers, winning_numbers,
have_numbers, have_numbers,
} })
} }
fn matching_numbers(&self) -> Vec<usize> { fn matching_numbers(&self) -> Vec<usize> {
@@ -117,17 +70,16 @@ impl Card {
} }
} }
fn part1(input: &str) -> usize { fn part1(input: &str) -> Result<usize, String> {
input input
.lines() .lines()
.map(Card::parse) .map(Card::parse)
.map(|card| card.value()) .try_fold(0, |accum, card| Ok(accum + card?.value()))
.sum()
} }
fn part2(input: &str) -> usize { fn part2(input: &str) -> Result<usize, String> {
let cards: Vec<Card> = input.lines().map(Card::parse).collect(); let cards: Vec<Card> = input.lines().map(Card::parse).collect::<Result<_, _>>()?;
let mut card_count: Vec<usize> = std::iter::repeat(1).take(cards.len()).collect(); let mut card_count = vec![1; cards.len()];
for i in 0..cards.len() { for i in 0..cards.len() {
let matches = cards[i].matching_numbers().len(); let matches = cards[i].matching_numbers().len();
@@ -135,14 +87,16 @@ fn part2(input: &str) -> usize {
card_count[j] += card_count[i]; card_count[j] += card_count[i];
} }
} }
card_count.into_iter().sum() Ok(card_count.into_iter().sum())
} }
fn main() { fn main() -> Result<(), String> {
let input = include_str!("../input"); let input = include_str!("../input");
println!("Part 1 : {}", part1(input)); println!("Part 1 : {}", part1(input)?);
println!("Part 2 : {}", part2(input)); println!("Part 2 : {}", part2(input)?);
Ok(())
} }
#[cfg(test)] #[cfg(test)]
@@ -161,14 +115,14 @@ mod tests {
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"}; "};
assert_eq!(part1(input), 13); assert_eq!(part1(input).unwrap(), 13);
} }
#[test] #[test]
fn parse_card() { fn parse_card() {
let input = "Card 1: 41 48 83 6 17 | 83 86 6 31 17 9 48 53"; let input = "Card 1: 41 48 83 6 17 | 83 86 6 31 17 9 48 53";
assert_eq!( assert_eq!(
Card::parse(input), Card::parse(input).unwrap(),
Card { Card {
id: 1, id: 1,
winning_numbers: vec![41, 48, 83, 6, 17], winning_numbers: vec![41, 48, 83, 6, 17],
@@ -188,6 +142,6 @@ mod tests {
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"}; "};
assert_eq!(part2(input), 30); assert_eq!(part2(input).unwrap(), 30);
} }
} }