Add day 4

This commit is contained in:
2023-12-04 11:33:28 +01:00
committed by Hannes Körber
parent 2c429dad95
commit feaf58b509
4 changed files with 239 additions and 0 deletions

1
2023/day4/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target/

39
2023/day4/Cargo.lock generated Normal file
View File

@@ -0,0 +1,39 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "day4"
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/day4/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "day4"
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"

189
2023/day4/src/main.rs Normal file
View File

@@ -0,0 +1,189 @@
use std::cmp::min;
use nom::{
bytes::complete::tag,
character::complete::char,
multi::{many1, separated_list1},
sequence::{preceded, separated_pair, terminated, tuple},
};
#[derive(Debug, PartialEq, Eq)]
struct Card {
id: usize,
winning_numbers: Vec<usize>,
have_numbers: Vec<usize>,
}
mod parser {
use nom::{
branch::alt,
bytes::complete::take_while1,
character::complete::{char, digit1},
combinator::map,
multi::many_m_n,
sequence::preceded,
IResult,
};
pub fn double_digit_number(i: &str) -> IResult<&str, usize> {
let single_digit = preceded(char(' '), many_m_n(1, 1, digit1));
let double_digit = many_m_n(1, 1, digit1);
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(" 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 {
fn parse(input: &str) -> Self {
let card_prefix = tuple((tag("Card"), many1(char(' '))));
let winning_numbers = separated_list1(char(' '), parser::double_digit_number);
let have_numbers = separated_list1(char(' '), parser::double_digit_number);
let numbers = separated_pair(winning_numbers, tag(" | "), have_numbers);
let card_info = terminated(preceded(card_prefix, parser::number), char(':'));
let mut card = separated_pair(card_info, char(' '), numbers);
let (rest, (card_id, (winning_numbers, have_numbers))) = card(input).unwrap();
assert!(rest.is_empty());
Self {
id: card_id,
winning_numbers,
have_numbers,
}
}
fn matching_numbers(&self) -> Vec<usize> {
self.have_numbers
.iter()
.filter(|n| self.winning_numbers.contains(n))
.copied()
.collect()
}
fn value(&self) -> usize {
let matches = self.matching_numbers().len();
if matches == 0 {
0
} else {
2_usize.pow(matches as u32 - 1)
}
}
}
fn part1(input: &str) -> usize {
input
.lines()
.map(Card::parse)
.map(|card| card.value())
.sum()
}
fn part2(input: &str) -> usize {
let cards: Vec<Card> = input.lines().map(Card::parse).collect();
let mut card_count: Vec<usize> = std::iter::repeat(1).take(cards.len()).collect();
for i in 0..cards.len() {
let matches = cards[i].matching_numbers().len();
for j in (i + 1)..min(i + matches + 1, cards.len()) {
card_count[j] += card_count[i];
}
}
card_count.into_iter().sum()
}
fn main() {
let input = include_str!("../input");
println!("Part 1 : {}", part1(input));
println!("Part 2 : {}", part2(input));
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
#[test]
fn example_01() {
let input = indoc! {"
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"};
assert_eq!(part1(input), 13);
}
#[test]
fn parse_card() {
let input = "Card 1: 41 48 83 6 17 | 83 86 6 31 17 9 48 53";
assert_eq!(
Card::parse(input),
Card {
id: 1,
winning_numbers: vec![41, 48, 83, 6, 17],
have_numbers: vec![83, 86, 6, 31, 17, 9, 48, 53],
}
);
}
#[test]
fn example_02() {
let input = indoc! {"
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"};
assert_eq!(part2(input), 30);
}
}