day02 solved
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -23,6 +23,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"itertools 0.14.0",
|
||||||
"test-log",
|
"test-log",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
@@ -180,7 +181,7 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"criterion-plot",
|
"criterion-plot",
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
"itertools",
|
"itertools 0.10.5",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"oorandom",
|
"oorandom",
|
||||||
@@ -201,7 +202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cast",
|
"cast",
|
||||||
"itertools",
|
"itertools 0.10.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -300,6 +301,15 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
|
itertools = "0.14"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-error = "0.2"
|
tracing-error = "0.2"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ To run the tests against included test input files: `RUST_LOG=debug cargo test -
|
|||||||
|
|
||||||
Because this is over-engineered, I've included benchmarks for each day's solution. Because, why not?
|
Because this is over-engineered, I've included benchmarks for each day's solution. Because, why not?
|
||||||
|
|
||||||
To run benchmarks: `cargo bench`.
|
To run benchmarks: `cargo bench`. Or a specific day and/or part: `cargo bench -- "day02 part1"`.
|
||||||
|
|
||||||
### Results
|
### Results
|
||||||
|
|
||||||
@@ -29,3 +29,4 @@ Timings are given as: [lower-bound **best-estimate** upper-bound]
|
|||||||
| Day | Part 1 | Part 2 |
|
| Day | Part 1 | Part 2 |
|
||||||
|-----|--------|--------|
|
|-----|--------|--------|
|
||||||
| 01 | [101.34 µs **101.95 µs** 102.61 µs] | [105.90 µs **106.40 µs** 106.95 µs] |
|
| 01 | [101.34 µs **101.95 µs** 102.61 µs] | [105.90 µs **106.40 µs** 106.95 µs] |
|
||||||
|
| 02 | [165.59 ms **166.60 ms** 167.65 ms] | [184.17 ms **185.25 ms** 186.42 ms] |
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use aoc::day01;
|
use aoc::day01;
|
||||||
|
use aoc::day02;
|
||||||
use criterion::{Criterion, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
|
|
||||||
fn day01_benchmark(c: &mut Criterion) {
|
fn day01_benchmark(c: &mut Criterion) {
|
||||||
@@ -6,5 +7,10 @@ fn day01_benchmark(c: &mut Criterion) {
|
|||||||
c.bench_function("day01 part2", |b| b.iter(|| day01::part2(day01::INPUT)));
|
c.bench_function("day01 part2", |b| b.iter(|| day01::part2(day01::INPUT)));
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, day01_benchmark);
|
fn day02_benchmark(c: &mut Criterion) {
|
||||||
|
c.bench_function("day02 part1", |b| b.iter(|| day02::part1(day02::INPUT)));
|
||||||
|
c.bench_function("day02 part2", |b| b.iter(|| day02::part2(day02::INPUT)));
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, day01_benchmark, day02_benchmark);
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
|||||||
1
src/day02/input/test1.txt
Normal file
1
src/day02/input/test1.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124
|
||||||
172
src/day02/mod.rs
Normal file
172
src/day02/mod.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
|
use color_eyre::{
|
||||||
|
Result,
|
||||||
|
eyre::{Error, OptionExt, eyre},
|
||||||
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use tracing::{debug, debug_span, info, info_span, instrument};
|
||||||
|
|
||||||
|
pub const INPUT: &str = include_str!("input/input.txt");
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ProductRange(std::ops::RangeInclusive<i64>);
|
||||||
|
|
||||||
|
impl FromStr for ProductRange {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut parts = s.split('-');
|
||||||
|
let start = parts
|
||||||
|
.next()
|
||||||
|
.ok_or(eyre!("Invalid product range: no start"))?
|
||||||
|
.parse::<i64>()?;
|
||||||
|
let end = parts
|
||||||
|
.next()
|
||||||
|
.ok_or(eyre!("Invalid product range: no end"))?
|
||||||
|
.parse::<i64>()?;
|
||||||
|
if parts.next().is_some() {
|
||||||
|
return Err(eyre!("Invalid product range: too many parts"));
|
||||||
|
}
|
||||||
|
Ok(ProductRange(start..=end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ProductRange {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}-{}", self.0.start(), self.0.end())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ProductRange {
|
||||||
|
type Item = i64;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.0.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_product_id(id: i64) -> Result<bool> {
|
||||||
|
let digits: Vec<u32> = id
|
||||||
|
.to_string()
|
||||||
|
.chars()
|
||||||
|
.map(|c| {
|
||||||
|
c.to_digit(10)
|
||||||
|
.ok_or_eyre("Invalid product id: contains a non-decimal digit")
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<u32>>>()?;
|
||||||
|
if digits.len() % 2 != 0 {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Ok(digits[..digits.len() / 2] != digits[digits.len() / 2..])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_product_id2(id: i64) -> Result<bool> {
|
||||||
|
let digits: Vec<u32> = id
|
||||||
|
.to_string()
|
||||||
|
.chars()
|
||||||
|
.map(|c| {
|
||||||
|
c.to_digit(10)
|
||||||
|
.ok_or_eyre("Invalid product id: contains a non-decimal digit")
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<u32>>>()?;
|
||||||
|
let mut chunk_size = digits.len() / 2;
|
||||||
|
loop {
|
||||||
|
if chunk_size == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if digits
|
||||||
|
.chunks(chunk_size)
|
||||||
|
.tuple_windows()
|
||||||
|
.all(|(a, b)| a == b)
|
||||||
|
{
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
chunk_size -= 1;
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProductRange {
|
||||||
|
fn invalid_ids(self) -> Result<Vec<i64>> {
|
||||||
|
let mut invalid_ids = vec![];
|
||||||
|
|
||||||
|
for id in self {
|
||||||
|
if !is_valid_product_id(id)? {
|
||||||
|
invalid_ids.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Invalid IDs: {:?}", &invalid_ids);
|
||||||
|
Ok(invalid_ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_ids2(self) -> Result<Vec<i64>> {
|
||||||
|
let mut invalid_ids = vec![];
|
||||||
|
|
||||||
|
for id in self {
|
||||||
|
if !is_valid_product_id2(id)? {
|
||||||
|
invalid_ids.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Invalid IDs: {:?}", &invalid_ids);
|
||||||
|
Ok(invalid_ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(input))]
|
||||||
|
pub fn part1(input: &str) -> Result<i64> {
|
||||||
|
let mut total_invalid = 0;
|
||||||
|
for range in input.trim().split(',') {
|
||||||
|
let _span = debug_span!("range", range = %range).entered();
|
||||||
|
let range: ProductRange = range.parse()?;
|
||||||
|
total_invalid += range.invalid_ids()?.iter().sum::<i64>();
|
||||||
|
}
|
||||||
|
Ok(total_invalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(input))]
|
||||||
|
pub fn part2(input: &str) -> Result<i64> {
|
||||||
|
let mut total_invalid = 0;
|
||||||
|
for range in input.trim().split(',') {
|
||||||
|
let _span = debug_span!("range", range = %range).entered();
|
||||||
|
let range: ProductRange = range.parse()?;
|
||||||
|
total_invalid += range.invalid_ids2()?.iter().sum::<i64>();
|
||||||
|
}
|
||||||
|
Ok(total_invalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solve() -> Result<()> {
|
||||||
|
info!("Day 2");
|
||||||
|
{
|
||||||
|
let _span = info_span!("day02").entered();
|
||||||
|
let p1 = part1(INPUT)?;
|
||||||
|
info!("Part 1: {}", p1);
|
||||||
|
let p2 = part2(INPUT)?;
|
||||||
|
info!("Part 2: {}", p2);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use test_log::test;
|
||||||
|
|
||||||
|
const TEST_INPUT1: &str = include_str!("input/test1.txt");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_part1() {
|
||||||
|
assert_eq!(part1(TEST_INPUT1).unwrap(), 1227775554);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_part2() {
|
||||||
|
assert_eq!(part2(TEST_INPUT1).unwrap(), 4174379265);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
pub mod day01;
|
pub mod day01;
|
||||||
|
pub mod day02;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod day01;
|
pub mod day01;
|
||||||
|
pub mod day02;
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
@@ -24,6 +25,7 @@ fn main() -> Result<()> {
|
|||||||
{
|
{
|
||||||
let _span = tracing::info_span!("aoc").entered();
|
let _span = tracing::info_span!("aoc").entered();
|
||||||
day01::solve()?;
|
day01::solve()?;
|
||||||
|
day02::solve()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user