diff --git a/src/bin/day6.rs b/src/bin/day6.rs new file mode 100644 index 0000000..e4d1403 --- /dev/null +++ b/src/bin/day6.rs @@ -0,0 +1,195 @@ +use std::time::Instant; + +use aoc_2025::{load, print_res}; +use bstr::{BStr, BString, ByteSlice}; +use color_eyre::eyre::ContextCompat; + +#[derive(Debug)] +pub enum Op { + Sum, + Product, +} + +impl Op { + pub fn parse(v: &[u8]) -> color_eyre::Result { + match v { + b"+" => Ok(Op::Sum), + b"*" => Ok(Op::Product), + _ => color_eyre::eyre::bail!("Invalid operation: `{}`", v.as_bstr()), + } + } +} + +type Parsed<'a> = Vec<&'a BStr>; + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result> { + Ok(input.lines().map(|a| a.as_bstr()).collect()) +} + +type Problem = (Op, Vec); + +fn parse_part1(input: Parsed) -> color_eyre::Result> { + let mut values = Vec::new(); + + for line in input { + if line.starts_with(b"+") || line.starts_with(b"*") { + return line + .fields() + .map(Op::parse) + .zip(values.into_iter()) + .map(|(r, v)| Ok((r?, v))) + .collect(); + } + + let line = str::from_utf8(line)?; + + if values.is_empty() { + values = line + .split_whitespace() + .map(|v| Ok::<_, color_eyre::Report>(vec![v.parse()?])) + .collect::>()?; + } else { + line.split_whitespace() + .zip(values.iter_mut()) + .try_for_each(|(v, col)| { + col.push(v.parse()?); + Ok::<_, color_eyre::Report>(()) + })?; + } + } + + color_eyre::eyre::bail!("Operations not found"); +} + +fn grand_total(problems: &[Problem]) -> u64 { + let mut grand_total = 0; + + for (op, values) in problems { + match op { + Op::Sum => grand_total += values.iter().sum::(), + Op::Product => grand_total += values.iter().product::(), + } + } + + grand_total +} + +fn parse_part2(input: Parsed) -> color_eyre::Result> { + let num_cols = input + .first() + .with_context(|| "input is empty")? + .fields() + .count(); + + let mut col_widths = vec![0; num_cols]; + + for line in &input { + line.fields() + .zip(col_widths.iter_mut()) + .for_each(|(v, max)| *max = (*max).max(v.len())); + } + + let col_width_sum = col_widths.iter().sum::() + col_widths.len() - 1; + + let mut iter = input.iter(); + let mut cols: Vec<_> = col_widths + .iter() + .map(|&w| vec![None::<(u64, bool)>; w]) + .collect(); + + loop { + let line = iter.next().with_context(|| "Unexpected end of input")?; + + color_eyre::eyre::ensure!( + line.len() == col_width_sum, + "Line `{line}` is of an incorrect length (expected {col_width_sum})" + ); + + if line[0] == b'*' || line[0] == b'+' { + return line + .fields() + .map(Op::parse) + .zip(cols) + .map(|(op, v)| { + let op = op?; + let values = v + .into_iter() + .map(|n| match n { + Some((v, _)) => Ok(v), + None => color_eyre::eyre::bail!("No number found"), + }) + .collect::>()?; + + Ok((op, values)) + }) + .collect(); + } + + let mut line: &[u8] = line; + + for (values, &width) in cols.iter_mut().zip(&col_widths) { + for (value, &digit) in values.iter_mut().zip(line.iter()) { + if digit == b' ' { + match value { + // Mark the number as finished + &mut Some((v, false)) => *value = Some((v, true)), + // Already finished, skip this number + Some((_, true)) => continue, + // Not yet started, skip this number + None => continue, + } + } else { + let digit = (digit - b'0') as u64; + + match value { + Some((_, true)) => { + color_eyre::eyre::bail!("Number has re-started after it has ended") + } + Some((v, _)) => *v = *v * 10 + digit, + None => *value = Some((digit, false)), + } + } + } + + if line.len() > width { + line = &line[width + 1..]; + } else { + line = &[]; + } + } + } +} + +#[inline(never)] +pub fn part1(input: Parsed) { + let grand_total = grand_total(&parse_part1(input).unwrap()); + print_res!("Grand total is: {grand_total}"); +} + +#[inline(never)] +pub fn part2(input: Parsed) { + let grand_total = grand_total(&parse_part2(input).unwrap()); + print_res!("Grand total is: {grand_total}"); +} + +pub fn main() -> color_eyre::Result<()> { + let context = load()?; + + let start = Instant::now(); + let parsed = parsing(&context.input)?; + let elapsed = humantime::format_duration(start.elapsed()); + + let start = Instant::now(); + if context.part == 1 { + part1(parsed); + } else { + part2(parsed); + } + let elapsed_part = humantime::format_duration(start.elapsed()); + + println!(" Parsing: {elapsed}"); + println!(" Solving: {elapsed_part}"); + + Ok(()) +}