commit 2cfee8a4485a84bb428278ae53eebc59e4c365b9 Author: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sat Dec 2 18:51:05 2023 +0100 day1 diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9dd4081 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +input*.txt diff --git a/2023/day1/.gitignore b/2023/day1/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/2023/day1/.gitignore @@ -0,0 +1 @@ +/target diff --git a/2023/day1/Cargo.lock b/2023/day1/Cargo.lock new file mode 100644 index 0000000..ae5bfe8 --- /dev/null +++ b/2023/day1/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day1" +version = "0.1.0" diff --git a/2023/day1/Cargo.toml b/2023/day1/Cargo.toml new file mode 100644 index 0000000..afab261 --- /dev/null +++ b/2023/day1/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "day1" +version = "0.1.0" +edition = "2021" + +[profile.release] +debug = 1 + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/2023/day1/README.md b/2023/day1/README.md new file mode 100644 index 0000000..d83d8ce --- /dev/null +++ b/2023/day1/README.md @@ -0,0 +1,12 @@ +# day 1 + +benchmarks: + +Ensure that `input.txt` contains many, many copies of the actual input, the actual input is way too small. + +`cargo build --release && hyperfine 'target/release/day1 naive' 'target/release/day1 zero_alloc' 'target/release/day1 branchless'` +``` + target/release/day1 branchless ran + 1.52 ± 0.06 times faster than target/release/day1 zero_alloc + 7.74 ± 0.27 times faster than target/release/day1 naive +``` diff --git a/2023/day1/src/branchless.rs b/2023/day1/src/branchless.rs new file mode 100644 index 0000000..e280e5e --- /dev/null +++ b/2023/day1/src/branchless.rs @@ -0,0 +1,76 @@ +pub unsafe fn part2(input: &str) { + let sum = input + .lines() + .map(|line| { + let bytes = line.as_bytes(); + + let mut digits = [0_u8; 128]; + + assert!(bytes.len() <= digits.len()); + + let mut i = 0; + + while i < bytes.len() { + let mut insert = |b| digits[i] |= b; + + // in memory: + // o n e X X X X X + // in the integer bytes: + // X X X X X e n o + let block = bytes.as_ptr().add(i).cast::().read_unaligned().to_le(); + + let one = (block & ((1 << (8 * 1)) - 1)) as u8; + let three = block & ((1 << (8 * 3)) - 1); + let four = block & ((1 << (8 * 4)) - 1); + let five = block & ((1 << (8 * 5)) - 1); + + const fn gorble(s: &[u8]) -> u64 { + let mut bytes = [0; 8]; + let mut i = 0; + while i < s.len() { + bytes[7 - i] = s[i]; + i += 1; + } + // like: u64::from_be_bytes([0, 0, 0, b't', b'h', b'g', b'i', b'e']) + u64::from_be_bytes(bytes) + } + macro_rules! check { + ($const:ident $len:ident == $str:expr => $value:expr) => { + const $const: u64 = gorble($str); + insert(if $len == $const { $value } else { 0 }); + }; + } + + insert(if one >= b'0' && one <= b'9' { one } else { 0 }); + + check!(EIGHT five == b"eight" => b'8'); + check!(SEVEN five == b"seven" => b'7'); + check!(THREE five == b"three" => b'3'); + + check!(FIVE four == b"five" => b'5'); + check!(FOUR four == b"four" => b'4'); + check!(NINE four == b"nine" => b'9'); + + check!(SIX three == b"six" => b'6'); + check!(TWO three == b"two" => b'2'); + check!(ONE three == b"one" => b'1'); + + i += 1; + } + + let first = digits[..bytes.len()].iter().find(|&&d| d > b'0').unwrap(); + let last = digits[..bytes.len()] + .iter() + .rev() + .find(|&&d| d > b'0') + .unwrap(); + + let first = (first - b'0') as u64; + let last = (last - b'0') as u64; + + first * 10 + last + }) + .sum::(); + + println!("part2: {sum}"); +} diff --git a/2023/day1/src/main.rs b/2023/day1/src/main.rs new file mode 100644 index 0000000..0f988fe --- /dev/null +++ b/2023/day1/src/main.rs @@ -0,0 +1,49 @@ +use std::mem::MaybeUninit; + +mod branchless; +mod naive; +mod zero_alloc; + +fn main() { + let kind = std::env::args().nth(1).unwrap_or("naive".into()); + + let mut input = std::hint::black_box(include_str!("../input.txt")).to_owned(); + + input.reserve(5); + unsafe { + input + .as_mut_vec() + .spare_capacity_mut() + .fill(MaybeUninit::new(0)) + }; + + match kind.as_str() { + "part1" => part1(&input), + "naive" => naive::part2(&input), + "zero_alloc" => zero_alloc::part2(&input), + "branchless" => unsafe { branchless::part2(&input) }, + _ => { + eprintln!("error: invalid mode, must be part1,naive,zero_alloc,branchless"); + std::process::exit(1); + } + } +} + +fn part1(input: &str) { + let sum = input + .lines() + .map(|line| { + let mut chars = line.chars().filter(|c| c.is_ascii_digit()); + let first = chars.next().unwrap(); + let last = chars.next_back().unwrap_or(first); + + [first, last] + .into_iter() + .collect::() + .parse::() + .unwrap() + }) + .sum::(); + + println!("part1: {sum}"); +} diff --git a/2023/day1/src/naive.rs b/2023/day1/src/naive.rs new file mode 100644 index 0000000..c835fa4 --- /dev/null +++ b/2023/day1/src/naive.rs @@ -0,0 +1,28 @@ +pub fn part2(input: &str) { + let sum = input + .lines() + .map(|line| { + let line = line + .replace("one", "one1one") + .replace("two", "two2two") + .replace("three", "three3three") + .replace("four", "four4four") + .replace("five", "five5five") + .replace("six", "six6six") + .replace("seven", "seven7seven") + .replace("eight", "eight8eight") + .replace("nine", "nine9nine"); + let mut chars = line.chars().filter(|c| c.is_ascii_digit()); + let first = chars.next().unwrap(); + let last = chars.next_back().unwrap_or(first); + + [first, last] + .into_iter() + .collect::() + .parse::() + .unwrap() + }) + .sum::(); + + println!("part2: {sum}"); +} diff --git a/2023/day1/src/zero_alloc.rs b/2023/day1/src/zero_alloc.rs new file mode 100644 index 0000000..8a6b9c4 --- /dev/null +++ b/2023/day1/src/zero_alloc.rs @@ -0,0 +1,43 @@ +pub fn part2(input: &str) { + let sum = input + .lines() + .map(|line| { + let bytes = line.as_bytes(); + + let mut i = 0; + let mut first = None; + let mut last = b'_'; + + let mut insert = |byte| { + if first.is_none() { + first = Some(byte); + } + last = byte; + }; + + while i < bytes.len() { + match bytes[i] { + b @ b'0'..=b'9' => insert(b), + b'o' if bytes.get(i..(i + 3)) == Some(b"one") => insert(b'1'), + b't' if bytes.get(i..(i + 3)) == Some(b"two") => insert(b'2'), + b't' if bytes.get(i..(i + 5)) == Some(b"three") => insert(b'3'), + b'f' if bytes.get(i..(i + 4)) == Some(b"four") => insert(b'4'), + b'f' if bytes.get(i..(i + 4)) == Some(b"five") => insert(b'5'), + b's' if bytes.get(i..(i + 3)) == Some(b"six") => insert(b'6'), + b's' if bytes.get(i..(i + 5)) == Some(b"seven") => insert(b'7'), + b'e' if bytes.get(i..(i + 5)) == Some(b"eight") => insert(b'8'), + b'n' if bytes.get(i..(i + 4)) == Some(b"nine") => insert(b'9'), + _ => {} + } + i += 1; + } + + let first = (first.unwrap() - b'0') as u64; + let last = (last - b'0') as u64; + + first * 10 + last + }) + .sum::(); + + println!("part2: {sum}"); +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..1bc2eb8 --- /dev/null +++ b/shell.nix @@ -0,0 +1,11 @@ +{ pkgs ? import { } }: pkgs.mkShell { + buildInputs = with pkgs; [ + rustup + ]; + shellHook = '' + export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin + ''; + packages = (with pkgs; [ + shellcheck + ]); +}