Skip to content

Commit

Permalink
add more implementations of d1
Browse files Browse the repository at this point in the history
  • Loading branch information
SOF3 committed Dec 1, 2024
1 parent b56a437 commit b6debb1
Show file tree
Hide file tree
Showing 8 changed files with 790 additions and 121 deletions.
515 changes: 508 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,17 @@ edition = "2021"

[dependencies]
anyhow = "1.0.93"
bitvec = "1.0.1"
clap = { version = "4.5.21", features = ["derive"] }
criterion = "0.5.1"
jq-rs = { version = "0.4.1", features = ["bundled"] }
paste = "1.0.15"
reqwest = { version = "0.12.9", features = ["blocking"] }
simd-json = "0.14.3"

[patch.crates-io]
jq-src = { git = "https://github.com/SOF3/jq-src", rev = "refs/tags/jq-1.7.1" }

[[bench]]
name = "main"
harness = false
4 changes: 4 additions & 0 deletions benches/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
use criterion::*;

criterion_group!(benches, aoc2024::bench);
criterion_main!(benches);
195 changes: 195 additions & 0 deletions src/all.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use std::path::PathBuf;
use std::time::Instant;
use std::{env, fmt, fs, io};

use anyhow::Context;
use clap::{Parser, ValueEnum};
use criterion::{BatchSize, Criterion, Bencher};

macro_rules! main {
(
$(day $day:literal {
$(part $part:literal $impls:tt)*
})*
) => {
$(
paste::paste! {
mod [<d $day>];
}
)*

pub fn run(args: Args) -> anyhow::Result<()> {
let variant = args.variant.as_str();
match args.day {
$(
$day => match args.part {
$(
$part => {
let input = load_input(args.mode, $day)?;

let output = main!(@impl variant, &input, $impls);

println!("{output}");

Ok(())
},
)*
_ => anyhow::bail!("Unimplemented part"),
},
)*
_ => anyhow::bail!("Unimplemented day"),
}
}

pub fn bench(c: &mut Criterion) {
fn try_unwrap<R, E: fmt::Debug>(f: impl FnOnce() -> Result<R, E>) -> R {
f().unwrap()
}

$($(
{
let mut group = c.benchmark_group(concat!("Day ", $day, " Part ", $part));
main!(@bench group, $day, $impls);
group.finish();
}
)*)*
}
};
(@impl $variant:ident, $input:expr, {
$($name:literal => $fn:expr,)*
}) => {
match $variant {
$($name => call($fn, $input),)*
_ => anyhow::bail!("Unknown variant implementation"),
}
};
(@bench $group:ident, $day:literal, {
$($name:literal => $fn:expr,)*
}) => {
$(
{
let mut f = try_unwrap(move || anyhow::Ok($fn));
$group.bench_function($name, move |b| {
call_benched(b, $day, &mut f);
});
}
)*
};
}

macro_rules! jq {
($file:literal, $function:literal) => {{
let mut program =
jq_rs::compile(concat!(include_str!(concat!("all/", $file)), "\n", $function))
.map_err(|err| anyhow::anyhow!("compile {}: {err}", $file))?;
move |data: JsonString| -> String {
let output = program.run(data.0.as_str()).expect("jq program error");
output
}
}};
}

fn call<In: Parse, Out: fmt::Display>(mut f: impl FnMut(In) -> Out, input: &str) -> String {
let start_time = Instant::now();
let parsed = Parse::parse(input);
let duration = Instant::now() - start_time;
eprintln!("Parse time: {}ms", duration.as_secs_f32() * 1000.);

let start_time = Instant::now();
let output = f(parsed);
let duration = Instant::now() - start_time;
eprintln!("Execution time: {}ms", duration.as_secs_f32() * 1000.);

output.to_string()
}

fn call_benched<In: Parse, Out: fmt::Display>(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,
);
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Mode {
Sample,
Private,
}

#[derive(Parser)]
pub struct Args {
mode: Mode,
day: u32,
part: u32,
#[clap(default_value = "")]
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,
}
}
}

fn load_input(mode: Mode, day: u32) -> anyhow::Result<String> {
let dir = env::var("CARGO_MANIFEST_DIR").context("need cargo run")?;
let path = PathBuf::from(dir).join("input").join(format!(
"d{day}.{}.input.txt",
match mode {
Mode::Sample => "sample",
Mode::Private => "private",
}
));

if let Mode::Private = mode {
let exists =
fs::exists(&path).with_context(|| format!("test {} existence", path.display()))?;
if !exists {
eprintln!("Downloading day {day} input");

let session_cookie = env::var("AOC_SESSION")
.context("private file missing and AOC_SESSION env var missing")?;

let client = reqwest::blocking::Client::new();
let data = client
.get(format!("https://adventofcode.com/2024/day/{day}/input"))
.header("Cookie", format!("session={session_cookie}"))
.send()
.context("request aoc private input")?;
let input = io::read_to_string(data).context("read aoc private input")?;
fs::write(&path, &input).context("write aoc private input to cache")?;
}
}

fs::read_to_string(&path).with_context(|| format!("read file {}", path.display()))
}

pub trait Parse: Clone {
fn parse(input: &str) -> Self;
}

impl Parse for String {
fn parse(input: &str) -> Self { input.to_string() }
}

#[derive(Clone)]
struct JsonString(String);

impl Parse for JsonString {
fn parse(input: &str) -> Self {
let value = simd_json::json!(input);
Self(simd_json::to_string(&value).unwrap())
}
}
10 changes: 10 additions & 0 deletions src/all/d1.jq
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
def d1q1:
split("\n") |
map(
split(" ") |
map(tonumber?) |
select(length == 2) |
.[0] - .[1] | abs
) |
add
;
60 changes: 52 additions & 8 deletions src/d1.rs → src/all/d1.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use std::{fmt, iter};
use std::{collections::HashMap, fmt, iter};

struct Input {
use bitvec::vec::BitVec;

use crate::Parse;

#[derive(Clone)]
pub struct Input {
left: Vec<u32>,
right: Vec<u32>,
}

impl Input {
impl Parse for Input {
fn parse(input: &str) -> Self {
let (left, right): (Vec<_>, Vec<_>) = input
.lines()
Expand All @@ -19,9 +24,7 @@ impl Input {
}
}

pub fn p1(input: &str) -> impl fmt::Display {
let Input { mut left, mut right } = Input::parse(input);

pub fn p1_zip(Input { mut left, mut right }: Input) -> impl fmt::Display {
left.sort_unstable();
right.sort_unstable();

Expand Down Expand Up @@ -65,9 +68,16 @@ fn unique<T: Copy + Eq>(iter: impl IntoIterator<Item = T>) -> impl Iterator<Item
UniqueIterator { iter: iter.into_iter(), peek: None }
}

pub fn p2_sorted(input: &str) -> impl fmt::Display {
let Input { mut left, mut right } = Input::parse(input);
pub fn p2_hash(Input { left, right }: Input) -> impl fmt::Display {
let mut counts = HashMap::<u32, u32>::new();
for item in right {
*counts.entry(item).or_default() += 1;
}

left.into_iter().map(|item| item * counts.get(&item).copied().unwrap_or_default()).sum::<u32>()
}

pub fn p2_sorted(Input { mut left, mut right }: Input) -> impl fmt::Display {
left.sort_unstable();
right.sort_unstable();

Expand All @@ -93,3 +103,37 @@ pub fn p2_sorted(input: &str) -> impl fmt::Display {

output
}

pub fn p2_count(Input { left, right }: Input) -> impl fmt::Display {
fn collect_buckets(items: Vec<u32>) -> Vec<u32> {
let mut output = vec![0u32; 100000];
for item in items {
output[item as usize] += 1;
}
output
}

let left = collect_buckets(left);
let right = collect_buckets(right);

iter::zip(left, right).enumerate().map(|(i, (l, r))| (i as u32) * l * r).sum::<u32>()
}

pub fn p2_bitvec(Input { left, right }: Input) -> impl fmt::Display {
fn collect_buckets(items: Vec<u32>) -> (BitVec, Vec<u32>) {
let mut presence: BitVec = iter::repeat(false).take(100000).collect();
let mut output = vec![0u32; 100000];
for item in items {
presence.set(item as usize, true);
output[item as usize] += 1;
}
(presence, output)
}

let (left_presence, left) = collect_buckets(left);
let (right_presence, right) = collect_buckets(right);

let presence = left_presence & right_presence;

presence.iter_ones().map(|i| left[i] * right[i] * (i as u32)).sum::<u32>()
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod all;
pub use all::bench;
pub use all::run;
use all::Parse;
Loading

0 comments on commit b6debb1

Please sign in to comment.