diff --git a/input/year2023/day06.txt b/input/year2023/day06.txt new file mode 100644 index 0000000..c6865a5 --- /dev/null +++ b/input/year2023/day06.txt @@ -0,0 +1,2 @@ +Time: 51 92 68 90 +Distance: 222 2031 1126 1225 diff --git a/src/lib.rs b/src/lib.rs index 0c5d71d..511e631 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,4 +25,5 @@ pub mod year2023 { pub mod day03; pub mod day04; pub mod day05; + pub mod day06; } diff --git a/src/main.rs b/src/main.rs index f9369d1..73664da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,8 @@ struct Solution { macro_rules! solution { ($year:tt, $day:tt) => { Solution { - year: (&stringify!($year)).parse_u32(), - day: (&stringify!($day)).parse_u32(), + year: (&stringify!($year)).parse_unsigned::(), + day: (&stringify!($day)).parse_unsigned::(), input: include_str!(concat![ "../input/", stringify!($year), @@ -38,7 +38,7 @@ macro_rules! solution { fn main() { let (year, day) = match std::env::args().nth(1) { Some(arg) => { - let mut split = arg.split("::").map(|s| s.parse_u32()); + let mut split = arg.split("::").map(|s| s.parse_unsigned()); (split.next(), split.next()) } None => (None, None), @@ -100,5 +100,6 @@ fn year2023() -> Vec { solution!(year2023, day03), solution!(year2023, day04), solution!(year2023, day05), + solution!(year2023, day06), ] } diff --git a/src/util/parse.rs b/src/util/parse.rs index 24ccd0a..a9f891e 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -1,74 +1,133 @@ +use std::marker::PhantomData; use std::str::Bytes; -pub struct U32s<'a> { +pub trait IsSigned {} + +impl IsSigned for i8 {} +impl IsSigned for i16 {} +impl IsSigned for i32 {} +impl IsSigned for i64 {} +impl IsSigned for i128 {} +impl IsSigned for isize {} + +pub trait IsUnsigned {} + +impl IsUnsigned for u8 {} +impl IsUnsigned for u16 {} +impl IsUnsigned for u32 {} +impl IsUnsigned for u64 {} +impl IsUnsigned for u128 {} +impl IsUnsigned for usize {} + +pub struct Signed<'a, T = i64> +where + T: Add + Mul + TryFrom + TryFrom + IsSigned, +{ bytes: Bytes<'a>, + _phantom: PhantomData, } -impl<'a> Iterator for U32s<'a> { - type Item = u32; +impl<'a, T> Iterator for Signed<'a, T> +where + T: Add + Mul + TryFrom + TryFrom + IsSigned, +{ + type Item = T; fn next(&mut self) -> Option { - try_parse_u32(&mut self.bytes) + try_parse_signed::(&mut self.bytes) } } -pub struct I32s<'a> { +pub struct Unsigned<'a, T = u64> +where + T: Add + Mul + TryFrom + IsUnsigned, +{ bytes: Bytes<'a>, + _phantom: PhantomData, } -impl<'a> Iterator for I32s<'a> { - type Item = i32; +impl<'a, T> Iterator for Unsigned<'a, T> +where + T: Add + Mul + TryFrom + IsUnsigned, +{ + type Item = T; fn next(&mut self) -> Option { - try_parse_i32(&mut self.bytes) + try_parse_unsigned::(&mut self.bytes) } } pub trait ParseExt { - fn parse_u32(&self) -> u32; + fn parse_signed(&self) -> T + where + T: Add + Mul + TryFrom + TryFrom + IsSigned; - fn u32s(&self) -> U32s<'_>; + fn parse_unsigned(&self) -> T + where + T: Add + Mul + TryFrom + IsUnsigned; - fn parse_i32(&self) -> i32; + fn signed(&self) -> Signed<'_, T> + where + T: Add + Mul + TryFrom + TryFrom + IsSigned; - fn i32s(&self) -> I32s<'_>; + fn unsigned(&self) -> Unsigned<'_, T> + where + T: Add + Mul + TryFrom + IsUnsigned; } impl ParseExt for &str { - fn parse_u32(&self) -> u32 { - match try_parse_u32(&mut self.bytes()) { - Some(num) => num, - None => panic!("unable to parse u32: {self}"), - } - } - - fn u32s(&self) -> U32s<'_> { - U32s { + fn signed(&self) -> Signed<'_, T> + where + T: Add + Mul + TryFrom + TryFrom + IsSigned, + { + Signed { bytes: self.bytes(), + _phantom: PhantomData, } } - fn parse_i32(&self) -> i32 { - match try_parse_i32(&mut self.bytes()) { + fn parse_signed(&self) -> T + where + T: Add + Mul + TryFrom + TryFrom + IsSigned, + { + match try_parse_signed(&mut self.bytes()) { Some(num) => num, - None => panic!("unable to parse i32: {self}"), + None => panic!("unable to parse signed: {self}"), } } - fn i32s(&self) -> I32s<'_> { - I32s { + fn unsigned(&self) -> Unsigned<'_, T> + where + T: Add + Mul + TryFrom + IsUnsigned, + { + Unsigned { bytes: self.bytes(), + _phantom: PhantomData, + } + } + + fn parse_unsigned(&self) -> T + where + T: Add + Mul + TryFrom + IsUnsigned, + { + match try_parse_unsigned(&mut self.bytes()) { + Some(num) => num, + None => panic!("unable to parse signed: {self}"), } } } -fn try_parse_u32(bytes: &mut Bytes<'_>) -> Option { +use std::ops::{Add, Mul}; +fn try_parse_unsigned(bytes: &mut Bytes<'_>) -> Option +where + T: Add + Mul + TryFrom + IsUnsigned, +{ let mut n = loop { let byte = bytes.next()?; let digit = byte.wrapping_sub(b'0'); if digit < 10 { - break digit as u32; + break T::try_from(digit).unwrap_or_else(|_| unreachable!()); } }; @@ -79,29 +138,33 @@ fn try_parse_u32(bytes: &mut Bytes<'_>) -> Option { let digit = byte.wrapping_sub(b'0'); if digit < 10 { - n = 10 * n + digit as u32; + n = T::try_from(10).unwrap_or_else(|_| unreachable!()) * n + + T::try_from(digit).unwrap_or_else(|_| unreachable!()); } else { break Some(n); } } } -fn try_parse_i32(bytes: &mut Bytes<'_>) -> Option { - let mut fac = 1; +fn try_parse_signed(bytes: &mut Bytes<'_>) -> Option +where + T: Add + Mul + TryFrom + TryFrom + IsSigned, +{ + let mut fac = T::try_from(1i32).unwrap_or_else(|_| unreachable!()); let mut n = loop { let byte = bytes.next()?; if byte == b'-' { let byte = bytes.next()?; let digit = byte.wrapping_sub(b'0'); if digit < 10 { - fac = -1; - break digit as i32; + fac = T::try_from(-1i32).unwrap_or_else(|_| unreachable!()); + break T::try_from(digit).unwrap_or_else(|_| unreachable!()); } } let digit = byte.wrapping_sub(b'0'); if digit < 10 { - break digit as i32; + break T::try_from(digit).unwrap_or_else(|_| unreachable!()); } }; @@ -112,7 +175,8 @@ fn try_parse_i32(bytes: &mut Bytes<'_>) -> Option { let digit = byte.wrapping_sub(b'0'); if digit < 10 { - n = 10 * n + digit as i32; + n = T::try_from(10).unwrap_or_else(|_| unreachable!()) * n + + T::try_from(digit).unwrap_or_else(|_| unreachable!()); } else { break Some(n * fac); } diff --git a/src/year2016/day01.rs b/src/year2016/day01.rs index d8faf18..acc7465 100644 --- a/src/year2016/day01.rs +++ b/src/year2016/day01.rs @@ -4,7 +4,7 @@ use crate::util::{index::*, parse::ParseExt}; fn parse(input: &str) -> impl Iterator + '_ { let dirs = input.bytes().filter(u8::is_ascii_uppercase); - let steps: Vec<_> = input.u32s().collect(); + let steps: Vec<_> = input.unsigned().collect(); dirs.zip(steps) } diff --git a/src/year2016/day03.rs b/src/year2016/day03.rs index fb110e0..2710a29 100644 --- a/src/year2016/day03.rs +++ b/src/year2016/day03.rs @@ -9,7 +9,7 @@ pub fn part1(input: &str) -> impl std::fmt::Display { input .lines() .map(|s| { - s.u32s() + s.unsigned() // This is faster than `collect().try_into().expect("")` // presumably because of the allocation of the Vec // and the additional check for the Option for each line @@ -33,9 +33,9 @@ pub fn part2(input: &str) -> impl std::fmt::Display { let mut valid = 0; while let Some((l1, l2, l3)) = get3(&mut lines) { - let l1: Vec = l1.u32s().collect(); - let l2: Vec = l2.u32s().collect(); - let l3: Vec = l3.u32s().collect(); + let l1: Vec = l1.unsigned().collect(); + let l2: Vec = l2.unsigned().collect(); + let l3: Vec = l3.unsigned().collect(); for i in 0..2 { if valid_triangle(&[l1[i], l2[i], l3[i]]) { valid += 1; diff --git a/src/year2022/day01.rs b/src/year2022/day01.rs index 7828b9f..9bc1351 100644 --- a/src/year2022/day01.rs +++ b/src/year2022/day01.rs @@ -1,7 +1,7 @@ use crate::util::parse::ParseExt; fn calories(input: &str) -> impl Iterator + '_ { - input.split("\n\n").map(|set| set.u32s().sum()) + input.split("\n\n").map(|set| set.unsigned::().sum()) } pub fn part1(input: &str) -> impl std::fmt::Display { diff --git a/src/year2022/day04.rs b/src/year2022/day04.rs index fac1b70..0db08c6 100644 --- a/src/year2022/day04.rs +++ b/src/year2022/day04.rs @@ -4,7 +4,7 @@ fn solve(input: &str, filter: fn(u32, u32, u32, u32) -> bool) -> usize { input .lines() .filter_map(|line| { - let mut nums = line.u32s(); + let mut nums = line.unsigned::(); if (filter)(nums.next()?, nums.next()?, nums.next()?, nums.next()?) { Some(()) diff --git a/src/year2022/day05.rs b/src/year2022/day05.rs index 6289974..19e7831 100644 --- a/src/year2022/day05.rs +++ b/src/year2022/day05.rs @@ -2,7 +2,7 @@ use crate::util::parse::ParseExt; fn parse(input: &str) -> Option<(Vec>, impl Iterator + '_)> { let (board, moveset) = input.split_once("\n\n")?; - let size = board.lines().last()?.u32s().count(); + let size = board.lines().last()?.unsigned::().count(); let mut crates: Vec> = vec![vec![]; size + 1]; board.lines().for_each(|line| { @@ -22,7 +22,7 @@ fn parse(input: &str) -> Option<(Vec>, impl Iterator } let moves = moveset.lines().filter_map(|line| { - let mut nums = line.u32s(); + let mut nums = line.unsigned(); Some([nums.next()?, nums.next()?, nums.next()?]) }); diff --git a/src/year2023/day02.rs b/src/year2023/day02.rs index d594531..beb726e 100644 --- a/src/year2023/day02.rs +++ b/src/year2023/day02.rs @@ -18,7 +18,7 @@ pub fn part1(input: &str) -> impl std::fmt::Display { for b in draw.split(", ") { let (num, col) = b.split_once(' ')?; - let num = num.parse_u32(); + let num = num.parse_unsigned::(); match col { "red" => red += num, "green" => green += num, @@ -47,7 +47,7 @@ pub fn part2(input: &str) -> impl std::fmt::Display { for draw in cubes.split("; ") { for b in draw.split(", ") { let (num, col) = b.split_once(' ')?; - let num = num.parse_u32(); + let num = num.parse_unsigned::(); match col { "red" => { diff --git a/src/year2023/day04.rs b/src/year2023/day04.rs index 25fd01a..8cf44d6 100644 --- a/src/year2023/day04.rs +++ b/src/year2023/day04.rs @@ -6,8 +6,8 @@ pub fn part1(input: &str) -> impl std::fmt::Display { .map(|line| { let (_, rest) = line.split_once(':').unwrap(); let (winning, numbers) = rest.split_once('|').unwrap(); - let winning = winning.u32s().collect::>(); - let numbers = numbers.u32s().filter(|n| winning.contains(n)).count() as u32; + let winning = winning.unsigned::().collect::>(); + let numbers = numbers.unsigned::().filter(|n| winning.contains(n)).count() as u32; (numbers > 0).then(|| 2u32.pow(numbers - 1)).unwrap_or(0) }) .sum::() @@ -24,8 +24,8 @@ pub fn part2(input: &str) -> impl std::fmt::Display { .map(|(i, line)| { let (_, rest) = line.split_once(':').unwrap(); let (winning, numbers) = rest.split_once('|').unwrap(); - let winning = winning.u32s().collect::>(); - let numbers = numbers.u32s().filter(|n| winning.contains(n)).count(); + let winning = winning.unsigned::().collect::>(); + let numbers = numbers.unsigned::().filter(|n| winning.contains(n)).count(); let mul = mults[i]; mults .iter_mut() diff --git a/src/year2023/day05.rs b/src/year2023/day05.rs index 2e49b68..b3562d2 100644 --- a/src/year2023/day05.rs +++ b/src/year2023/day05.rs @@ -47,8 +47,7 @@ fn parse(input: &str) -> (Vec, Vec) { let seeds = chunks .next() .unwrap() - .u32s() - .map(|v| v as usize) + .unsigned::() .collect::>(); let maps = chunks @@ -60,12 +59,8 @@ fn parse(input: &str) -> (Vec, Vec) { if idx == 0 { return None; } - let mut nums = line.u32s(); - Some(( - nums.next()? as usize, - nums.next()? as usize, - nums.next()? as usize, - )) + let mut nums = line.unsigned::(); + Some((nums.next()?, nums.next()?, nums.next()?)) }) .collect(), }) @@ -110,6 +105,7 @@ pub fn part2(input: &str) -> impl std::fmt::Display { res[0] } +#[allow(dead_code)] static TEST_INPUT: &str = "seeds: 79 14 55 13 seed-to-soil map: diff --git a/src/year2023/day06.rs b/src/year2023/day06.rs new file mode 100644 index 0000000..154e02b --- /dev/null +++ b/src/year2023/day06.rs @@ -0,0 +1,49 @@ +use crate::util::parse::ParseExt; + +pub fn part1(input: &str) -> impl std::fmt::Display { + let mut lines = input.lines(); + let times = lines.next().unwrap(); + let dists = lines.next().unwrap(); + + times + .signed::() + .zip(dists.signed()) + .map(|(lim, dist)| { + (0..dist) + .filter_map(|d| (d * (lim - d) > dist).then_some(1)) + .sum::() + }) + .product::() +} + +pub fn part2(input: &str) -> impl std::fmt::Display { + let mut lines = input.lines(); + let time = lines + .next() + .unwrap() + .chars() + .filter(|a| a.is_ascii_digit()) + .collect::().as_str() + .parse_signed::(); + let dist = lines + .next() + .unwrap() + .chars() + .filter(|a| a.is_ascii_digit()) + .collect::().as_str() + .parse_signed::(); + + (0..time) + .filter_map(|d| (d * (time - d) > dist).then_some(1)) + .sum::() +} + +#[test] +fn test_part1() { + assert_eq!("", part1("").to_string()) +} + +#[test] +fn test_part2() { + assert_eq!("", part2("").to_string()) +}