From 788f6dd1965f2fc12557b99b3a23246420d87598 Mon Sep 17 00:00:00 2001 From: SOFe Date: Mon, 2 Dec 2024 22:26:48 +0800 Subject: [PATCH] d2 (one of the part 2 solutions is wrong) --- Cargo.lock | 14 ++++- Cargo.toml | 1 + input/d2.sample.input.txt | 6 ++ src/all.rs | 73 ++++++++++++---------- src/all/d1.rs | 3 +- src/all/d2.rs | 126 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- 7 files changed, 189 insertions(+), 37 deletions(-) create mode 100644 input/d2.sample.input.txt create mode 100644 src/all/d2.rs diff --git a/Cargo.lock b/Cargo.lock index 18bf130..3d97d01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,7 @@ dependencies = [ "bitvec", "clap", "criterion", + "itertools 0.13.0", "jq-rs", "paste", "reqwest", @@ -313,7 +314,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -334,7 +335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -890,6 +891,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index f6eb759..5279d1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ anyhow = "1.0.93" bitvec = "1.0.1" clap = { version = "4.5.21", features = ["derive"] } criterion = "0.5.1" +itertools = "0.13.0" jq-rs = { version = "0.4.1", features = ["bundled"] } paste = "1.0.15" reqwest = { version = "0.12.9", features = ["blocking"] } diff --git a/input/d2.sample.input.txt b/input/d2.sample.input.txt new file mode 100644 index 0000000..b49c10d --- /dev/null +++ b/input/d2.sample.input.txt @@ -0,0 +1,6 @@ +7 6 4 2 1 +1 2 7 8 9 +9 7 6 2 1 +1 3 2 4 5 +8 6 4 4 1 +1 3 6 7 9 diff --git a/src/all.rs b/src/all.rs index 8517ce1..0f0b1bc 100644 --- a/src/all.rs +++ b/src/all.rs @@ -4,7 +4,7 @@ use std::{env, fmt, fs, io}; use anyhow::Context; use clap::{Parser, ValueEnum}; -use criterion::{BatchSize, Criterion, Bencher}; +use criterion::{BatchSize, Bencher, Criterion}; macro_rules! main { ( @@ -26,9 +26,7 @@ macro_rules! main { $( $part => { let input = load_input(args.mode, $day)?; - - let output = main!(@impl variant, &input, $impls); - + let output: String = main!(@impl variant, &input, $impls); println!("{output}"); Ok(()) @@ -49,9 +47,12 @@ macro_rules! main { $($( { - let mut group = c.benchmark_group(concat!("Day ", $day, " Part ", $part)); - main!(@bench group, $day, $impls); - group.finish(); + let mut group_rust = c.benchmark_group(concat!("Day ", $day, " Part ", $part, " Rust")); + main!(@bench group_rust, $day, $impls, false); + group_rust.finish(); + let mut group_jq = c.benchmark_group(concat!("Day ", $day, " Part ", $part, " JQ")); + main!(@bench group_jq, $day, $impls, true); + group_jq.finish(); } )*)* } @@ -66,13 +67,15 @@ macro_rules! main { }; (@bench $group:ident, $day:literal, { $($name:literal => $fn:expr,)* - }) => { + }, $is_jq:literal) => { $( { let mut f = try_unwrap(move || anyhow::Ok($fn)); - $group.bench_function($name, move |b| { - call_benched(b, $day, &mut f); - }); + if $name.starts_with("jq") == $is_jq{ + $group.bench_function($name, move |b| { + call_benched(b, $day, &mut f); + }); + } } )* }; @@ -108,11 +111,7 @@ fn call(mut f: impl FnMut(In) -> Out, input: &str) fn call_benched(b: &mut Bencher, day: u32, f: impl FnMut(In) -> Out) { let input = load_input(Mode::Private, day).unwrap(); let parsed: In = Parse::parse(&input); - b.iter_batched( - || parsed.clone(), - f, - BatchSize::LargeInput, - ); + b.iter_batched(|| parsed.clone(), f, BatchSize::LargeInput); } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] @@ -130,22 +129,6 @@ pub struct Args { variant: String, } -main! { - day 1 { - part 1 { - "zip" => d1::p1_zip, - "jq" => jq!("d1.jq", "d1q1"), - } - part 2 { - "hash" => d1::p2_hash, - "sorted" => d1::p2_sorted, - "count" => d1::p2_count, - "bitvec" => d1::p2_bitvec, - "jq/hash" => jq!("d1.jq", "d1q2_hash"), - } - } -} - fn load_input(mode: Mode, day: u32) -> anyhow::Result { let dir = env::var("CARGO_MANIFEST_DIR").context("need cargo run")?; let path = PathBuf::from(dir).join("input").join(format!( @@ -196,3 +179,29 @@ impl Parse for JsonString { Self(simd_json::to_string(&value).unwrap()) } } + +main! { + day 1 { + part 1 { + "zip" => d1::p1_zip, + "jq" => jq!("d1.jq", "d1q1"), + } + part 2 { + "hash" => d1::p2_hash, + "sorted" => d1::p2_sorted, + "count" => d1::p2_count, + "bitvec" => d1::p2_bitvec, + "jq/hash" => jq!("d1.jq", "d1q2_hash"), + } + } + day 2 { + part 1 { + "windows" => d2::p1_windows, + "first-all" => d2::p1_first_all, + } + part 2 { + "brute" => d2::p2_brute_force, + "vec" => d2::p2_vec, + } + } +} diff --git a/src/all/d1.rs b/src/all/d1.rs index 5e57293..64c478e 100644 --- a/src/all/d1.rs +++ b/src/all/d1.rs @@ -1,4 +1,5 @@ -use std::{collections::HashMap, fmt, iter}; +use std::collections::HashMap; +use std::{fmt, iter}; use bitvec::vec::BitVec; diff --git a/src/all/d2.rs b/src/all/d2.rs new file mode 100644 index 0000000..d7dc1dc --- /dev/null +++ b/src/all/d2.rs @@ -0,0 +1,126 @@ +use std::fmt; + +use itertools::Itertools; + +use crate::Parse; + +#[derive(Clone)] +pub struct Input(Vec); + +#[derive(Clone)] +struct Line(Vec); + +impl Parse for Input { + fn parse(input: &str) -> Self { + Self( + input + .lines() + .map(|line| Line(line.split(' ').map(|s| s.parse::().unwrap()).collect())) + .collect(), + ) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Direction { + Increase, + Decrease, + Illegal, +} + +fn compare(left: u32, right: u32) -> Direction { + if (right + 1..=right + 3).contains(&left) { + Direction::Decrease + } else if (left + 1..=left + 3).contains(&right) { + Direction::Increase + } else { + Direction::Illegal + } +} + +fn is_safe_windows(levels: impl Iterator) -> bool { + let directions = levels.tuple_windows().map(|(left, right)| compare(left, right)); + directions.tuple_windows().all(|(left, right)| left != Direction::Illegal && left == right) +} + +fn is_safe_first_all(levels: impl Iterator) -> bool { + let mut directions = levels.tuple_windows().map(|(left, right)| compare(left, right)); + let first = directions.next().unwrap(); + first != Direction::Illegal && directions.all(|d| d == first) +} + +pub fn p1_windows(input: Input) -> impl fmt::Display { + fn is_safe(line: &Line) -> bool { is_safe_windows(line.0.iter().copied()) } + + input.0.iter().filter(|line| is_safe(line)).count() +} + +pub fn p1_first_all(input: Input) -> impl fmt::Display { + fn is_safe(line: &Line) -> bool { is_safe_first_all(line.0.iter().copied()) } + + input.0.iter().filter(|line| is_safe(line)).count() +} + +pub fn p2_brute_force(input: Input) -> impl fmt::Display { + fn is_safe(line: &Line) -> bool { + is_safe_first_all(line.0.iter().copied()) + || (0..line.0.len()).any(|skip| { + is_safe_first_all( + line.0[..skip].iter().copied().chain(line.0[skip + 1..].iter().copied()), + ) + }) + } + + input.0.iter().filter(|line| is_safe(line)).count() +} + +// TODO: this answer is wrong +pub fn p2_vec(input: Input) -> impl fmt::Display { + fn is_safe_skip(line: &Line, index: usize, dominant: Direction) -> bool { + if let (Some(prev_index), Some(&next_level)) = (index.checked_sub(1), line.0.get(index + 1)) + { + if compare(line.0[prev_index], next_level) != dominant { + return false; + } + } + + true + } + + fn is_safe(line: &Line) -> bool { + let directions: Vec<_> = + line.0.iter().tuple_windows().map(|(&left, &right)| compare(left, right)).collect(); + + let increase_count = directions.iter().filter(|&&d| d == Direction::Increase).count(); + let dominants = if increase_count * 2 > directions.len() { + &[Direction::Increase][..] + } else if increase_count * 2 == directions.len() { + &[Direction::Increase, Direction::Decrease][..] + } else { + &[Direction::Decrease][..] + }; + + dominants.iter().any(|&dominant| { + let mut violations = directions.iter().enumerate().filter(|(_, &d)| d != dominant); + if let Some((index, _)) = violations.next() { + // line[index] -> line[index+1] violation, + // attempt to skip either line[index] or line[index+1] + + if is_safe_skip(line, index, dominant) && is_safe_skip(line, index + 1, dominant) { + let mut next_violation = violations.next(); + if next_violation.is_some_and(|(next_index, _)| next_index == index + 1) { + next_violation = violations.next(); + } + + next_violation.is_none() + } else { + false + } + } else { + true + } + }) + } + + input.0.iter().filter(|line| is_safe(line)).count() +} diff --git a/src/lib.rs b/src/lib.rs index d3887b2..c711bc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ mod all; -pub use all::bench; -pub use all::run; use all::Parse; +pub use all::{bench, run};