Compare commits

..
6 Commits
Author SHA1 Message Date
Jan-Bulthuis 0ff3b02db9 feat: Day 4 2025-12-04 09:20:37 +01:00
Jan-Bulthuis e3affe980a chore: Switched to WinnowDay 2025-12-03 23:16:00 +01:00
Jan-Bulthuis d08eabf619 fix: Allow dead code again but disallow unused 2025-12-03 23:10:28 +01:00
Jan-Bulthuis ff3fe411ae feat: Day 3 2025-12-03 23:05:13 +01:00
Jan-Bulthuis 22fc3511b7 feat: Fancier traits 2025-12-03 23:04:50 +01:00
Jan-Bulthuis 3740d93f81 feat: Day 2 2025-12-02 07:27:01 +01:00
6 changed files with 457 additions and 45 deletions

No files matched your search

+16 -13
View File
@@ -1,4 +1,4 @@
use crate::AoC; use crate::{AoC, WinnowDay};
use winnow::{ use winnow::{
Parser, Result, Parser, Result,
ascii::{digit1, multispace0}, ascii::{digit1, multispace0},
@@ -11,11 +11,12 @@ enum Side {
Right(i64), Right(i64),
} }
const AOC: AoC<Parsed> = AoC::new(parse, part1, part2); fn day() -> impl WinnowDay {
AoC::new(parse as _, part1 as _, part2 as _)
}
fn parse(input: String) -> Parsed { fn parse(input: &mut &str) -> Result<Parsed> {
let mut input: &str = input.as_str(); repeat(1.., parse_line).parse_next(input)
repeat(1.., parse_line).parse_next(&mut input).unwrap()
} }
fn parse_line(input: &mut &str) -> Result<Side> { fn parse_line(input: &mut &str) -> Result<Side> {
@@ -34,7 +35,7 @@ fn parse_right(input: &mut &str) -> Result<Side> {
.parse_next(input) .parse_next(input)
} }
fn part1(input: &Parsed) -> String { fn part1(input: Parsed) -> String {
let mut res = 0; let mut res = 0;
let mut pos = 50i64; let mut pos = 50i64;
for change in input { for change in input {
@@ -49,12 +50,12 @@ fn part1(input: &Parsed) -> String {
format!("{res}") format!("{res}")
} }
fn part2(input: &Parsed) -> String { fn part2(input: Parsed) -> String {
let input = input let input = input
.iter() .iter()
.flat_map(|side| match side { .flat_map(|side| match side {
Side::Left(dx) => (0..*dx).map(|i| Side::Left(1)).collect::<Vec<Side>>(), Side::Left(dx) => (0..*dx).map(|_| Side::Left(1)).collect::<Vec<Side>>(),
Side::Right(dx) => (0..*dx).map(|i| Side::Right(1)).collect::<Vec<Side>>(), Side::Right(dx) => (0..*dx).map(|_| Side::Right(1)).collect::<Vec<Side>>(),
}) })
.collect::<Vec<Side>>(); .collect::<Vec<Side>>();
let mut res = 0; let mut res = 0;
@@ -73,11 +74,13 @@ fn part2(input: &Parsed) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::Day;
use super::*; use super::*;
#[test] #[test]
fn part1_test() { fn part1_test() {
AOC.assert1( day().assert1(
"L68 "L68
L30 L30
R48 R48
@@ -94,12 +97,12 @@ L82",
#[test] #[test]
fn part1_solve() { fn part1_solve() {
AOC.assert1(include_str!("../input/day1.txt"), "982"); day().assert1(include_str!("../input/day1.txt"), "982");
} }
#[test] #[test]
fn part2_test() { fn part2_test() {
AOC.assert2( day().assert2(
"L68 "L68
L30 L30
R48 R48
@@ -116,6 +119,6 @@ L82",
#[test] #[test]
fn part2_solve() { fn part2_solve() {
AOC.assert2(include_str!("../input/day1.txt"), "6106"); day().assert2(include_str!("../input/day1.txt"), "6106");
} }
} }
+87
View File
@@ -0,0 +1,87 @@
use crate::{AoC, WinnowDay};
use winnow::{
Parser, Result,
ascii::digit1,
combinator::{separated, separated_pair},
};
type Parsed = Vec<(u64, u64)>;
fn day() -> impl WinnowDay {
AoC::new(parse as _, part1 as _, part2 as _)
}
fn parse(input: &mut &str) -> Result<Parsed> {
separated(1.., parse_range, ",").parse_next(input)
}
fn parse_range(input: &mut &str) -> Result<(u64, u64)> {
separated_pair(digit1.parse_to(), "-", digit1.parse_to()).parse_next(input)
}
fn part1(input: Parsed) -> String {
let res = input
.iter()
.map(|(from, to)| {
(*from..=*to)
.filter(|i| {
let n = (i.ilog10() + 1) >> 1;
let p = 10u64.pow(n);
i / p == i % p
})
.sum::<u64>()
})
.sum::<u64>();
format!("{res}")
}
fn part2(input: Parsed) -> String {
let res = input
.iter()
.map(|(from, to)| {
(*from..=*to)
.filter(|i| {
let n = i.ilog10() + 1;
(1..=(n >> 1)).filter(|p| n % p == 0).any(|p| {
(1..(n / p))
.all(|s| i % 10u64.pow(p) == (i / 10u64.pow(p * s)) % 10u64.pow(p))
})
})
.sum::<u64>()
})
.sum::<u64>();
format!("{res}")
}
#[cfg(test)]
mod tests {
use crate::Day;
use super::*;
#[test]
fn part1_test() {
day().assert1(
"11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124",
"1227775554",
);
}
#[test]
fn part1_solve() {
day().assert1(include_str!("../input/day2.txt"), "12599655151");
}
#[test]
fn part2_test() {
day().assert2(
"11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124",
"4174379265",
);
}
#[test]
fn part2_solve() {
day().assert2(include_str!("../input/day2.txt"), "20942028255");
}
}
+102
View File
@@ -0,0 +1,102 @@
use std::iter::Sum;
use crate::{AoC, WinnowDay};
use winnow::{
Parser, Result,
ascii::newline,
combinator::{repeat, terminated},
error::ContextError,
token::one_of,
};
fn day() -> impl WinnowDay {
AoC::new(parse as _, part1 as _, part2 as _)
}
fn parse(input: &mut &str) -> Result<Vec<Vec<usize>>> {
repeat(1.., parse_line).parse_next(input)
}
fn parse_line(input: &mut &str) -> Result<Vec<usize>, ContextError> {
terminated(
repeat(
1..,
one_of(('0'..='9',)).map(|c: char| c.to_string().parse::<usize>().unwrap()),
),
newline,
)
.parse_next(input)
}
fn part1(input: Vec<Vec<usize>>) -> usize {
input.iter().map(joltage(2)).sum()
}
fn part2(input: Vec<Vec<usize>>) -> usize {
input.iter().map(joltage(12)).sum()
}
fn joltage<
'a,
B: IntoIterator<IntoIter = I> + Copy,
I: ExactSizeIterator<Item = &'a N>,
N: Sum<&'a N> + Into<usize> + From<usize> + Copy + Default + 'a,
>(
size: usize,
) -> impl Fn(B) -> N {
move |line| {
(line.into_iter().len() - size..line.into_iter().len())
.fold((0, 0), |(start, val), end| {
line.into_iter()
.enumerate()
.skip(start)
.take(end - start + 1)
.map(|(i, v)| (i + 1, (*v).into() + val * 10))
.reduce(|a, b| if a.1 >= b.1 { a } else { b })
.unwrap()
})
.1
.into()
}
}
#[cfg(test)]
mod tests {
use crate::Day;
use super::*;
#[test]
fn part1_test() {
day().assert1(
"987654321111111
811111111111119
234234234234278
818181911112111
",
"357",
);
}
#[test]
fn part1_solve() {
day().assert1(include_str!("../input/day3.txt"), "17343");
}
#[test]
fn part2_test() {
day().assert2(
"987654321111111
811111111111119
234234234234278
818181911112111
",
"3121910778619",
);
}
#[test]
fn part2_solve() {
day().assert2(include_str!("../input/day3.txt"), "172664333119298");
}
}
+155
View File
@@ -0,0 +1,155 @@
use winnow::{
Parser, Result,
ascii::{multispace0, newline},
combinator::{alt, preceded, repeat, terminated},
};
use crate::{AoC, SimpleDay, WinnowDay};
fn day() -> impl WinnowDay {
AoC::new(parse as _, part1 as _, part2 as _)
}
type Parsed = Vec<Vec<Cell>>;
#[derive(Debug, PartialEq, Eq)]
enum Cell {
Empty,
Paper,
}
impl Cell {
fn is_empty(&self) -> bool {
self == &Cell::Empty
}
fn is_paper(&self) -> bool {
self == &Cell::Paper
}
}
fn parse(input: &mut &str) -> Result<Parsed> {
repeat(1.., preceded(multispace0, parse_line)).parse_next(input)
}
fn parse_line(input: &mut &str) -> Result<Vec<Cell>> {
repeat(1.., parse_cell).parse_next(input)
}
fn parse_cell(input: &mut &str) -> Result<Cell> {
alt((".".map(|_| Cell::Empty), "@".map(|_| Cell::Paper))).parse_next(input)
}
fn part1(input: Parsed) -> usize {
let mut input = input;
remove_paper(&mut input)
}
fn remove_paper(input: &mut [Vec<Cell>]) -> usize {
let mut score = 0;
let mut access = Vec::new();
let h = input.len();
let w = input[0].len();
let directions = [
(-1, -1),
(0, -1),
(1, -1),
(1, 0),
(1, 1),
(0, 1),
(-1, 1),
(-1, 0),
];
for y in 0..h {
for x in 0..w {
match input[y][x] {
Cell::Empty => {}
Cell::Paper => {
let count = directions
.iter()
.filter(|dir| {
let nx = x as isize + dir.0;
let ny = y as isize + dir.1;
nx >= 0
&& nx < w as isize
&& ny >= 0
&& ny < h as isize
&& input[ny as usize][nx as usize].is_paper()
})
.count();
if count < 4 {
score += 1;
access.push((x, y));
}
}
}
}
}
access.iter().for_each(|(x, y)| {
input[*y][*x] = Cell::Empty;
});
score
}
fn part2(input: Parsed) -> usize {
let mut input = input;
let mut score = 0;
loop {
let removed = remove_paper(&mut input);
score += removed;
if removed == 0 {
return score;
}
}
}
#[cfg(test)]
mod tests {
use crate::Day;
use super::*;
#[test]
fn part1_test() {
day().assert1(
"..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.",
"13",
);
}
#[test]
fn part1_solve() {
day().assert1(include_str!("../input/day4.txt"), "1356");
}
#[test]
fn part2_test() {
day().assert2(
"..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.",
"43",
);
}
#[test]
fn part2_solve() {
day().assert2(include_str!("../input/day4.txt"), "8713");
}
}
+78 -15
View File
@@ -1,36 +1,99 @@
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused)] #![allow(unused)]
mod day1; use std::fmt::{Debug, Display};
struct AoC<T> { use winnow::Parser;
parse: fn(String) -> T,
part1: fn(&T) -> String, mod day1;
part2: fn(&T) -> String, mod day2;
mod day3;
mod day4;
trait Day {
fn assert1(&self, input: &str, output: &str);
fn assert2(&self, input: &str, output: &str);
} }
impl<T> AoC<T> { struct AoC<P, O1, O2> {
const fn new(parse: fn(String) -> T, part1: fn(&T) -> String, part2: fn(&T) -> String) -> Self { parse: P,
part1: O1,
part2: O2,
}
impl<P, O1, O2> AoC<P, O1, O2> {
fn new(parse: P, part1: O1, part2: O2) -> Self {
Self { Self {
parse, parse,
part1, part1,
part2, part2,
} }
} }
}
trait SimpleDay: Day {}
impl<P, O1, O2> SimpleDay for AoC<fn(String) -> P, fn(P) -> O1, fn(P) -> O2>
where
O1: Display,
O2: Display,
{
}
impl<P, O1, O2> Day for AoC<fn(String) -> P, fn(P) -> O1, fn(P) -> O2>
where
O1: Display,
O2: Display,
{
fn assert1(&self, input: &str, output: &str) { fn assert1(&self, input: &str, output: &str) {
let parsed = (self.parse)(input.into()); let parsed = (self.parse)(input.to_string());
assert_eq!((self.part1)(&parsed), output); assert_eq!(format!("{}", (self.part1)(parsed)), output);
} }
fn assert2(&self, input: &str, output: &str) { fn assert2(&self, input: &str, output: &str) {
let parsed = (self.parse)(input.into()); let parsed = (self.parse)(input.to_string());
assert_eq!((self.part2)(&parsed), output); assert_eq!(format!("{}", (self.part2)(parsed)), output);
}
} }
fn apply(&self, input: &str) { trait WinnowDay: Day {}
let parsed = (self.parse)(input.into());
println!("Part 1: {}", (self.part1)(&parsed)); impl<O, E, O1, O2> WinnowDay
println!("Part 2: {}", (self.part2)(&parsed)); for AoC<for<'a, 'b> fn(&'a mut &'b str) -> Result<O, E>, fn(O) -> O1, fn(O) -> O2>
where
E: Debug,
O1: Display,
O2: Display,
{
}
fn as_parser<O, E>(
func: for<'a, 'b> fn(&'a mut &'b str) -> Result<O, E>,
) -> impl for<'a> Parser<&'a str, O, E>
where
E: Debug,
{
func
}
impl<O, E, O1, O2> Day
for AoC<for<'a, 'b> fn(&'a mut &'b str) -> Result<O, E>, fn(O) -> O1, fn(O) -> O2>
where
E: Debug,
O1: Display,
O2: Display,
{
fn assert1(&self, input: &str, output: &str) {
let mut input = input;
let mut parser = as_parser(self.parse);
let parsed = parser.parse_next(&mut input).unwrap();
assert_eq!(format!("{}", (self.part1)(parsed)), output);
}
fn assert2(&self, input: &str, output: &str) {
let mut input = input;
let mut parser = as_parser(self.parse);
let parsed = parser.parse_next(&mut input).unwrap();
assert_eq!(format!("{}", (self.part2)(parsed)), output);
} }
} }
+19 -17
View File
@@ -1,46 +1,48 @@
use crate::AoC;
use winnow::{Parser, Result}; use winnow::{Parser, Result};
type Parsed = String; use crate::{AoC, SimpleDay, WinnowDay};
const AOC: AoC<Parsed> = AoC::new(parse, part1, part2); fn day() -> impl WinnowDay {
AoC::new(parse as _, part1 as _, part2 as _)
fn parse(input: String) -> Parsed {
let mut input: &str = input.as_str();
String::new()
} }
fn part1(input: &Parsed) -> String { type Parsed = ();
let res = 0;
format!("{res}") fn parse(input: &mut &str) -> Result<Parsed> {
"".map(|_| ()).parse_next(input)
} }
fn part2(input: &Parsed) -> String { fn part1(input: Parsed) -> usize {
let res = 0; 0
format!("{res}") }
fn part2(input: Parsed) -> usize {
0
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::Day;
use super::*; use super::*;
#[test] #[test]
fn part1_test() { fn part1_test() {
AOC.assert1("", ""); day().assert1("", "");
} }
#[test] #[test]
fn part1_solve() { fn part1_solve() {
AOC.assert1(include_str!("../input/day1.txt"), ""); day().assert1(include_str!("../input/day.txt"), "");
} }
// #[test] // #[test]
// fn part2_test() { // fn part2_test() {
// AOC.assert2("", ""); // day().assert2("", "");
// } // }
// #[test] // #[test]
// fn part2_solve() { // fn part2_solve() {
// AOC.assert2(include_str!("../input/day1.txt"), ""); // day().assert2(include_str!("../input/day.txt"), "");
// } // }
} }