diff --git a/2024/fetch-input.sh b/2024/fetch-input.sh new file mode 100755 index 0000000..591bf82 --- /dev/null +++ b/2024/fetch-input.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +AOC_YEAR="${AOC_YEAR:-2024}" +if [ "$#" -lt 1 ]; then + echo "Error: No day provided" + exit 1 +fi +DAY="$1" +f="$HOME/.cache/aoc$AOC_YEAR/$DAY.input" +if [ -f "$f" ]; then + echo "Skip: File already exists" + exit 0 +fi +url="https://adventofcode.com/$AOC_YEAR/day/$DAY/input" +cookie_file="$HOME/.advent-of-code-session-cookie" +if [[ ! -f "$cookie_file" ]]; then + echo "Cookie file not found: $cookie_file" + exit 1 +fi +cookie="$(cat "$cookie_file")" +mkdir -p "$(dirname "$f")" +if curl -s --fail-with-body -X GET "$url" -H "Cookie:$cookie" > "$f"; then + cat "$f" + echo "Downloaded $url to $f - contents have been output to this terminal as well" + exit 0 +else + echo "Error: curl failed (are you sure the input is available now?)" + rm -f "$f" + exit 1 +fi diff --git a/2024/readme.md b/2024/readme.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/2024/readme.md @@ -0,0 +1 @@ + diff --git a/2024/rust/.envrc b/2024/rust/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/2024/rust/.envrc @@ -0,0 +1 @@ +use flake diff --git a/2024/rust/.gitignore b/2024/rust/.gitignore new file mode 100644 index 0000000..1ddbf5b --- /dev/null +++ b/2024/rust/.gitignore @@ -0,0 +1,3 @@ +/target + +/.direnv diff --git a/2024/rust/Cargo.lock b/2024/rust/Cargo.lock new file mode 100644 index 0000000..b7ba074 --- /dev/null +++ b/2024/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aoc2024" +version = "1.0.0" diff --git a/2024/rust/Cargo.toml b/2024/rust/Cargo.toml new file mode 100644 index 0000000..2581d11 --- /dev/null +++ b/2024/rust/Cargo.toml @@ -0,0 +1,103 @@ +[package] +name = "aoc2024" +version = "1.0.0" + +[[bin]] +name = "day1" +path = "src/day1.rs" + +[[bin]] +name = "day2" +path = "src/day2.rs" + +[[bin]] +name = "day3" +path = "src/day3.rs" + +[[bin]] +name = "day4" +path = "src/day4.rs" + +[[bin]] +name = "day5" +path = "src/day5.rs" + +[[bin]] +name = "day6" +path = "src/day6.rs" + +[[bin]] +name = "day7" +path = "src/day7.rs" + +[[bin]] +name = "day8" +path = "src/day8.rs" + +[[bin]] +name = "day9" +path = "src/day9.rs" + +[[bin]] +name = "day10" +path = "src/day10.rs" + +[[bin]] +name = "day11" +path = "src/day11.rs" + +[[bin]] +name = "day12" +path = "src/day12.rs" + +[[bin]] +name = "day13" +path = "src/day13.rs" + +[[bin]] +name = "day14" +path = "src/day14.rs" + +[[bin]] +name = "day15" +path = "src/day15.rs" + +[[bin]] +name = "day16" +path = "src/day16.rs" + +[[bin]] +name = "day17" +path = "src/day17.rs" + +[[bin]] +name = "day18" +path = "src/day18.rs" + +[[bin]] +name = "day19" +path = "src/day19.rs" + +[[bin]] +name = "day20" +path = "src/day20.rs" + +[[bin]] +name = "day21" +path = "src/day21.rs" + +[[bin]] +name = "day22" +path = "src/day22.rs" + +[[bin]] +name = "day23" +path = "src/day23.rs" + +[[bin]] +name = "day24" +path = "src/day24.rs" + +[[bin]] +name = "day25" +path = "src/day25.rs" diff --git a/2024/rust/flake.lock b/2024/rust/flake.lock new file mode 100644 index 0000000..697ffb2 --- /dev/null +++ b/2024/rust/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1732937961, + "narHash": "sha256-B5pYT+IVaqcrfOekkwKvx/iToDnuQWzc2oyDxzzBDc4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4703b8d2c708e13a8cab03d865f90973536dcdf5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/2024/rust/flake.nix b/2024/rust/flake.nix new file mode 100644 index 0000000..c7acbb4 --- /dev/null +++ b/2024/rust/flake.nix @@ -0,0 +1,27 @@ +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + outputs = { + self, + nixpkgs, + }: let + supportedSystems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; + forEachSupportedSystem = f: + nixpkgs.lib.genAttrs supportedSystems (system: + f { + pkgs = import nixpkgs {inherit system;}; + }); + in { + devShells = forEachSupportedSystem ({pkgs}: { + default = pkgs.mkShell { + buildInputs = with pkgs; [ + cargo + rustc + rustfmt + rustPackages.clippy + rust-analyzer + curl + ]; + }; + }); + }; +} diff --git a/2024/rust/readme.md b/2024/rust/readme.md new file mode 100644 index 0000000..682aeef --- /dev/null +++ b/2024/rust/readme.md @@ -0,0 +1,43 @@ +# Rust Advent of Code 2024 Solutions + +## Competing + +I compete very lightly. I use [my `at` script][at] like `at 2022-12-02 && ../ +fetch-input.sh 2` to fetch input as soon as it's available and I use `watchexec +-e rs 'clear; cargo test --bin day2 && cargo run --bin day2'` to run my code +with tests as I edit them. + +## Running + +### Debug + +```bash +cargo run --bin day1 +``` + +### Tests + +```bash +cargo test --bin day1 +``` + +### Release Mode + +For speeeeeed! + +```bash +cargo build --release --bin day1 +time ./target/release/day1 +``` + +### Everything + +You can use this `fish` script to build all binaries in release mode and run/ +time them all: + +```fish +cargo build --release --bins +time for f in (fd 'day.' target/release/ --type executable --max-depth 1); echo $f; time $f; end +``` + +[at]: https://git.lyte.dev/lytedev/nix/src/branch/main/modules/home-manager/scripts/common/bin/at diff --git a/2024/rust/src/day1.rs b/2024/rust/src/day1.rs new file mode 100644 index 0000000..285059d --- /dev/null +++ b/2024/rust/src/day1.rs @@ -0,0 +1,146 @@ +use crate::prelude::*; + +mod prelude; + +fn main() { + Day1::show(day_input(1), day_input(1)) +} + +struct Day1 {} +impl Day1 { + fn calibration_value(line: &str) -> i128 { + println!("{line}"); + let bytes = line.as_bytes(); + let mut first_digit: Option = None; + let mut last_digit: Option = None; + for i in 0..bytes.len() { + let n = bytes.len() - 1 - i; + println!("{n} {i}"); + if first_digit.is_none() && (0x30..=0x39).contains(&bytes[i]) { + first_digit = Some(bytes[i] - 0x30); + println!("found first {first_digit:?}"); + } + if last_digit.is_none() && (0x30..=0x39).contains(&bytes[n]) { + last_digit = Some(bytes[n] - 0x30); + println!("found last {last_digit:?}"); + } + if first_digit.is_some() && last_digit.is_some() { + break; + } + } + println!("{:?} {:?}", first_digit, last_digit); + Into::::into(first_digit.or(last_digit).unwrap() * 10) + + Into::::into(last_digit.or(first_digit).unwrap()) + } + + fn num_name(s: &[u8]) -> Option { + for (i, w) in [ + "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", + ] + .map(|s| s.as_bytes()) + .iter() + .enumerate() + { + if s.starts_with(w) { + return Some(i as u8); + } + } + return None; + } + + fn num_name_end(s: &[u8]) -> Option { + for (i, w) in [ + "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", + ] + .map(|s| s.as_bytes()) + .iter() + .enumerate() + { + if s.ends_with(w) { + return Some(i as u8); + } + } + return None; + } + + fn calibration_value_words(line: &str) -> i128 { + println!("{line}"); + let bytes = line.as_bytes(); + let mut first_digit: Option = None; + let mut last_digit: Option = None; + for i in 0..bytes.len() { + let n = bytes.len() - 1 - i; + println!("NI: {n} {i}"); + if first_digit.is_none() { + if (0x30..=0x39).contains(&bytes[i]) { + first_digit = Some(bytes[i] - 0x30); + println!("found first {first_digit:?}"); + } else if let Some(n) = Self::num_name(&bytes[i..]) { + first_digit = Some(n); + println!("found first text {first_digit:?}"); + } + } + if last_digit.is_none() { + println!("{:?}", &bytes[..=n]); + if (0x30..=0x39).contains(&bytes[n]) { + last_digit = Some(bytes[n] - 0x30); + println!("found last {last_digit:?}"); + } else if let Some(n) = Self::num_name_end(&bytes[..=n]) { + last_digit = Some(n); + println!("found last text {last_digit:?}"); + } + } + if first_digit.is_some() && last_digit.is_some() { + break; + } + } + println!("{:?} {:?}", first_digit, last_digit); + Into::::into(first_digit.or(last_digit).unwrap() * 10) + + Into::::into(last_digit.or(first_digit).unwrap()) + } +} + +impl AoCSolution for Day1 { + type Input = String; + type Solution = i128; + + fn part1(input: Self::Input) -> Self::Solution { + input.lines().map(Self::calibration_value).sum() + } + + fn part2(input: Self::Input) -> Self::Solution { + input.lines().map(Self::calibration_value_words).sum() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + assert_eq!( + Day1::part1( + r#"1abc2 +pqr3stu8vwx +a1b2c3d4e5f +treb7uchet"# + .into() + ), + 142 + ); + assert_eq!( + Day1::part2( + r#"two1nine +eightwothree +abcone2threexyz +xtwone3four +4nineeightseven2 +zoneight234 +7pqrstsixteen"# + .into() + ), + 281 + ); + } +} diff --git a/2024/rust/src/prelude.rs b/2024/rust/src/prelude.rs new file mode 100644 index 0000000..b50b57e --- /dev/null +++ b/2024/rust/src/prelude.rs @@ -0,0 +1,68 @@ +pub use std::fs::File; +pub use std::io::Read; + +use std::path::{Path, PathBuf}; +use std::{env, io}; + +#[derive(Debug)] +enum InputFileError { + #![allow(dead_code)] + EnvVar(env::VarError), + Io(io::Error), +} + +/// Ensures that the input file exists +fn ensure_input_file(day: u8) -> Result { + let path = Path::new(&env::var("HOME").map_err(InputFileError::EnvVar)?) + .join(format!("./.cache/aoc2024/{0}.input", day)); + if !path.exists() { + eprintln!("Running input downloaded script with day arg {}...", day); + std::process::Command::new("sh") + .args(["../fetch-input.sh", &day.to_string()]) + .status() + .map_err(InputFileError::Io)?; + } + Ok(path) +} + +/// Useful when you simply want the day's input as a String. +pub fn day_input(day: u8) -> String { + let mut buffer = String::new(); + day_input_file(day) + .read_to_string(&mut buffer) + .expect(&format!("invalid utf8 input for day {}", day)); + buffer +} + +/// Useful when you want the day's input for streaming for maximum performance nonsense +pub fn day_input_file(day: u8) -> File { + let f = ensure_input_file(day).expect(&format!("Failed to ensure input for day {}", day)); + File::open(&f).expect(format!("Failed to open file {}", f.display()).as_str()) +} + +pub trait AoCSolution { + type Input; + type Solution: std::fmt::Debug + std::cmp::PartialEq + std::fmt::Display; + + // Are part1 and part2 solutions _always_ the same? + fn part1(input: Self::Input) -> Self::Solution; + fn part2(input: Self::Input) -> Self::Solution; + + fn show(i1: Self::Input, i2: Self::Input) { + println!("Part 1: {}", Self::part1(i1)); + println!("Part 2: {}", Self::part2(i2)); + } +} + +#[cfg(test)] +pub trait AoCSolutionTest: AoCSolution { + const PART1_TEST_INPUT: Self::Input; + const PART2_TEST_INPUT: Self::Input; + const PART1_TEST_RESULT: Self::Solution; + const PART2_TEST_RESULT: Self::Solution; + + fn assert_valid() { + assert_eq!(Self::part1(Self::PART1_TEST_INPUT), Self::PART1_TEST_RESULT); + assert_eq!(Self::part2(Self::PART2_TEST_INPUT), Self::PART2_TEST_RESULT); + } +} diff --git a/2024/rust/src/template.rs b/2024/rust/src/template.rs new file mode 100644 index 0000000..f6d6739 --- /dev/null +++ b/2024/rust/src/template.rs @@ -0,0 +1,32 @@ +mod prelude; +use crate::prelude::*; + +fn main() { + Day5::show(day_input(5), day_input(5)) +} + +struct Day5 {} +impl AoCSolution for Day5 { + type Input = String; + type Solution = usize; + + fn part1(input: Self::Input) -> Self::Solution { + 0 + } + + fn part2(input: Self::Input) -> Self::Solution { + 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let input = r#""#; + assert_eq!(Day2::part1(input.into()), 1); + assert_eq!(Day2::part2(input.into()), 0); + } +} diff --git a/readme.md b/readme.md index 005ea28..51a19db 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,7 @@ This repository is meant to contain all my [Advent of Code][aoc] code throughout the years I've participated (to whatever extent). +- [2024](./2024) - [2023](./2023) - [2022](./2022) - [2021](./2021)