Compare commits
4 commits
0863785c00
...
0948888663
Author | SHA1 | Date | |
---|---|---|---|
0948888663 | |||
0d3b3db401 | |||
dcb22a8a0c | |||
be518eca39 |
90
Cargo.lock
generated
90
Cargo.lock
generated
|
@ -9,6 +9,8 @@ dependencies = [
|
||||||
"aoc-runner",
|
"aoc-runner",
|
||||||
"aoc-runner-derive",
|
"aoc-runner-derive",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"num",
|
||||||
|
"yap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -40,6 +42,12 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -61,6 +69,82 @@ version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.32"
|
version = "1.0.32"
|
||||||
|
@ -129,3 +213,9 @@ name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yap"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60b9d7b5d31406903811da828b29fa5dfd183e37b7c4d4e2968f1fe3b9dde295"
|
||||||
|
|
|
@ -9,3 +9,5 @@ edition = "2021"
|
||||||
aoc-runner = "0.3.0"
|
aoc-runner = "0.3.0"
|
||||||
aoc-runner-derive = "0.3.0"
|
aoc-runner-derive = "0.3.0"
|
||||||
itertools = "0.10.1"
|
itertools = "0.10.1"
|
||||||
|
num = "0.4.0"
|
||||||
|
yap = "0.7.1"
|
||||||
|
|
|
@ -32,6 +32,17 @@ pub fn count_increases_tuple_windows(input: &[usize]) -> usize {
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[aoc(day1, part1, slice_windows)]
|
||||||
|
pub fn count_increases_slice_windows(input: &[usize]) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
for window in input.windows(2) {
|
||||||
|
if window[0] < window[1] {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
#[aoc(day1, part2)]
|
#[aoc(day1, part2)]
|
||||||
pub fn count_sliding_increases_indexed(input: &[usize]) -> usize {
|
pub fn count_sliding_increases_indexed(input: &[usize]) -> usize {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
@ -53,3 +64,14 @@ pub fn count_sliding_increases_tuple_windows(input: &[usize]) -> usize {
|
||||||
}
|
}
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[aoc(day1, part2, slice_windows)]
|
||||||
|
pub fn count_sliding_increases_slice_windows(input: &[usize]) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
for w in input.windows(4) {
|
||||||
|
if w[0] + w[1] + w[2] < w[1] + w[2] + w[3] {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
use aoc_runner_derive::{aoc, aoc_generator};
|
||||||
|
use yap::{IntoTokens, Tokens};
|
||||||
|
|
||||||
|
use crate::parsing::parse_number;
|
||||||
|
|
||||||
#[aoc_generator(day2)]
|
#[aoc_generator(day2)]
|
||||||
pub fn parse_movements(input: &str) -> Vec<Movement> {
|
pub fn parse_movements(input: &str) -> Vec<Movement> {
|
||||||
|
@ -12,35 +15,37 @@ pub fn parse_movements(input: &str) -> Vec<Movement> {
|
||||||
|
|
||||||
#[aoc(day2, part1)]
|
#[aoc(day2, part1)]
|
||||||
pub fn part1(input: &[Movement]) -> isize {
|
pub fn part1(input: &[Movement]) -> isize {
|
||||||
let mut state = NaiveState::default();
|
NaiveState::default().movements(input)
|
||||||
input.iter().for_each(|movement| state.movement(movement));
|
|
||||||
state.depth * state.horizontal_pos
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[aoc(day2, part2)]
|
#[aoc(day2, part2)]
|
||||||
pub fn part2(input: &[Movement]) -> isize {
|
pub fn part2(input: &[Movement]) -> isize {
|
||||||
let mut state = State::default();
|
CorrectState::default().movements(input)
|
||||||
input.iter().for_each(|movement| state.movement(movement));
|
|
||||||
state.depth * state.horizontal_pos
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub trait State {
|
||||||
|
fn movement(&mut self, movement: &Movement);
|
||||||
|
fn depth(&self) -> isize;
|
||||||
|
fn horizontal_pos(&self) -> isize;
|
||||||
|
fn result(&self) -> isize {
|
||||||
|
self.depth() * self.horizontal_pos()
|
||||||
|
}
|
||||||
|
fn movements(&mut self, moves: &[Movement]) -> isize {
|
||||||
|
for movement in moves {
|
||||||
|
self.movement(&movement);
|
||||||
|
}
|
||||||
|
self.result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CorrectState {
|
||||||
horizontal_pos: isize,
|
horizontal_pos: isize,
|
||||||
depth: isize,
|
depth: isize,
|
||||||
aim: isize,
|
aim: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for State {
|
impl State for CorrectState {
|
||||||
fn default() -> State {
|
|
||||||
State {
|
|
||||||
horizontal_pos: 0,
|
|
||||||
depth: 0,
|
|
||||||
aim: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
fn movement(&mut self, movement: &Movement) {
|
fn movement(&mut self, movement: &Movement) {
|
||||||
match movement {
|
match movement {
|
||||||
Movement::Forward(units) => {
|
Movement::Forward(units) => {
|
||||||
|
@ -51,23 +56,23 @@ impl State {
|
||||||
Movement::Up(units) => self.aim -= units,
|
Movement::Up(units) => self.aim -= units,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn depth(&self) -> isize {
|
||||||
|
self.depth
|
||||||
|
}
|
||||||
|
|
||||||
|
fn horizontal_pos(&self) -> isize {
|
||||||
|
self.horizontal_pos
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct NaiveState {
|
pub struct NaiveState {
|
||||||
horizontal_pos: isize,
|
horizontal_pos: isize,
|
||||||
depth: isize,
|
depth: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NaiveState {
|
impl State for NaiveState {
|
||||||
fn default() -> NaiveState {
|
|
||||||
NaiveState {
|
|
||||||
horizontal_pos: 0,
|
|
||||||
depth: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NaiveState {
|
|
||||||
fn movement(&mut self, movement: &Movement) {
|
fn movement(&mut self, movement: &Movement) {
|
||||||
match movement {
|
match movement {
|
||||||
Movement::Forward(units) => self.horizontal_pos += units,
|
Movement::Forward(units) => self.horizontal_pos += units,
|
||||||
|
@ -75,6 +80,14 @@ impl NaiveState {
|
||||||
Movement::Up(units) => self.depth -= units,
|
Movement::Up(units) => self.depth -= units,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn depth(&self) -> isize {
|
||||||
|
self.depth
|
||||||
|
}
|
||||||
|
|
||||||
|
fn horizontal_pos(&self) -> isize {
|
||||||
|
self.horizontal_pos
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Movement {
|
pub enum Movement {
|
||||||
|
@ -86,15 +99,12 @@ pub enum Movement {
|
||||||
impl FromStr for Movement {
|
impl FromStr for Movement {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if let Some(units) = s.strip_prefix("forward ") {
|
let mut tokens = s.into_tokens();
|
||||||
Ok(Self::Forward(units.parse().unwrap()))
|
yap::one_of!(ts from &mut tokens;
|
||||||
} else if let Some(units) = s.strip_prefix("down ") {
|
ts.tokens("forward ".chars()).then(|| parse_number(ts).map(|num| Movement::Forward(num))).flatten(),
|
||||||
Ok(Self::Down(units.parse().unwrap()))
|
ts.tokens("down ".chars()).then(|| parse_number(ts).map(|num| Movement::Down(num))).flatten(),
|
||||||
} else if let Some(units) = s.strip_prefix("up ") {
|
ts.tokens("up ".chars()).then(|| parse_number(ts).map(|num| Movement::Up(num))).flatten(),
|
||||||
Ok(Self::Up(units.parse().unwrap()))
|
).ok_or(())
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
195
src/day_04.rs
195
src/day_04.rs
|
@ -1,195 +0,0 @@
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Field {
|
|
||||||
number: usize,
|
|
||||||
marked: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BingoBoard {
|
|
||||||
fields: [[Field; 5]; 5],
|
|
||||||
finished: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for BingoBoard {
|
|
||||||
type Err = ();
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let fields: [[Field; 5]; 5] = s
|
|
||||||
.lines()
|
|
||||||
.map(|line| line.trim())
|
|
||||||
.filter(|line| line.len() != 0)
|
|
||||||
.map(|line| -> [Field; 5] {
|
|
||||||
let line: Vec<Field> = line
|
|
||||||
.split(' ')
|
|
||||||
.filter(|num| num.len() != 0)
|
|
||||||
.map(|field| {
|
|
||||||
let number = field.parse().unwrap();
|
|
||||||
Field {
|
|
||||||
number,
|
|
||||||
marked: false,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
line.try_into().unwrap()
|
|
||||||
})
|
|
||||||
.collect::<Vec<[Field; 5]>>()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
Ok(BingoBoard { fields, finished: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BingoBoard {
|
|
||||||
fn mark(&mut self, num: usize) -> Option<usize> {
|
|
||||||
if self.finished == true {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
for lines in self.fields.iter_mut() {
|
|
||||||
for field in lines.iter_mut() {
|
|
||||||
if field.number == num {
|
|
||||||
field.marked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(score) = self.bingo().map(|pre_score| pre_score * num) {
|
|
||||||
self.finished = true;
|
|
||||||
Some(score)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bingo(&self) -> Option<usize> {
|
|
||||||
let mut completed = false;
|
|
||||||
for i in 0..5 {
|
|
||||||
if (self.fields[i][0].marked
|
|
||||||
&& self.fields[i][1].marked
|
|
||||||
&& self.fields[i][2].marked
|
|
||||||
&& self.fields[i][3].marked
|
|
||||||
&& self.fields[i][4].marked)
|
|
||||||
|| (self.fields[0][i].marked
|
|
||||||
&& self.fields[1][i].marked
|
|
||||||
&& self.fields[2][i].marked
|
|
||||||
&& self.fields[3][i].marked
|
|
||||||
&& self.fields[4][i].marked)
|
|
||||||
{
|
|
||||||
completed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if completed {
|
|
||||||
Some(self.pre_score())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pre_score(&self) -> usize {
|
|
||||||
self.fields
|
|
||||||
.iter()
|
|
||||||
.flat_map(|lines| lines.iter())
|
|
||||||
.map(|field| if field.marked { 0 } else { field.number })
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BingoGame {
|
|
||||||
boards: Vec<BingoBoard>,
|
|
||||||
input_numbers: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BingoGame {
|
|
||||||
fn get_scores(mut self, max: Option<usize>) -> Vec<usize> {
|
|
||||||
let mut scores = Vec::new();
|
|
||||||
for num in self.input_numbers {
|
|
||||||
for board in &mut self.boards {
|
|
||||||
if let Some(score) = board.mark(num) {
|
|
||||||
scores.push(score);
|
|
||||||
if let Some(max) = max {
|
|
||||||
if scores.len() >= max {
|
|
||||||
return scores
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scores
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc_generator(day4)]
|
|
||||||
pub fn parse_input(input: &str) -> BingoGame {
|
|
||||||
let (input_numbers_str, boards_str) = input.split_once('\n').unwrap();
|
|
||||||
let input_numbers: Vec<usize> = input_numbers_str
|
|
||||||
.split(',')
|
|
||||||
.map(|num| num.parse().unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let boards: Vec<BingoBoard> = boards_str
|
|
||||||
.split_terminator("\n\n")
|
|
||||||
.map(|board| board.parse().unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
BingoGame {
|
|
||||||
boards,
|
|
||||||
input_numbers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day4, part1)]
|
|
||||||
pub fn part1(input: &BingoGame) -> usize {
|
|
||||||
let game = input.clone();
|
|
||||||
*game.get_scores(Some(1)).first().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[aoc(day4, part2)]
|
|
||||||
pub fn part2(input: &BingoGame) -> usize {
|
|
||||||
let game = input.clone();
|
|
||||||
*game.get_scores(None).last().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
const EXAMPLE_INPUT: &str =
|
|
||||||
"7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1
|
|
||||||
|
|
||||||
22 13 17 11 0
|
|
||||||
8 2 23 4 24
|
|
||||||
21 9 14 16 7
|
|
||||||
6 10 3 18 5
|
|
||||||
1 12 20 15 19
|
|
||||||
|
|
||||||
3 15 0 2 22
|
|
||||||
9 18 13 17 5
|
|
||||||
19 8 7 25 23
|
|
||||||
20 11 10 24 4
|
|
||||||
14 21 16 12 6
|
|
||||||
|
|
||||||
14 21 17 24 4
|
|
||||||
10 16 15 9 19
|
|
||||||
18 8 23 26 20
|
|
||||||
22 11 13 6 5
|
|
||||||
2 0 12 3 7
|
|
||||||
|
|
||||||
";
|
|
||||||
const RESULT_PART_1: usize = 4512;
|
|
||||||
const RESULT_PART_2: usize = 1924;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part1_example() {
|
|
||||||
let result = super::part1(&super::parse_input(EXAMPLE_INPUT));
|
|
||||||
assert_eq!(result, RESULT_PART_1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn part2_example() {
|
|
||||||
let result = super::part2(&super::parse_input(EXAMPLE_INPUT));
|
|
||||||
assert_eq!(result, RESULT_PART_2);
|
|
||||||
}
|
|
||||||
}
|
|
89
src/day_04/bingo.rs
Normal file
89
src/day_04/bingo.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Field {
|
||||||
|
pub number: usize,
|
||||||
|
pub marked: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Board {
|
||||||
|
pub fields: [[Field; 5]; 5],
|
||||||
|
pub finished: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Game {
|
||||||
|
pub boards: Vec<Board>,
|
||||||
|
pub input_numbers: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
pub fn mark(&mut self, num: usize) -> Option<usize> {
|
||||||
|
if self.finished == true {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
for lines in self.fields.iter_mut() {
|
||||||
|
for field in lines.iter_mut() {
|
||||||
|
if field.number == num {
|
||||||
|
field.marked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(score) = self.bingo().map(|pre_score| pre_score * num) {
|
||||||
|
self.finished = true;
|
||||||
|
Some(score)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bingo(&self) -> Option<usize> {
|
||||||
|
let mut completed = false;
|
||||||
|
for i in 0..5 {
|
||||||
|
if (self.fields[i][0].marked
|
||||||
|
&& self.fields[i][1].marked
|
||||||
|
&& self.fields[i][2].marked
|
||||||
|
&& self.fields[i][3].marked
|
||||||
|
&& self.fields[i][4].marked)
|
||||||
|
|| (self.fields[0][i].marked
|
||||||
|
&& self.fields[1][i].marked
|
||||||
|
&& self.fields[2][i].marked
|
||||||
|
&& self.fields[3][i].marked
|
||||||
|
&& self.fields[4][i].marked)
|
||||||
|
{
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if completed {
|
||||||
|
Some(self.pre_score())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_score(&self) -> usize {
|
||||||
|
self.fields
|
||||||
|
.iter()
|
||||||
|
.flat_map(|lines| lines.iter())
|
||||||
|
.map(|field| if field.marked { 0 } else { field.number })
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Game {
|
||||||
|
pub fn get_scores(mut self, max: Option<usize>) -> Vec<usize> {
|
||||||
|
let mut scores = Vec::new();
|
||||||
|
for num in self.input_numbers {
|
||||||
|
for board in &mut self.boards {
|
||||||
|
if let Some(score) = board.mark(num) {
|
||||||
|
scores.push(score);
|
||||||
|
if let Some(max) = max {
|
||||||
|
if scores.len() >= max {
|
||||||
|
return scores;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scores
|
||||||
|
}
|
||||||
|
}
|
72
src/day_04/mod.rs
Normal file
72
src/day_04/mod.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use aoc_runner_derive::{aoc, aoc_generator};
|
||||||
|
|
||||||
|
mod bingo;
|
||||||
|
mod parsing;
|
||||||
|
|
||||||
|
pub use bingo::{Board, Field, Game};
|
||||||
|
|
||||||
|
pub use parsing::parse_game;
|
||||||
|
|
||||||
|
#[aoc_generator(day4)]
|
||||||
|
pub fn parse_input(input: &str) -> Game {
|
||||||
|
parse_game(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[aoc(day4, part1)]
|
||||||
|
pub fn part1(input: &Game) -> usize {
|
||||||
|
let game = input.clone();
|
||||||
|
*game.get_scores(Some(1)).first().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[aoc(day4, part2)]
|
||||||
|
pub fn part2(input: &Game) -> usize {
|
||||||
|
let game = input.clone();
|
||||||
|
*game.get_scores(None).last().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
const EXAMPLE_INPUT: &str =
|
||||||
|
"7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1
|
||||||
|
|
||||||
|
22 13 17 11 0
|
||||||
|
8 2 23 4 24
|
||||||
|
21 9 14 16 7
|
||||||
|
6 10 3 18 5
|
||||||
|
1 12 20 15 19
|
||||||
|
|
||||||
|
3 15 0 2 22
|
||||||
|
9 18 13 17 5
|
||||||
|
19 8 7 25 23
|
||||||
|
20 11 10 24 4
|
||||||
|
14 21 16 12 6
|
||||||
|
|
||||||
|
14 21 17 24 4
|
||||||
|
10 16 15 9 19
|
||||||
|
18 8 23 26 20
|
||||||
|
22 11 13 6 5
|
||||||
|
2 0 12 3 7
|
||||||
|
|
||||||
|
";
|
||||||
|
const RESULT_PART_1: usize = 4512;
|
||||||
|
const RESULT_PART_2: usize = 1924;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn part1_example() {
|
||||||
|
let result = super::part1(&super::parse_input(EXAMPLE_INPUT));
|
||||||
|
assert_eq!(result, RESULT_PART_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn part2_example() {
|
||||||
|
let result = super::part2(&super::parse_input(EXAMPLE_INPUT));
|
||||||
|
assert_eq!(result, RESULT_PART_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_example() {
|
||||||
|
let game = super::parse_game(EXAMPLE_INPUT);
|
||||||
|
assert!(!game.boards.is_empty());
|
||||||
|
assert!(!game.input_numbers.is_empty());
|
||||||
|
}
|
||||||
|
}
|
90
src/day_04/parsing.rs
Normal file
90
src/day_04/parsing.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
use super::bingo::{Board, Field, Game};
|
||||||
|
use crate::parsing::{newline, parse_number, space};
|
||||||
|
use yap::{IntoTokens, Tokens};
|
||||||
|
|
||||||
|
pub fn parse_field(tokens: &mut impl Tokens<Item = char>) -> Option<Field> {
|
||||||
|
parse_number(tokens).map(|number| Field {
|
||||||
|
number,
|
||||||
|
marked: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_field_line(tokens: &mut impl Tokens<Item = char>) -> Option<[Field; 5]> {
|
||||||
|
tokens
|
||||||
|
.sep_by(|t| parse_field(t), |t| space(t))
|
||||||
|
.collect::<Vec<Field>>()
|
||||||
|
.try_into()
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_field_line_array(tokens: &mut impl Tokens<Item = char>) -> Option<[[Field; 5]; 5]> {
|
||||||
|
tokens
|
||||||
|
.sep_by(|t| parse_field_line(t), |t| newline(t))
|
||||||
|
.collect::<Vec<[Field; 5]>>()
|
||||||
|
.try_into()
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_board(tokens: &mut impl Tokens<Item = char>) -> Option<Board> {
|
||||||
|
parse_field_line_array(tokens).map(|fields| Board {
|
||||||
|
fields,
|
||||||
|
finished: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_boards(tokens: &mut impl Tokens<Item = char>) -> Vec<Board> {
|
||||||
|
tokens
|
||||||
|
.sep_by(|t| parse_board(t), |t| newline(t) && newline(t))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_input_numbers(tokens: &mut impl Tokens<Item = char>) -> Vec<usize> {
|
||||||
|
tokens
|
||||||
|
.sep_by(|t| parse_number(t), |t| t.token(','))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_game(input: &str) -> Game {
|
||||||
|
let mut tokens = input.into_tokens();
|
||||||
|
let input_numbers = parse_input_numbers(&mut tokens);
|
||||||
|
tokens.skip_many(newline);
|
||||||
|
let boards = parse_boards(&mut tokens);
|
||||||
|
Game {
|
||||||
|
input_numbers,
|
||||||
|
boards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use yap::IntoTokens;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_input_numbers() {
|
||||||
|
assert_eq!(
|
||||||
|
vec![1, 2, 3],
|
||||||
|
super::parse_input_numbers(&mut "1,2,3".into_tokens())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_field_line() {
|
||||||
|
if let Some(line) = super::parse_field_line(&mut " 3 15 0 2 22".into_tokens()) {
|
||||||
|
assert_eq!(3, line[0].number);
|
||||||
|
assert_eq!(15, line[1].number);
|
||||||
|
assert_eq!(0, line[2].number);
|
||||||
|
assert_eq!(2, line[3].number);
|
||||||
|
assert_eq!(22, line[4].number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_board() {
|
||||||
|
const BOARD: &str = " 3 15 0 2 22
|
||||||
|
9 18 13 17 5
|
||||||
|
19 8 7 25 23
|
||||||
|
20 11 10 24 4
|
||||||
|
14 21 16 12 6";
|
||||||
|
assert!(super::parse_board(&mut BOARD.into_tokens()).is_some());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
use aoc_runner_derive::aoc_lib;
|
use aoc_runner_derive::aoc_lib;
|
||||||
|
|
||||||
|
pub(crate) mod parsing;
|
||||||
|
|
||||||
pub mod day_01;
|
pub mod day_01;
|
||||||
pub mod day_02;
|
pub mod day_02;
|
||||||
pub mod day_03;
|
pub mod day_03;
|
||||||
|
|
23
src/parsing.rs
Normal file
23
src/parsing.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use num::Num;
|
||||||
|
use yap::{IntoTokens, Tokens};
|
||||||
|
pub fn space(tokens: &mut impl Tokens<Item = char>) -> bool {
|
||||||
|
tokens.token(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newline(tokens: &mut impl Tokens<Item = char>) -> bool {
|
||||||
|
tokens.tokens("\r\n".into_tokens()) || tokens.token('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_number<T: Num>(tokens: &mut impl Tokens<Item = char>) -> Option<T> {
|
||||||
|
parse_number_with_radix(tokens, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_number_with_radix<T: Num>(tokens: &mut impl Tokens<Item = char>, base: u32) -> Option<T> {
|
||||||
|
tokens.skip_tokens_while(|t| *t == ' ');
|
||||||
|
let digits: String = tokens.tokens_while(|c| c.is_digit(base)).collect();
|
||||||
|
if digits.len() > 0 {
|
||||||
|
T::from_str_radix(&digits, base).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue