Compare commits

..

1 commit

Author SHA1 Message Date
969d6d3977
WIP 2023-12-01 04:06:53 -06:00
33 changed files with 72 additions and 2398 deletions

25
2023/rust/Cargo.lock generated
View file

@ -5,28 +5,3 @@ version = 3
[[package]]
name = "aoc2023"
version = "1.0.0"
dependencies = [
"nom",
]
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]

View file

@ -103,4 +103,3 @@ name = "day25"
path = "src/day25.rs"
[dependencies]
nom = "7.1.3"

View file

@ -1,115 +1,102 @@
use crate::prelude::*;
use std::cell::OnceCell;
mod prelude;
const DIGITS = OnceCell::new();
fn main() {
Day1::show(day_input(1), day_input(1))
}
const ASCII_ZERO: u8 = 0x30;
const ASCII_NINE: u8 = 0x39;
let value: &String = digits.get_or_init(|| {
[
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
]
.map(str::as_bytes)});
assert_eq!(value, "Hello, World!");
assert!(cell.get().is_some());
const DIGITS: [&[u8]; 10] = ;
const DIGITS_REV: [&[u8]; 10] = [
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
]
.map(|s| s.chars().rev().collect::<String>().as_bytes());
type Solution = i64;
struct Day1 {}
impl Day1 {
fn calibration_value(line: &str) -> i128 {
println!("{line}");
let bytes = line.as_bytes();
let mut first_digit: Option<u8> = None;
let mut last_digit: Option<u8> = 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::<i128>::into(first_digit.or(last_digit).unwrap() * 10)
+ Into::<i128>::into(last_digit.or(first_digit).unwrap())
fn is_digit(b: &u8) -> bool {
(ASCII_ZERO..=ASCII_NINE).contains(b)
}
fn num_name(s: &[u8]) -> Option<u8> {
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 to_digit(b: &u8) -> Solution {
i64::from(b - ASCII_ZERO)
}
fn num_name_end(s: &[u8]) -> Option<u8> {
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(line_bytes: &[u8]) -> i64 {
let i1 = line_bytes.iter().position(Self::is_digit).unwrap();
let i2 = line_bytes[i1..]
.iter()
.rev()
.position(Self::is_digit)
.unwrap_or(i1);
Self::to_digit(&line_bytes[i1]) * 10 + Self::to_digit(&line_bytes[i2])
}
fn calibration_value_words(line: &str) -> i128 {
println!("{line}");
let bytes = line.as_bytes();
let mut first_digit: Option<u8> = None;
let mut last_digit: Option<u8> = 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:?}");
// part 2
fn num_name(s: &[u8], digits: &[&[u8]]) -> Option<i64> {
digits
.iter()
.position(|d| s.starts_with(d))
.map(|n| n as i64)
}
fn num_or_digit(s: &[u8], digits: &[&[u8]]) -> Option<Solution> {
if Self::is_digit(&s[0]) {
Some(Self::to_digit(&s[0]))
} else {
Self::num_name(s, digits)
}
}
fn calibration_value_words(line_bytes: &[u8]) -> i64 {
for i in 0..line_bytes.len() {
if let Some(first_digit) = Self::num_or_digit(&line_bytes[i..], &DIGITS) {
for j in (i..line_bytes.len()).rev() {
if let Some(last_digit) = Self::num_or_digit(&line_bytes[i..j], &DIGITS_REV) {
return first_digit * 10 + last_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;
return first_digit * 10 + first_digit;
}
}
println!("{:?} {:?}", first_digit, last_digit);
Into::<i128>::into(first_digit.or(last_digit).unwrap() * 10)
+ Into::<i128>::into(last_digit.or(first_digit).unwrap())
return 0;
}
}
impl AoCSolution for Day1 {
type Input = String;
type Solution = i128;
type Solution = i64;
fn part1(input: Self::Input) -> Self::Solution {
input.lines().map(Self::calibration_value).sum()
input
.lines()
.map(|l| Self::calibration_value(l.as_bytes()))
.sum()
}
fn part2(input: Self::Input) -> Self::Solution {
input.lines().map(Self::calibration_value_words).sum()
input
.lines()
.map(|l| Self::calibration_value_words(l.as_bytes()))
.sum()
}
}

View file

@ -1,108 +0,0 @@
mod prelude;
extern crate nom;
use crate::prelude::*;
use nom::{
branch::alt,
bytes::complete::{tag, take_while},
combinator::{map_res, value},
multi::separated_list0,
IResult,
};
use std::cmp::max;
fn main() {
Day2::show(day_input(2), day_input(2))
}
#[derive(Debug, PartialEq, Clone)]
enum Reveal {
Red(usize),
Green(usize),
Blue(usize),
}
#[derive(Debug, PartialEq)]
struct Game {
id: usize,
reveals: Vec<Reveal>,
}
// parser
fn int(input: &str) -> IResult<&str, usize> {
map_res(take_while(char::is_numeric), str::parse)(input)
}
fn reveal(input: &str) -> IResult<&str, Reveal> {
let (input, num) = int(input)?;
alt((
value(Reveal::Red(num), tag(" red")),
value(Reveal::Green(num), tag(" green")),
value(Reveal::Blue(num), tag(" blue")),
))(input)
}
fn game(input: &str) -> IResult<&str, Game> {
let (input, id) = int(tag("Game ")(input)?.0)?;
let (input, reveals) =
separated_list0(alt((tag(", "), tag("; "))), reveal)(tag(": ")(input)?.0)?;
Ok((input, Game { id, reveals }))
}
impl Game {
fn possible_power(&self) -> usize {
// TODO: probably the last place to cleanup nicely
let mut factors: [usize; 3] = [0, 0, 0];
for r in &self.reveals {
match r {
Reveal::Red(n) => factors[0] = max(*n, factors[0]),
Reveal::Green(n) => factors[1] = max(*n, factors[1]),
Reveal::Blue(n) => factors[2] = max(*n, factors[2]),
}
}
factors.iter().fold(1, |a, b| a * b)
}
}
struct Day2 {}
impl AoCSolution for Day2 {
type Input = String;
type Solution = usize;
fn part1(input: Self::Input) -> Self::Solution {
let valid = |r: &Reveal| match r {
Reveal::Red(n) => *n <= 12,
Reveal::Green(n) => *n <= 13,
Reveal::Blue(n) => *n <= 14,
};
input
.lines()
.map(|s| game(s).unwrap().1)
.filter(|g| g.reveals.iter().all(valid))
.map(|g| g.id)
.sum()
}
fn part2(input: Self::Input) -> Self::Solution {
input
.lines()
.map(|s| game(s).unwrap().1.possible_power())
.sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green"#;
assert_eq!(Day2::part1(input.into()), 8);
assert_eq!(Day2::part2(input.into()), 2286);
}
}

View file

@ -1,123 +0,0 @@
mod prelude;
use std::{collections::HashSet, iter::FromIterator};
use crate::prelude::*;
fn main() {
Day3::show(day_input(3), day_input(3))
}
struct Day3 {}
impl Day3 {
fn adjacent_nums(chargrid: &Vec<Vec<char>>, y: usize, x: usize) -> Vec<usize> {
let mut result = vec![];
let h = chargrid.len();
let w = chargrid[0].len();
let miny = (y - 1).clamp(0, h);
let minx = (x - 1).clamp(0, w);
let maxy = (y + 1).clamp(0, h);
let maxx = (x + 1).clamp(0, w);
let mut scanned: HashSet<(usize, usize)> = HashSet::new();
println!("symbol at {y} {x}: {}", chargrid[y][x]);
for y in miny..=maxy {
for x in minx..=maxx {
if scanned.contains(&(y, x)) {
continue;
}
if chargrid[y][x].is_digit(10) {
println!("{y} {x} {}", chargrid[y][x]);
scanned.insert((y, x));
let mut rx = x;
let mut lx = x;
for rrx in (x + 1)..w {
if !chargrid[y][rrx].is_digit(10) {
break;
}
rx = rrx;
scanned.insert((y, rx));
}
for llx in (0..=(x - 1)).rev() {
if !chargrid[y][llx].is_digit(10) {
break;
}
lx = llx;
scanned.insert((y, lx));
}
let ns = String::from_iter(&chargrid[y][lx..=rx]);
println!("ns: {}", ns);
if let Ok(n) = ns.parse() {
result.push(n);
println!("FOUND: {n}");
}
}
}
}
result
}
}
impl AoCSolution for Day3 {
type Input = String;
type Solution = usize;
fn part1(input: Self::Input) -> Self::Solution {
let mut result = 0;
let chargrid: Vec<Vec<char>> = input.lines().map(|s| s.chars().collect()).collect();
println!("{chargrid:?}");
for y in 0..chargrid.len() {
let line = &chargrid[y];
for x in 0..line.len() {
let c = line[x];
if !c.is_digit(10) && c != '.' {
result += Self::adjacent_nums(&chargrid, y, x).iter().sum::<usize>();
}
}
}
result
}
fn part2(input: Self::Input) -> Self::Solution {
let mut result = 0;
let chargrid: Vec<Vec<char>> = input.lines().map(|s| s.chars().collect()).collect();
println!("{chargrid:?}");
for y in 0..chargrid.len() {
let line = &chargrid[y];
for x in 0..line.len() {
let c = line[x];
if c == '*' {
let nums = Self::adjacent_nums(&chargrid, y, x);
println!("nums: {nums:?}");
if nums.len() == 2 {
result += nums[0] * nums[1];
}
}
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598.."#;
println!("input:\n{input}");
assert_eq!(Day3::part1(input.into()), 4361);
assert_eq!(Day3::part2(input.into()), 467835);
}
}

View file

@ -1,109 +0,0 @@
mod prelude;
extern crate nom;
use nom::{
branch::alt,
bytes::complete::{tag, take_while},
character::complete::space1,
combinator::{map_res, value},
multi::separated_list0,
sequence::tuple,
IResult,
};
use std::{
collections::{HashMap, HashSet},
iter::FromIterator,
};
use crate::prelude::*;
fn main() {
Day4::show(day_input(4), day_input(4))
}
#[derive(Debug)]
struct Card {
id: usize,
winning: HashSet<usize>,
has: Vec<usize>,
}
impl Card {
fn num_winning(&self) -> usize {
self.has.iter().filter(|n| self.winning.contains(n)).count() as usize
}
fn points(&self) -> usize {
let n = self.num_winning();
if n >= 1 {
2_u64.pow((n - 1) as u32) as usize
} else {
0
}
}
}
// parser
fn int(input: &str) -> IResult<&str, usize> {
map_res(take_while(char::is_numeric), str::parse)(input)
}
fn card(input: &str) -> IResult<&str, Card> {
let (input, id) = int(tuple((tag("Card"), space1))(input)?.0)?;
let (input, winning_list) = separated_list0(space1, int)(tuple((tag(":"), space1))(input)?.0)?;
let (input, has) = separated_list0(space1, int)(tuple((space1, tag("|"), space1))(input)?.0)?;
let winning = HashSet::from_iter(winning_list);
Ok((input, Card { id, winning, has }))
}
struct Day4 {}
impl Day4 {}
impl AoCSolution for Day4 {
type Input = String;
type Solution = usize;
fn part1(input: Self::Input) -> Self::Solution {
input.lines().map(|s| card(s).unwrap().1.points()).sum()
}
fn part2(input: Self::Input) -> Self::Solution {
let mut result = 0;
let mut copies: HashMap<usize, usize> = HashMap::new();
input.lines().for_each(|s| {
let card = card(s).unwrap().1;
println!("{}, cur result: {}", card.id, result);
let mut num_copies = 1;
if let Some(extra_copies) = copies.get(&card.id) {
num_copies += extra_copies;
}
for _ in 1..=num_copies {
for i in card.id..=(card.id + card.num_winning()) {
if let Some(n) = copies.get_mut(&i) {
*n += 1;
} else {
copies.insert(i, 1);
}
}
}
result += num_copies
});
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11"#;
println!("input:\n{input}");
assert_eq!(Day4::part1(input.into()), 13);
assert_eq!(Day4::part2(input.into()), 30);
}
}

View file

@ -1,155 +0,0 @@
mod prelude;
use std::collections::HashMap;
extern crate nom;
use nom::{
branch::alt,
bytes::complete::{tag, take_until, take_while},
character::complete::{line_ending, newline, space1},
combinator::{map_res, value},
multi::separated_list0,
sequence::tuple,
IResult,
};
use crate::prelude::*;
fn main() {
Day5::show(day_input(5), day_input(5))
}
#[derive(Hash, Debug, PartialEq, Eq, Clone)]
enum Layer {
Seed,
Soil,
Fertilizer,
Water,
Light,
Temperature,
Humidity,
Location,
}
#[derive(Hash, Eq, Debug, PartialEq, Clone)]
struct LayerDir {
from: Layer,
to: Layer,
}
#[derive(Debug)]
struct Mapping {
from_start: usize,
to_start: usize,
length: usize,
}
#[derive(Debug)]
struct Almanac {
seeds: Vec<usize>,
mappings: HashMap<LayerDir, Vec<Mapping>>,
}
fn layer(input: &str) -> IResult<&str, Layer> {
alt((
value(Layer::Seed, tag("seed")),
value(Layer::Soil, tag("soil")),
value(Layer::Fertilizer, tag("fertilizer")),
value(Layer::Water, tag("water")),
value(Layer::Light, tag("light")),
value(Layer::Temperature, tag("temperature")),
value(Layer::Humidity, tag("humidity")),
value(Layer::Location, tag("location")),
))(input)
}
fn int(input: &str) -> IResult<&str, usize> {
map_res(take_while(char::is_numeric), str::parse)(input)
}
fn mapping(input: &str) -> IResult<&str, Mapping> {
println!("mapping input: {input:?}");
let (input, nums) = separated_list0(space1, int)(input)?;
println!("nums: {nums:?}");
Ok((
input,
Mapping {
from_start: nums[0],
to_start: nums[1],
length: nums[2],
},
))
}
fn mapping_entry(input: &str) -> IResult<&str, (LayerDir, Vec<Mapping>)> {
let (input, (from, _, to, _)) = tuple((layer, tag("-to-"), layer, tag(" map:\n")))(input)?;
let (rest, input) = take_until("\n\n")(input)?;
let (input, mappings) = separated_list0(newline, mapping)(input)?;
Ok((rest, (LayerDir { from, to }, mappings)))
}
fn almanac(input: &str) -> IResult<&str, Almanac> {
let (input, seeds) = separated_list0(space1, int)(tag("seeds: ")(input)?.0)?;
let (input, mapping_tuples) =
separated_list0(tuple((line_ending, line_ending)), mapping_entry)(tag("\n\n")(input)?.0)?;
let mappings: HashMap<LayerDir, Vec<Mapping>> = mapping_tuples.into_iter().collect();
Ok((input, Almanac { seeds, mappings }))
}
struct Day5 {}
impl AoCSolution for Day5 {
type Input = String;
type Solution = usize;
fn part1(input: Self::Input) -> Self::Solution {
let almanac = almanac(&input);
println!("{almanac:?}");
0
}
fn part2(input: Self::Input) -> Self::Solution {
0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4"#;
assert_eq!(Day5::part1(input.into()), 35);
assert_eq!(Day5::part2(input.into()), 0);
}
}

View file

@ -4,6 +4,8 @@ pub use std::io::Read;
use std::path::{Path, PathBuf};
use std::{env, io};
pub type Reader = Box<dyn Read>;
#[derive(Debug)]
enum InputFileError {
VarError(env::VarError),

View file

@ -1,32 +0,0 @@
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);
}
}

View file

@ -1,30 +0,0 @@
#!/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

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@
use flake

View file

@ -1,3 +0,0 @@
/target
/.direnv

7
2024/rust/Cargo.lock generated
View file

@ -1,7 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aoc2024"
version = "1.0.0"

View file

@ -1,104 +0,0 @@
[package]
name = "aoc2024"
version = "1.0.0"
edition = "2021"
[[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"

View file

@ -1,27 +0,0 @@
{
"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
}

View file

@ -1,27 +0,0 @@
{
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
];
};
});
};
}

View file

@ -1,43 +0,0 @@
# 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

View file

@ -1,56 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let mut note: HistorianNote = day_input(1).parse().unwrap();
show_answers(note.smallests_distances(), note.similarity_score());
}
#[derive(Default)]
struct HistorianNote {
left: Vec<i64>,
right: Vec<i64>,
}
impl FromStr for HistorianNote {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut result = Self::default();
let mut line_tokens = s.lines().map(|l| l.split_once(" "));
while let Some((l, r)) = line_tokens.next().flatten() {
result.left.push(l.parse()?);
result.right.push(r.parse()?);
}
Ok(result)
}
}
impl HistorianNote {
fn smallests_distances(&mut self) -> i64 {
self.left.sort();
self.right.sort();
zip(&self.left, &self.right).fold(0, |sum, (l, r)| sum + (l - r).abs())
}
fn similarity_score(&self) -> i64 {
let frequencies = self.right.iter().fold(HashMap::new(), |mut map, r| {
*map.entry(r).or_default() += 1;
map
});
self.left
.iter()
.fold(0, |sum, l| sum + frequencies.get(l).unwrap_or(&0) * l)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let mut note: HistorianNote = "3 4\n4 3\n2 5\n1 3\n3 9\n3 3".parse().unwrap();
assert_eq!(note.smallests_distances(), 11);
assert_eq!(note.similarity_score(), 31);
}
}

View file

@ -1,115 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(10);
show_answers(part1(&input), part2(&input));
}
fn part1(input: &str) -> usize {
println!("{input}");
let mut sum = 0;
let map: Vec<Vec<u8>> = input
.lines()
.map(|l| l.bytes().map(|b| b - b'0').collect())
.collect();
for (y, row) in map.iter().enumerate() {
for (x, height) in row.iter().enumerate() {
let mut scores = HashSet::new();
if *height == 0 {
capture_scores(&map, *height, x, y, &mut scores);
sum += scores.len();
}
println!("{x} {y} {height}");
}
}
sum
}
fn capture_scores(
map: &Vec<Vec<u8>>,
height: u8,
x: usize,
y: usize,
scores: &mut HashSet<(usize, usize)>,
) {
if height == 9 {
scores.insert((x, y));
println!("found a 9");
}
for (nx, ny) in [
(x + 1, y),
(x.saturating_sub(1), y),
(x, y + 1),
(x, y.saturating_sub(1)),
] {
if nx >= map[0].len() || ny >= map.len() {
continue;
}
let next_height = map[ny][nx];
if next_height.saturating_sub(height) == 1 {
println!("{height} -> {next_height} at {nx},{ny}");
capture_scores(map, next_height, nx, ny, scores)
}
}
}
fn part2(input: &str) -> usize {
println!("{input}");
let mut sum = 0;
let map: Vec<Vec<u8>> = input
.lines()
.map(|l| l.bytes().map(|b| b - b'0').collect())
.collect();
for (y, row) in map.iter().enumerate() {
for (x, height) in row.iter().enumerate() {
if *height == 0 {
sum += find_paths_to_nines(&map, *height, x, y);
}
println!("{x} {y} {height}");
}
}
sum
}
fn find_paths_to_nines(map: &Vec<Vec<u8>>, height: u8, x: usize, y: usize) -> usize {
let mut sum = 0;
if height == 9 {
return 1;
}
for (nx, ny) in [
(x + 1, y),
(x.saturating_sub(1), y),
(x, y + 1),
(x, y.saturating_sub(1)),
] {
if nx >= map[0].len() || ny >= map.len() {
continue;
}
let next_height = map[ny][nx];
if next_height.saturating_sub(height) == 1 {
println!("{height} -> {next_height} at {nx},{ny}");
sum += find_paths_to_nines(map, next_height, nx, ny)
}
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"89010123
78121874
87430965
96549874
45678903
32019012
01329801
10456732"#;
assert_eq!(part1(input), 36);
assert_eq!(part2(input), 81);
}
}

View file

@ -1,62 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(11);
show_answers(part1(&input), part2(&input));
}
fn part1(input: &str) -> usize {
stone_blinks_by_count(input, 25)
}
fn stone_blink(s: usize) -> Vec<usize> {
if s == 0 {
return vec![1];
}
let ss = s.to_string();
if ss.len() % 2 == 0 {
let mid = ss.len() / 2;
let (s1, s2) = (ss[0..mid].parse().unwrap(), ss[mid..].parse().unwrap());
return vec![s1, s2];
}
vec![s * 2024]
}
fn stone_blinks_by_count(input: &str, count: usize) -> usize {
let mut stones: HashMap<usize, usize> = input
.trim()
.split(" ")
.map(|n| n.parse().unwrap())
.fold(HashMap::new(), |mut acc, n| {
*acc.entry(n).or_default() += 1;
acc
});
let mut next_stones: HashMap<usize, usize> = HashMap::new();
for _c in 0..count {
for (n, c) in &stones {
for nn in stone_blink(*n) {
*next_stones.entry(nn).or_default() += c;
}
}
println!("afterblink\n{stones:?}\n{next_stones:?}");
(stones, next_stones) = (next_stones, HashMap::new());
}
stones.into_values().sum()
}
fn part2(input: &str) -> usize {
stone_blinks_by_count(input, 75)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"125 17"#;
assert_eq!(part1(input), 55312);
assert_eq!(part2(input), 65601038650482);
}
}

View file

@ -1,171 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(12);
show_answers(part1(&input), part2(&input));
}
enum Dir {
N,
S,
E,
W,
}
#[derive(Debug)]
struct Region {
locations: HashSet<(usize, usize)>,
perimeters: usize,
}
impl Region {
fn measure_at(bytes: &Vec<Vec<u8>>, sx: usize, sy: usize) -> Self {
let target = bytes[sy][sx];
let (height, width) = (bytes.len(), bytes[0].len());
let mut candidates = vec![(sx, sy)];
let mut locations = HashSet::from_iter(candidates.clone());
let mut perimeters = 0;
while let Some((x, y)) = candidates.pop() {
print!("AT {x},{y}: ");
for (nx, ny) in [
(x, y.saturating_sub(1)),
(x, y + 1),
(x + 1, y),
(x.saturating_sub(1), y),
] {
if (nx, ny) == (x, y) || nx >= width || ny >= height {
print!("EDGE@{nx},{ny}; ");
perimeters += 1;
continue;
}
if bytes[ny][nx] != target {
print!("EDGE@{nx},{ny}; ");
perimeters += 1;
continue;
}
if !locations.contains(&(nx, ny)) {
candidates.push((nx, ny));
locations.insert((nx, ny));
}
}
print!("\n");
}
Self {
locations,
perimeters,
}
}
}
fn regions(input: &str) -> Vec<Region> {
let mut regions = vec![];
let bytes: Vec<Vec<u8>> = input
.trim()
.lines()
.map(|l| l.trim().bytes().collect())
.collect();
let mut regionated: HashSet<(usize, usize)> = HashSet::new();
for y in 0..bytes.len() {
for x in 0..bytes[y].len() {
if regionated.contains(&(x, y)) {
continue;
}
let region = Region::measure_at(&bytes, x, y);
regionated.extend(&region.locations);
regions.push(region);
}
}
regions
}
fn part1(input: &str) -> usize {
regions(input)
.iter()
.map(|r| r.locations.len() * r.perimeters)
.sum()
}
fn part2(input: &str) -> usize {
regions(input)
.iter()
.map(|r| {
println!("{r:?}");
// find top edge in region
if r.locations.len() <= 2 {
return r.locations.len() * 4;
}
let (sx, sy) = {
let (x, mut y) = *r.locations.iter().next().unwrap();
y = (1..y)
.rev()
.find(|y| r.locations.contains(&(x, y - 1)))
.unwrap_or(0);
(x, y)
};
// walk along region edge and count turns until we end up where we started
let mut turns = 0;
let ops = [
|(x, y)| (x + 1, y),
|(x, y)| (x, y + 1),
|(x, y): (usize, usize)| (x.saturating_sub(1), y),
|(x, y): (usize, usize)| (x, y.saturating_sub(1)),
];
let mut dir = 0;
let (mut x, mut y) = (sx, sy);
println!("start at {x},{y}");
loop {
let initial_dir = dir;
while !r.locations.contains(&ops[dir]((x, y))) || ops[dir]((x, y)) == (x, y) {
println!("turning @ {x},{y}");
dir = (dir + 1) % ops.len();
if dir == initial_dir {
panic!("turned too many times in one place");
}
turns += 1;
}
(x, y) = ops[dir]((x, y));
println!("move to {x},{y}");
if turns >= 3 && x == sx && y == sy {
break;
}
}
println!("turns {turns}");
// walk each side, counting turns
r.locations.len() * turns
})
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE"#;
let bytes: Vec<Vec<u8>> = input
.trim()
.lines()
.map(|l| l.trim().bytes().collect())
.collect();
let region = Region::measure_at(&bytes, 0, 0);
assert_eq!(region.locations.len(), 12);
assert_eq!(region.perimeters, 18);
assert_eq!(part1(input), 1930);
assert_eq!(part2(input), 1206);
}
}

View file

@ -1,136 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let data: Data = day_input(2).parse().unwrap();
show_answers(data.num_safe(), data.num_safe_with_dampener());
}
#[derive(Default, Debug)]
struct Report {
levels: Vec<i64>,
}
impl FromStr for Report {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let levels = s
.split(" ")
.map(|s| s.parse())
.collect::<Result<Vec<i64>, ParseIntError>>()?;
Ok(Self { levels })
}
}
impl Report {
fn is_safe(&self, skip_index: Option<usize>) -> bool {
let mut ordering: Option<Ordering> = None;
let skip_index = skip_index.unwrap_or(self.levels.len() + 1);
let max = if skip_index < self.levels.len() {
self.levels.len() - 2
} else {
self.levels.len() - 1
};
for i in 0..max {
let mut ai = i;
let mut bi = ai + 1;
if ai >= skip_index {
ai += 1
};
if bi >= skip_index {
bi += 1
};
let (a, b) = (self.levels[ai], self.levels[bi]);
if !Self::safe_level_pair(a, b, &ordering) {
return false;
}
if ordering == None {
ordering = Some(a.cmp(&b))
}
}
return true;
}
fn is_safe_with_any_single_level_removed(&self) -> bool {
for i in 0..self.levels.len() {
if self.is_safe(Some(i)) {
return true;
}
}
return false;
}
fn safe_level_pair(a: i64, b: i64, kind: &Option<Ordering>) -> bool {
if a == b {
return false;
}
if (a - b).abs() > 3 {
return false;
}
if kind.map(|o| o == a.cmp(&b)) == Some(false) {
return false;
}
return true;
}
}
#[derive(Default)]
struct Data {
reports: Vec<Report>,
}
impl FromStr for Data {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let reports = s
.lines()
.map(|s| s.parse())
.collect::<Result<Vec<Report>, ParseIntError>>()?;
Ok(Self { reports })
}
}
impl Data {
fn num_safe(&self) -> usize {
self.reports.iter().filter(|r| r.is_safe(None)).count()
}
fn num_safe_with_dampener(&self) -> usize {
self.reports
.iter()
.filter(|r| r.is_safe_with_any_single_level_removed())
.count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let data: Data = "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"
.parse()
.unwrap();
assert_eq!(data.num_safe(), 2);
assert_eq!(data.num_safe_with_dampener(), 4);
// assert_eq!(report.(), 31);
}
#[test]
fn test2() {
let report: Report = Report {
levels: vec![1, 2, 5, 8, 9, 15],
};
assert_eq!(report.is_safe(None), false);
assert_eq!(report.is_safe_with_any_single_level_removed(), true);
// assert_eq!(report.(), 31);
}
}

View file

@ -1,130 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(3);
show_answers(sum_muls(&input), sum_muls_with_do_and_dont(&input));
}
fn sum_muls(input: &str) -> usize {
let mut sum = 0;
// this string slice will be how we keep track of what is "left" to parse
// though it would likely be easier to use an index and slice as needed :shrug:
let mut s = &input[..];
// look for the next valid mul operation
while let Some(i) = s.find("mul(") {
// re-slice to the remaining string
s = &s[i + 4..];
// variables to contain our operands
let mut left: Option<usize> = None;
let mut right: Option<usize> = None;
// variable to contain our currently-parsing token, as we will continue
// from here one character at a time
let mut token = &s[0..0];
for c in s.chars() {
match c {
// any digit simply gets "appended" to our token (in reality, we are just increasing the length of the token slice)
'0'..='9' => {
token = &s[0..token.len() + 1];
// the problem indicates that numbers may not be longer than
// 3 digits, so if we encounter one, we break out of this
// loop and head to the next mul operation
if token.len() > 3 {
break;
}
}
// once we hit a comma, we check that we don't already have
// a left operand then parse the number (which we can unwrap
// since we are assuring we are dealing with digits above,
// though we could easily optimize that out with bit of
// arithmetic), advance through our string slice, and reset our
// currently-parsing token
',' => {
if left.is_some() {
break;
}
left = Some(token.parse().unwrap());
s = &s[token.len() + 1..];
token = &s[0..0];
}
// pretty much the same logic for finding a comma, but for the
// right operand
')' => {
if right.is_some() {
break;
}
right = Some(token.parse().unwrap());
s = &s[token.len() + 1..];
token = &s[0..0];
}
_ => break,
}
}
if let (Some(left), Some(right)) = (left, right) {
sum += left * right;
}
}
sum
}
fn sum_muls_with_do_and_dont(input: &str) -> usize {
// the gist of this function is that we look for string segments that are
// between do() and don't() operations and only sum_muls for those NOT
// between a don't() and do()
let mut sum = 0;
let mut s = &input[..];
// since there may be a case where the last do() or don't() is a do(),
// we need to know whether or not to parse the "remainder" after we have
// processed the last don't() segment which may have ended with a do()
let mut mul_rest = true;
while let Some(i) = s.find("don't()") {
// once we find a don't, we know we want to sum_muls for everything up
// to the current position
sum += sum_muls(&s[0..i]);
// then we can advance our slice
s = &s[i..];
mul_rest = false;
if let Some(i) = s.find("do()") {
// if we find a do(), set our mul_rest flag appropriately, advance
// along the slice, and head to the next iteration (or be done in
// the loop)
s = &s[i..];
mul_rest = true;
}
}
// if we "ended" with a d(), we want to sum_muls for the rest of the slice
if mul_rest {
sum += sum_muls(s);
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
assert_eq!(
sum_muls("xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"),
161
);
assert_eq!(
sum_muls_with_do_and_dont(
"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
),
48
);
}
}

View file

@ -1,126 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(4);
let grid: Vec<Vec<u8>> = input
.trim()
.lines()
.map(|l| l.trim().bytes().collect())
.collect();
show_answers(count_xmas(&grid), count_mas_x(&grid));
}
fn count_xmas(grid: &Vec<Vec<u8>>) -> usize {
let height = grid.len();
let width = grid[0].len();
let mut counter = 0;
const XMAS: (u8, u8, u8, u8) = (b'X', b'M', b'A', b'S');
const SAMX: (u8, u8, u8, u8) = (b'S', b'A', b'M', b'X');
for (y, line) in grid.iter().enumerate() {
for (x, letter) in line.iter().enumerate() {
// let near_left_edge = x < 3;
let near_top_edge = y < 3;
let near_right_edge = x >= width - 3;
let near_bottom_edge = y >= height - 3;
if !near_right_edge && !near_top_edge {
// north-west
let ray = (
*letter,
grid[y - 1][x + 1],
grid[y - 2][x + 2],
grid[y - 3][x + 3],
);
if ray == XMAS || ray == SAMX {
counter += 1;
}
}
if !near_right_edge {
// west
let ray = (*letter, grid[y][x + 1], grid[y][x + 2], grid[y][x + 3]);
if ray == XMAS || ray == SAMX {
counter += 1;
}
}
if !near_right_edge && !near_bottom_edge {
// south-west
let ray = (
*letter,
grid[y + 1][x + 1],
grid[y + 2][x + 2],
grid[y + 3][x + 3],
);
if ray == XMAS || ray == SAMX {
counter += 1;
}
}
if !near_bottom_edge {
// south
let ray = (*letter, grid[y + 1][x], grid[y + 2][x], grid[y + 3][x]);
if ray == XMAS || ray == SAMX {
counter += 1;
}
}
}
}
counter
}
fn count_mas_x(grid: &Vec<Vec<u8>>) -> usize {
let mut counter = 0;
let (width, height) = (grid[0].len(), grid.len());
for y in 1..height - 1 {
for x in 1..width - 1 {
// 'A' will always be in the middle, so we can easily just look for
// middle 'A' occurences and then check for opposing 'S' and 'M'
// characters
if grid[y][x] != b'A' {
continue;
}
// check opposing cross-characters
let ray1 = (grid[y - 1][x - 1], grid[y + 1][x + 1]);
let ray2 = (grid[y + 1][x - 1], grid[y - 1][x + 1]);
if (ray1 == (b'M', b'S') || ray1 == (b'S', b'M'))
&& (ray2 == (b'M', b'S') || ray2 == (b'S', b'M'))
{
counter += 1;
}
}
}
counter
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX"#;
let grid: Vec<Vec<u8>> = input
.trim()
.lines()
.map(|l| l.trim().bytes().collect())
.collect();
assert_eq!(count_xmas(&grid), 18);
assert_eq!(count_mas_x(&grid), 9);
}
}

View file

@ -1,133 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(5);
let update: ManualUpdate = input.parse().unwrap();
show_answers(update.part1(), update.part2());
}
#[derive(Debug)]
struct ManualUpdate {
rules: HashMap<i64, HashSet<i64>>,
updates: Vec<Vec<i64>>,
}
impl FromStr for ManualUpdate {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (rules_string, updates_string) = s.split_once("\n\n").unwrap();
let mut rules: HashMap<i64, HashSet<i64>> = HashMap::new();
let mut updates = vec![];
for rule in rules_string.lines() {
let (left, right) = rule.split_once("|").unwrap();
let (left, right): (i64, i64) = (left.parse()?, right.parse()?);
rules.entry(left).or_default().insert(right);
}
for update in updates_string.lines() {
updates.push(
update
.split(",")
.map(|page| page.parse())
.collect::<Result<Vec<i64>, Self::Err>>()?,
);
}
Ok(Self { rules, updates })
}
}
impl ManualUpdate {
fn part1(&self) -> i64 {
self.updates
.iter()
.filter(|update| self.is_update_valid(update))
.map(Self::middle_page_number)
.sum()
}
fn part2(&self) -> i64 {
self.updates
.iter()
.filter(|update| !self.is_update_valid(update))
.map(|update| self.sort_update(update))
.map(|sorted_update| Self::middle_page_number(&sorted_update))
.sum()
}
fn middle_page_number(update: &Vec<i64>) -> i64 {
update[update.len() / 2]
}
fn is_update_valid(&self, update: &Vec<i64>) -> bool {
// work through pages in an update backwards
for (i, page) in update.iter().rev().enumerate() {
// so we can loop through each previous page
for previous_page in update[0..update.len() - i].iter().rev() {
if let Some(failure_pages) = self.rules.get(page) {
if failure_pages.contains(previous_page) {
return false;
}
}
}
}
return true;
}
fn sort_update(&self, update: &Vec<i64>) -> Vec<i64> {
let mut sorted_update = update.clone();
sorted_update.sort_by(|a, b| {
if let Some(after_pages) = self.rules.get(a) {
if after_pages.contains(b) {
return Ordering::Less;
}
}
return Ordering::Greater;
});
sorted_update
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let manual_update: ManualUpdate = "47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13
75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47"
.parse()
.unwrap();
assert_eq!(manual_update.part1(), 143);
assert_eq!(manual_update.part2(), 123);
}
}

View file

@ -1,193 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(6);
let mut lab: Lab = input.parse().unwrap();
let mut lab2: Lab = input.parse().unwrap();
show_answers(
lab.num_steps_until_guard_leaves_lab(),
lab2.num_positions_of_new_obstructions_that_cause_guard_loop(),
);
}
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone)]
struct Vec2 {
x: i64,
y: i64,
}
impl Vec2 {
fn new(x: i64, y: i64) -> Self {
Self { x, y }
}
fn add(&self, v: &Vec2) -> Self {
Self::new(self.x + v.x, self.y + v.y)
}
}
impl Display for Vec2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Vec2({}, {})", self.x, self.y)
}
}
#[derive(Debug, Clone)]
struct Obstruction {
pos: Vec2,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct Guard {
pos: Vec2,
vel: Vec2,
}
impl Guard {
fn turn(&mut self) {
// too lazy for trig today I guess
if self.vel.x > 0 && self.vel.y > 0 {
self.vel.x *= -1;
} else if self.vel.x < 0 && self.vel.y > 0 {
self.vel.y *= -1;
} else if self.vel.x < 0 && self.vel.y < 0 {
self.vel.x *= -1;
} else if self.vel.x > 0 && self.vel.y < 0 {
self.vel.y *= -1;
} else if self.vel.y != 0 {
self.vel.x = self.vel.y * -1;
self.vel.y = 0;
} else if self.vel.x != 0 {
self.vel.y = self.vel.x;
self.vel.x = 0;
}
}
}
#[derive(Debug, Clone)]
struct Lab {
obstructions: Vec<Obstruction>,
guard: Guard,
size: Vec2,
}
impl FromStr for Lab {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut size = Vec2::default();
let mut obstructions = vec![];
let mut guard: Option<Guard> = None;
for (y, line) in s.lines().enumerate() {
size.y += 1;
size.x = line.len() as i64; // whatever - still linear ;)
for (x, c) in line.bytes().enumerate() {
match c {
b'#' => obstructions.push(Obstruction {
pos: Vec2::new(x as i64, y as i64),
}),
b'^' => {
guard = Some(Guard {
pos: Vec2::new(x as i64, y as i64),
vel: Vec2::new(0, -1),
})
}
_ => {}
}
}
}
Ok(Lab {
size,
guard: guard.unwrap(),
obstructions,
})
}
}
impl Lab {
fn step(&mut self) {
// move guard
let next_pos = self.guard.pos.add(&self.guard.vel);
self.guard.pos = next_pos;
// after moving, check if we need to turn
// TODO: obstructions should lookup by position instead of this
// broad-phase "check each one each time" slow business
while self.has_obstacle_at(&self.guard.pos.add(&self.guard.vel)) {
self.guard.turn();
}
}
fn contains(&self, pos: &Vec2) -> bool {
(0..self.size.x).contains(&pos.x) && (0..self.size.y).contains(&pos.y)
}
fn num_steps_until_guard_leaves_lab(&mut self) -> usize {
let mut visited = HashSet::new();
visited.insert(self.guard.pos.clone());
while self.contains(&self.guard.pos) {
self.step();
visited.insert(self.guard.pos.clone());
}
visited.len() - 1
}
fn has_obstacle_at(&self, pos: &Vec2) -> bool {
self.obstructions.iter().any(|o| o.pos == *pos)
}
fn num_positions_of_new_obstructions_that_cause_guard_loop(&mut self) -> usize {
let mut positions_with_cycles_detected = 0;
let mut visited = HashSet::new();
visited.insert(self.guard.clone());
// TODO: avoid super brute force
for x in 0..self.size.x {
for y in 0..self.size.y {
println!("Simulating at {x} {y}...");
let mut alternate_visited = visited.clone();
let mut alternate_lab = self.clone();
alternate_lab.obstructions.push(Obstruction {
pos: Vec2::new(x, y),
});
while alternate_lab.contains(&alternate_lab.guard.pos) {
alternate_lab.step();
if alternate_visited.contains(&alternate_lab.guard) {
positions_with_cycles_detected += 1;
break;
}
alternate_visited.insert(alternate_lab.guard.clone());
}
}
}
positions_with_cycles_detected
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = "
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#..."
.trim();
let mut lab: Lab = input.parse().unwrap();
let mut lab2 = lab.clone();
assert_eq!(lab.num_steps_until_guard_leaves_lab(), 41);
assert_eq!(
lab2.num_positions_of_new_obstructions_that_cause_guard_loop(),
6
);
}
}

View file

@ -1,93 +0,0 @@
mod prelude;
use std::slice::Windows;
pub use crate::prelude::*;
fn main() {
let input = day_input(7);
show_answers(
sum_possibly_valid_tests(&input, &add_and_mul),
sum_possibly_valid_tests(&input, &add_and_mul_and_concat),
);
}
fn sum_possibly_valid_tests(input: &str, f: &dyn Fn(Vec<usize>, &usize) -> Vec<usize>) -> usize {
let mut sum = 0;
for l in input.lines() {
let (target, operands) = l.split_once(':').unwrap();
let target: usize = target.parse().unwrap();
let operands: Vec<usize> = operands
.trim()
.split(" ")
.map(|s| s.parse().unwrap())
.collect();
if operands
.iter()
.skip(1)
.fold(vec![operands[0]], f)
.iter()
.any(|n| *n == target)
{
sum += target;
}
}
sum
}
fn add_and_mul(lefts: Vec<usize>, right: &usize) -> Vec<usize> {
let mut result = vec![];
for left in lefts {
result.push(left + right);
result.push(left * right);
}
result
}
fn add_and_mul_and_concat(lefts: Vec<usize>, right: &usize) -> Vec<usize> {
let mut result = vec![];
for left in lefts {
result.push(left + right);
result.push(left * right);
result.push(format!("{}{}", left, right).parse().unwrap());
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
assert_eq!(
sum_possibly_valid_tests(
"190: 10 19
3267: 81 40 27
83: 17 5
156: 15 6
7290: 6 8 6 15
161011: 16 10 13
192: 17 8 14
21037: 9 7 18 13
292: 11 6 16 20",
&add_and_mul
),
3749
);
assert_eq!(
sum_possibly_valid_tests(
"190: 10 19
3267: 81 40 27
83: 17 5
156: 15 6
7290: 6 8 6 15
161011: 16 10 13
192: 17 8 14
21037: 9 7 18 13
292: 11 6 16 20",
&add_and_mul_and_concat
),
11387
);
}
}

View file

@ -1,115 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(8);
show_answers(part1(&input), part2(&input));
}
fn part1(input: &str) -> usize {
let mut map: HashMap<u8, HashSet<(i64, i64)>> = HashMap::new();
let mut antinodes: HashSet<(i64, i64)> = HashSet::new();
let (width, height) = (
input.lines().count() as i64,
input.lines().next().unwrap().len() as i64,
);
for (y, line) in input.lines().enumerate() {
for (x, c) in line.bytes().enumerate() {
if c == b'.' {
continue;
}
map.entry(c)
.or_insert_with(|| HashSet::new())
.insert((x as i64, y as i64));
}
}
for nodes in map.values() {
println!("{nodes:?}");
for (i, (x1, y1)) in nodes.iter().enumerate() {
for (j, (x2, y2)) in nodes.iter().enumerate() {
if i == j {
continue;
}
let (dx, dy) = (x1 - x2, y1 - y2);
let (a1x, a1y) = (x1 + dx, y1 + dy);
let (a2x, a2y) = (x2 - dx, y2 - dy);
if a1x >= 0 && a1x < width && a1y >= 0 && a1y < height {
antinodes.insert((a1x, a1y));
}
if a2x >= 0 && a2x < width && a2y >= 0 && a2y < height {
antinodes.insert((a2x, a2y));
}
}
}
}
antinodes.len()
}
fn part2(input: &str) -> usize {
let mut map: HashMap<u8, HashSet<(i64, i64)>> = HashMap::new();
let mut antinodes: HashSet<(i64, i64)> = HashSet::new();
let (width, height) = (
input.trim().lines().count() as i64,
input.lines().next().unwrap().trim().len() as i64,
);
for (y, line) in input.lines().enumerate() {
for (x, c) in line.bytes().enumerate() {
if c == b'.' {
continue;
}
map.entry(c)
.or_insert_with(|| HashSet::new())
.insert((x as i64, y as i64));
}
}
for nodes in map.values() {
for (i, (x1, y1)) in nodes.iter().enumerate() {
for (j, (x2, y2)) in nodes.iter().enumerate() {
antinodes.insert((*x1, *y1));
antinodes.insert((*x2, *y2));
if i == j {
continue;
}
let (dx, dy) = (x1 - x2, y1 - y2);
let (mut a1x, mut a1y) = (x1 + dx, y1 + dy);
while a1x >= 0 && a1x < width && a1y >= 0 && a1y < height {
antinodes.insert((a1x, a1y));
println!("{a1x} {a1y} {}", antinodes.len());
a1x += dx;
a1y += dy;
}
let (mut a2x, mut a2y) = (x2 - dx, y2 - dy);
while a2x >= 0 && a2x < width && a2y >= 0 && a2y < height {
antinodes.insert((a2x, a2y));
println!("{a2x} {a2y} {}", antinodes.len());
a2x -= dx;
a2y -= dy;
}
}
}
}
antinodes.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = "............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............";
assert_eq!(part1(input), 14);
assert_eq!(part2(input), 34);
}
}

View file

@ -1,101 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(9);
show_answers(part1(&input), part2(&input));
}
struct Disk {
blocks: Vec<Option<usize>>,
files: Vec<(usize, u8)>,
free_blocks: usize,
}
impl FromStr for Disk {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut blocks: Vec<Option<usize>> = vec![];
let mut files = vec![];
let mut free_blocks: usize = 0;
for (i, len) in s.trim().bytes().map(|len| len - b'0').enumerate() {
// every other iteration is "free space"
let file_id = if i % 2 == 0 {
Some((i / 2) as usize)
} else {
free_blocks += len as usize;
None
};
if file_id.is_some() {
files.push((blocks.len(), len));
}
for _ in 0..len {
blocks.push(file_id);
}
}
Ok(Self {
files,
blocks,
free_blocks,
})
}
}
impl Disk {
fn checksum(&self) -> usize {
self.blocks
.iter()
.enumerate()
.map(|(i, b)| i * b.unwrap_or_default())
.sum()
}
}
fn part1(input: &str) -> usize {
let mut disk: Disk = input.parse().unwrap();
let mut right = disk.blocks.len() - 1;
for left in 0..(right.clone() - disk.free_blocks + 1) {
if disk.blocks[left].is_some() {
continue;
}
while disk.blocks[right].is_none() {
right -= 1;
}
(disk.blocks[left], disk.blocks[right]) = (disk.blocks[right], disk.blocks[left]);
}
disk.checksum()
}
fn part2(input: &str) -> usize {
let mut disk: Disk = input.parse().unwrap();
for (cur_i, len) in disk.files.iter().rev() {
for left in 0..*cur_i {
if disk.blocks[left..(left + (*len as usize))]
.iter()
.all(|f| f.is_none())
{
for i in 0..*len {
let i = i as usize;
(disk.blocks[left + i], disk.blocks[cur_i + i]) =
(disk.blocks[cur_i + i], disk.blocks[left + i])
}
break;
}
}
}
disk.checksum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#"2333133121414131402"#;
assert_eq!(part1(input), 1928);
assert_eq!(part2(input), 2858);
}
}

View file

@ -1,60 +0,0 @@
use std::path::{Path, PathBuf};
pub use std::{
cmp::Ordering,
collections::HashMap,
collections::HashSet,
convert::Infallible,
fmt::Display,
fs::File,
io::Read,
iter::zip,
num::{ParseFloatError, ParseIntError},
str::FromStr,
sync::{Arc, LazyLock, Mutex},
};
pub type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub type EmptyResult = AnyResult<()>;
#[derive(Debug)]
enum InputFileError {
#[allow(dead_code)]
EnvVar(std::env::VarError),
#[allow(dead_code)]
Io(std::io::Error),
}
/// Ensures that the input file exists
fn ensure_input_file(day: u8) -> Result<PathBuf, InputFileError> {
let path = Path::new(&std::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 fn show_answers<T: std::fmt::Display>(part1: T, part2: T) {
println!("Part 1: {}", part1);
println!("Part 2: {}", part2);
}

View file

@ -1,27 +0,0 @@
mod prelude;
pub use crate::prelude::*;
fn main() {
let input = day_input(DAY);
show_answers(part1(&input), part2(&input));
}
fn part1(input: &str) -> usize {
0
}
fn part2(input: &str) -> usize {
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let input = r#""#;
assert_eq!(part1(input), 1);
assert_eq!(part2(input), 0);
}
}

View file

@ -3,7 +3,6 @@
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)