Compare commits
111 Commits
6a619d756f
...
main
Author | SHA1 | Date | |
---|---|---|---|
77fba87ef6
|
|||
8b8ed2a323
|
|||
6efb9e0f83
|
|||
237ca36381
|
|||
d3bade1bdd
|
|||
50a197856a
|
|||
d3ce12693b
|
|||
d31f9725f5
|
|||
c5857ed449
|
|||
40d5e820bc
|
|||
5d518248a8
|
|||
cebdbd1007
|
|||
32937aaa0e
|
|||
c681727fb3
|
|||
02fc154547
|
|||
13f61e3a2b
|
|||
8958ba9361
|
|||
e60f6effa3
|
|||
c2e3422544
|
|||
6b6dededc2
|
|||
65d498f168
|
|||
cdb3a7261a
|
|||
dbee7d91b6
|
|||
0fee7c3594
|
|||
824d111b18
|
|||
a1dceb6ff1
|
|||
3712b32634
|
|||
2a11e17d92
|
|||
1c254fff93
|
|||
44108c4b35
|
|||
b588837624
|
|||
9a6ca66059
|
|||
2729799fa2
|
|||
8d2fbc0fcb
|
|||
4f48d839b2
|
|||
414569537e
|
|||
5036866663
|
|||
9be86e2cc2
|
|||
5bcead2691
|
|||
c99d8a400a
|
|||
5e8b974137
|
|||
28a88e1aa7
|
|||
33615b015f
|
|||
b7a1f05b1e
|
|||
de7ee8f0f6
|
|||
c31d653612
|
|||
20e6889572
|
|||
755fbbc53d
|
|||
4b85a90635
|
|||
2cf8527c4f
|
|||
6283ff37f9
|
|||
f2186d18d3
|
|||
c261ee56fe
|
|||
411d6aa26d
|
|||
c2c0145219
|
|||
4dfdaca58c
|
|||
003bc3212d
|
|||
b060de20c7
|
|||
bc7ec50c94
|
|||
8ae2115b52
|
|||
74a6b16924
|
|||
e8a38e7b24
|
|||
c6153663b5
|
|||
8b011941c4
|
|||
d88f907c03
|
|||
d6d81a0c29
|
|||
ed184fc92c
|
|||
a1774d1f73
|
|||
3bbf05b30c
|
|||
6a8a7a9ad1
|
|||
1a6d37f4f3
|
|||
35637cece1
|
|||
ebf5a0a489
|
|||
4aa7e9f43c
|
|||
a5439062a4
|
|||
3658183deb
|
|||
38cba37b06
|
|||
6022d2cc39
|
|||
447ff5c62c
|
|||
c213bbbc27
|
|||
a56fc933c9
|
|||
31eb500832
|
|||
4c14c6092e
|
|||
de535303d4
|
|||
50b6d045e7
|
|||
d2defae8a2
|
|||
cd8900d936
|
|||
11b29a4d57
|
|||
3bfde9fd9b
|
|||
2e239681ce
|
|||
a7354b6ed7
|
|||
462918b382
|
|||
8af11a6092
|
|||
d9d55b069f
|
|||
0716dde8b1
|
|||
206c1fca85
|
|||
f7cf4f1e9f
|
|||
b08c8fbd80
|
|||
2bc751dd0d
|
|||
a6ea5b4155
|
|||
622877843e
|
|||
47e40942e2
|
|||
3e0bc0d5cc
|
|||
868c3e56fc
|
|||
fee37aebd0
|
|||
b1918bbebf
|
|||
1cd535c2aa
|
|||
1dd3ce5862
|
|||
145d779e83
|
|||
49c37800a0
|
|||
e2cd0fe9cc
|
BIN
.aoc_tiles/tiles/2024/01.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
.aoc_tiles/tiles/2024/02.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
.aoc_tiles/tiles/2024/03.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
.aoc_tiles/tiles/2024/04.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
.aoc_tiles/tiles/2024/05.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
.aoc_tiles/tiles/2024/06.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
.aoc_tiles/tiles/2024/07.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
.aoc_tiles/tiles/2024/08.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
.aoc_tiles/tiles/2024/09.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
.aoc_tiles/tiles/2024/10.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
.aoc_tiles/tiles/2024/11.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
.aoc_tiles/tiles/2024/12.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
.aoc_tiles/tiles/2024/13.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
.aoc_tiles/tiles/2024/14.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
.aoc_tiles/tiles/2024/15.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
.aoc_tiles/tiles/2024/16.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
.aoc_tiles/tiles/2024/17.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
.aoc_tiles/tiles/2024/18.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
.aoc_tiles/tiles/2024/19.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
.aoc_tiles/tiles/2024/20.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
.aoc_tiles/tiles/2024/21.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
.aoc_tiles/tiles/2024/22.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
.aoc_tiles/tiles/2024/23.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
.aoc_tiles/tiles/2024/24.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
.aoc_tiles/tiles/2024/25.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
62
.github/workflows/test.yaml
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: AoC 2024
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: setup toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache: true
|
||||
components: clippy, rustfmt
|
||||
toolchain: stable
|
||||
rustflags: ""
|
||||
|
||||
- name: install cargo-aoc
|
||||
run: cargo install --locked cargo-aoc
|
||||
|
||||
- name: get date
|
||||
id: date
|
||||
run: |
|
||||
echo date=$(date +%Y%m%d) > $GITHUB_OUTPUT
|
||||
|
||||
- name: Get cached inputs
|
||||
id: inputs
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: input
|
||||
key: ${{ steps.date.outputs.date }}
|
||||
|
||||
- name: Get inputs from source
|
||||
if: steps.inputs.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cargo aoc credentials ${{ secrets.AOC_SESSION }}
|
||||
for i in $(seq 1 25); do
|
||||
dayfmt=$(printf %02d $i)
|
||||
if [ ${{ steps.date.outputs.date }} -ge 202412${dayfmt} ]; then
|
||||
cargo aoc input --year 2024 --day $i
|
||||
fi
|
||||
done
|
||||
|
||||
- name: cargo test
|
||||
run: cargo test --lib
|
||||
|
||||
- name: rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: clippy
|
||||
run: cargo clippy --lib --tests -- -D warnings
|
||||
if: always()
|
||||
|
||||
- name: full run
|
||||
run: cargo run --release
|
||||
if: always()
|
5
.gitignore
vendored
@ -1,4 +1,7 @@
|
||||
**/target
|
||||
input
|
||||
flamegraph.svg
|
||||
perf.data
|
||||
perf.data*
|
||||
guesses
|
||||
.aoc_tiles/*
|
||||
!.aoc_tiles/tiles/
|
||||
|
30
.pre-commit-config.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rust-linting
|
||||
name: Rust linting
|
||||
entry: cargo fmt --all --
|
||||
pass_filenames: true
|
||||
types: [file, rust]
|
||||
language: system
|
||||
- id: rust-clippy
|
||||
name: Rust clippy
|
||||
entry: cargo clippy --lib --all-features --tests -- -D warnings
|
||||
pass_filenames: false
|
||||
types: [file, rust]
|
||||
language: system
|
||||
- repo: https://github.com/LiquidFun/aoc_tiles
|
||||
rev: 0.6.2
|
||||
hooks:
|
||||
- id: aoc-tiles
|
||||
# Optionally use these arguments. Auto add tiles to git adds the tiles to git,
|
||||
# possibly amends your commit by creating the tile images and updating the README.
|
||||
# Language sorting shows the preference of the order of the languages to use.
|
||||
# Exclude paterns are globs which can be used to exclude files when creating
|
||||
# the tiles. See the customization section in the README for more flags.
|
||||
# Simply remove the comments (#) below for args and the flags you want.
|
||||
args:
|
||||
- --auto-add-tiles-to-git=amend
|
||||
- --overwrite-year=2024
|
||||
# - --language-sorting=jl,kt,py,rs
|
||||
# - --exclude-patterns=2021/*/*.apl,2021/*/*.py,2021/*/*.cpp
|
105
1/src/main.rs
@ -1,105 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Lines};
|
||||
use std::iter::zip;
|
||||
use std::time::Instant;
|
||||
|
||||
// BOILERPLATE
|
||||
type InputIter = Lines<BufReader<File>>;
|
||||
|
||||
fn get_input() -> InputIter {
|
||||
let f = File::open("input").unwrap();
|
||||
let br = BufReader::new(f);
|
||||
br.lines()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let start = Instant::now();
|
||||
let ans1 = problem1(get_input());
|
||||
let duration = start.elapsed();
|
||||
println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64());
|
||||
|
||||
let start = Instant::now();
|
||||
let ans2 = problem2(get_input());
|
||||
let duration = start.elapsed();
|
||||
println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64());
|
||||
}
|
||||
|
||||
struct Locations {
|
||||
left: Vec<u64>,
|
||||
right: Vec<u64>,
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<Lines<T>> for Locations {
|
||||
fn from(input: Lines<T>) -> Self {
|
||||
let mut left = Vec::new();
|
||||
let mut right = Vec::new();
|
||||
for line in input.map(|i| i.unwrap()) {
|
||||
let parts: Vec<&str> = line.split_ascii_whitespace().collect();
|
||||
left.push(parts[0].parse::<u64>().unwrap());
|
||||
right.push(parts[1].parse::<u64>().unwrap());
|
||||
}
|
||||
Self { left, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl Locations {
|
||||
fn sort(&mut self) {
|
||||
self.left.sort();
|
||||
self.right.sort();
|
||||
}
|
||||
fn right_count(&self) -> HashMap<u64, u64> {
|
||||
let mut right_count: HashMap<u64, u64> = HashMap::new();
|
||||
for rval in &self.right {
|
||||
right_count.insert(*rval, *right_count.get(rval).unwrap_or(&0) + 1);
|
||||
}
|
||||
right_count
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
|
||||
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let mut locations = Locations::from(input);
|
||||
locations.sort();
|
||||
|
||||
zip(locations.left, locations.right)
|
||||
.map(|(l, r)| u64::abs_diff(l, r))
|
||||
.sum()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let locations = Locations::from(input);
|
||||
let right_count = locations.right_count();
|
||||
locations
|
||||
.left
|
||||
.iter()
|
||||
.map(|l| l * right_count.get(l).unwrap_or(&0))
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
use std::io::Cursor;
|
||||
|
||||
const EXAMPLE: &str = &"3 4
|
||||
4 3
|
||||
2 5
|
||||
1 3
|
||||
3 9
|
||||
3 3";
|
||||
|
||||
#[test]
|
||||
fn problem1_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem1(c.lines()), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn problem2_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem2(c.lines()), 31);
|
||||
}
|
||||
}
|
7
2/Cargo.lock
generated
@ -1,7 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "day2"
|
||||
version = "0.1.0"
|
133
2/src/main.rs
@ -1,133 +0,0 @@
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Lines};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// BOILERPLATE
|
||||
type InputIter = Lines<BufReader<File>>;
|
||||
|
||||
fn get_input() -> InputIter {
|
||||
let f = File::open("input").unwrap();
|
||||
let br = BufReader::new(f);
|
||||
br.lines()
|
||||
}
|
||||
|
||||
fn duration_format(duration: Duration) -> String {
|
||||
match duration.as_secs_f64() {
|
||||
x if x > 1.0 => format!("{:.3}s", x),
|
||||
x if x > 0.010 => format!("{:.3}ms", x * 1e3),
|
||||
x => format!("{:.3}us", x * 1e6),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = get_input();
|
||||
let start = Instant::now();
|
||||
let ans1 = problem1(input);
|
||||
let duration1 = start.elapsed();
|
||||
println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1));
|
||||
|
||||
let input = get_input();
|
||||
let start = Instant::now();
|
||||
let ans2 = problem2(input);
|
||||
let duration2 = start.elapsed();
|
||||
println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2));
|
||||
println!("Total duration: {}", duration_format(duration1 + duration2));
|
||||
}
|
||||
|
||||
struct Reports {
|
||||
reports: Vec<Vec<u64>>,
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<Lines<T>> for Reports {
|
||||
fn from(lines: Lines<T>) -> Self {
|
||||
let mut reports = Vec::new();
|
||||
for line in lines.map(|i| i.unwrap()) {
|
||||
reports.push(
|
||||
line.split_ascii_whitespace()
|
||||
.map(|record| record.parse::<u64>().unwrap())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
Reports { reports }
|
||||
}
|
||||
}
|
||||
|
||||
impl Reports {
|
||||
fn is_safe(report: &Vec<u64>) -> bool {
|
||||
let mut ascending: bool = true;
|
||||
let mut descending: bool = true;
|
||||
for (a, b) in report.iter().zip(report.iter().skip(1)) {
|
||||
if a > b {
|
||||
ascending = false
|
||||
}
|
||||
if a < b {
|
||||
descending = false;
|
||||
}
|
||||
let ad = a.abs_diff(*b);
|
||||
if !(ad >= 1 && ad <= 3) || (!ascending && !descending) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
fn count_safe(&self) -> u64 {
|
||||
self.reports.iter().filter(|report| Self::is_safe(report)).count() as u64
|
||||
}
|
||||
fn is_dumb_dampened_safe(report: &Vec<u64>) -> bool {
|
||||
if Self::is_safe(report) {
|
||||
return true;
|
||||
}
|
||||
for i in 0..report.len() {
|
||||
let mut new_vec = report.clone();
|
||||
new_vec.remove(i);
|
||||
if Self::is_safe(&new_vec) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn dampened_count_safe(&self) -> u64 {
|
||||
self.reports
|
||||
.iter()
|
||||
.filter(|report| Self::is_dumb_dampened_safe(report))
|
||||
.count() as u64
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
|
||||
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let reports = Reports::from(input);
|
||||
reports.count_safe()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let reports = Reports::from(input);
|
||||
reports.dampened_count_safe()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
use std::io::Cursor;
|
||||
|
||||
const EXAMPLE: &str = &"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";
|
||||
|
||||
#[test]
|
||||
fn problem1_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem1(c.lines()), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn problem2_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem2(c.lines()), 4);
|
||||
}
|
||||
}
|
71
3/Cargo.lock
generated
@ -1,71 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "day3"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "day3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
nom = "7.1.3"
|
||||
regex = { version = "1.11.1", default-features = false, features = ["perf", "std"] }
|
@ -1,90 +0,0 @@
|
||||
use regex::bytes::Regex;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Lines};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// BOILERPLATE
|
||||
type InputIter = Lines<BufReader<File>>;
|
||||
|
||||
pub fn get_input() -> InputIter {
|
||||
let f = File::open("input").unwrap();
|
||||
let br = BufReader::new(f);
|
||||
br.lines()
|
||||
}
|
||||
|
||||
fn duration_format(duration: Duration) -> String {
|
||||
match duration.as_secs_f64() {
|
||||
x if x > 1.0 => format!("{:.3}s", x),
|
||||
x if x > 0.010 => format!("{:.3}ms", x * 1e3),
|
||||
x => format!("{:.3}us", x * 1e6),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = get_input();
|
||||
let start = Instant::now();
|
||||
let ans1 = problem1(input);
|
||||
let duration1 = start.elapsed();
|
||||
println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1));
|
||||
|
||||
let input = get_input();
|
||||
let start = Instant::now();
|
||||
let ans2 = problem2(input);
|
||||
let duration2 = start.elapsed();
|
||||
println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2));
|
||||
println!("Total duration: {}", duration_format(duration1 + duration2));
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
|
||||
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let mut sum = 0u64;
|
||||
let re = Regex::new(r"(?-u)mul\((\d+),(\d+)\)").unwrap();
|
||||
for line in input.map(|i| i.unwrap()) {
|
||||
let line = line.as_bytes();
|
||||
for m in re.captures_iter(line) {
|
||||
sum += std::str::from_utf8(&m[1]).unwrap().parse::<u64>().unwrap() * std::str::from_utf8(&m[2]).unwrap().parse::<u64>().unwrap();
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let mut sum = 0u64;
|
||||
let mut do_mul = true;
|
||||
let re = Regex::new(r"(?-u)(do\(\)|don't\(\)|mul\((\d+),(\d+)\))").unwrap();
|
||||
for line in input.map(|i| i.unwrap()) {
|
||||
let line = line.as_bytes();
|
||||
for m in re.captures_iter(line) {
|
||||
match std::str::from_utf8(&m[1]).unwrap() {
|
||||
"do()" => do_mul = true,
|
||||
"don't()" => do_mul = false,
|
||||
_ if do_mul => sum += std::str::from_utf8(&m[2]).unwrap().parse::<u64>().unwrap() * std::str::from_utf8(&m[3]).unwrap().parse::<u64>().unwrap(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
use std::io::Cursor;
|
||||
|
||||
const EXAMPLE1: &str = &"xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))";
|
||||
const EXAMPLE2: &str = &"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))";
|
||||
|
||||
#[test]
|
||||
fn problem1_example() {
|
||||
let c = Cursor::new(EXAMPLE1);
|
||||
assert_eq!(problem1(c.lines()), 161);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn problem2_example() {
|
||||
let c = Cursor::new(EXAMPLE2);
|
||||
assert_eq!(problem2(c.lines()), 48);
|
||||
}
|
||||
}
|
7
4/Cargo.lock
generated
@ -1,7 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "day4"
|
||||
version = "0.1.0"
|
@ -1,6 +0,0 @@
|
||||
[package]
|
||||
name = "day4"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
666
Cargo.lock
generated
Normal file
@ -0,0 +1,666 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "aoc-runner"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d21ef9204ad206a5a3e918e9920da04e1118ad91ce4f23570be964b9d6b9dfcb"
|
||||
|
||||
[[package]]
|
||||
name = "aoc-runner-derive"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba8b944269d3fee645d281b1335e1797044db497bb02d0098cc3fdb8900069cc"
|
||||
dependencies = [
|
||||
"aoc-runner-internal",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aoc-runner-internal"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "274b0ba7f3669a45ec0aaacf94eb032a749de880ab776091576cca94037c9982"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aoc2024"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aoc-runner",
|
||||
"aoc-runner-derive",
|
||||
"atoi",
|
||||
"bitflags",
|
||||
"cached",
|
||||
"colored",
|
||||
"grid",
|
||||
"itertools",
|
||||
"misc",
|
||||
"nom",
|
||||
"rayon",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "cached"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9718806c4a2fe9e8a56fd736f97b340dd10ed1be8ed733ed50449f351dc33cae"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"cached_proc_macro",
|
||||
"cached_proc_macro_types",
|
||||
"hashbrown",
|
||||
"once_cell",
|
||||
"thiserror",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cached_proc_macro"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cached_proc_macro_types"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "grid"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "misc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
25
Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "aoc2024"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
aoc-runner = "0.3.0"
|
||||
aoc-runner-derive = "0.3.0"
|
||||
atoi = "2.0.0"
|
||||
bitflags = "2.6.0"
|
||||
cached = "0.54.0"
|
||||
colored = "2.1.0"
|
||||
grid = {version = "0.1.0", path = "utils/grid"}
|
||||
itertools = "0.13.0"
|
||||
misc = {path = "utils/misc"}
|
||||
nom = "7.1.3"
|
||||
rayon = "1.10.0"
|
||||
regex = "1.11.1"
|
||||
rustc-hash = "2.1.0"
|
||||
thread_local = "1.1.8"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
80
README.md
Normal file
@ -0,0 +1,80 @@
|
||||
<!-- AOC TILES BEGIN -->
|
||||
<h1 align="center">
|
||||
2024 - 48 ⭐ - Rust
|
||||
</h1>
|
||||
<a href="src/day1.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/01.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day2.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/02.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day3.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/03.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day4.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/04.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day5.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/05.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day6.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/06.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day7.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/07.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day8.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/08.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day9.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/09.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day10.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/10.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day11.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/11.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day12.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/12.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day13.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/13.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day14.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/14.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day15.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/15.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day16.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/16.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day17.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/17.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day18.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/18.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day19.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/19.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day20.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/20.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day21.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/21.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day22.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/22.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day23.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/23.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day24.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/24.png" width="161px">
|
||||
</a>
|
||||
<a href="src/day25.rs">
|
||||
<img src=".aoc_tiles/tiles/2024/25.png" width="161px">
|
||||
</a>
|
||||
<!-- AOC TILES END -->
|
92
src/day1.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::io::{BufRead, Lines};
|
||||
|
||||
type HashMap<K, V> = FxHashMap<K, V>;
|
||||
|
||||
#[aoc_generator(day1)]
|
||||
pub fn get_input(input: &[u8]) -> Locations {
|
||||
Locations::from(input.lines())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Locations {
|
||||
left: Vec<u64>,
|
||||
right: Vec<u64>,
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<Lines<T>> for Locations {
|
||||
fn from(input: Lines<T>) -> Self {
|
||||
let mut left = Vec::new();
|
||||
let mut right = Vec::new();
|
||||
for line in input.map(|i| i.unwrap()) {
|
||||
let parts: Vec<&str> = line.split_ascii_whitespace().collect();
|
||||
left.push(parts[0].parse::<u64>().unwrap());
|
||||
right.push(parts[1].parse::<u64>().unwrap());
|
||||
}
|
||||
Self { left, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl Locations {
|
||||
fn sort(&mut self) {
|
||||
self.left.sort();
|
||||
self.right.sort();
|
||||
}
|
||||
fn right_count(&self) -> HashMap<u64, u64> {
|
||||
let mut right_count: HashMap<u64, u64> = HashMap::default();
|
||||
for rval in &self.right {
|
||||
right_count.insert(*rval, *right_count.get(rval).unwrap_or(&0) + 1);
|
||||
}
|
||||
right_count
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day1, part1)]
|
||||
pub fn part1(locations: &Locations) -> u64 {
|
||||
let mut locations = locations.clone();
|
||||
locations.sort();
|
||||
|
||||
locations
|
||||
.left
|
||||
.iter()
|
||||
.zip(locations.right)
|
||||
.map(|(l, r)| u64::abs_diff(*l, r))
|
||||
.sum()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day1, part2)]
|
||||
pub fn part2(locations: &Locations) -> u64 {
|
||||
let right_count = locations.right_count();
|
||||
locations
|
||||
.left
|
||||
.iter()
|
||||
.map(|l| l * right_count.get(l).unwrap_or(&0))
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day1::*;
|
||||
|
||||
const EXAMPLE: &[u8] = b"3 4
|
||||
4 3
|
||||
2 5
|
||||
1 3
|
||||
3 9
|
||||
3 3";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
let input = get_input(EXAMPLE);
|
||||
assert_eq!(part1(&input), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
let input = get_input(EXAMPLE);
|
||||
assert_eq!(part2(&input), 31);
|
||||
}
|
||||
}
|
111
src/day10.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use grid::Grid;
|
||||
use itertools::Itertools;
|
||||
use std::io::BufRead;
|
||||
|
||||
#[aoc_generator(day10)]
|
||||
pub fn get_input(input: &[u8]) -> TrailMap {
|
||||
TrailMap::from(input)
|
||||
}
|
||||
|
||||
pub struct TrailMap {
|
||||
map: Grid<u8>,
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<T> for TrailMap {
|
||||
fn from(input: T) -> Self {
|
||||
Self { map: input.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl TrailMap {
|
||||
fn trailheads(&self) -> Vec<(i64, i64)> {
|
||||
self.map
|
||||
.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, v)| **v == b'0')
|
||||
.map(|(i, _v)| self.map.coord(i as i64).unwrap().into())
|
||||
.collect_vec()
|
||||
}
|
||||
fn count_reachable_from(&self, pos: &(i64, i64), needle: u8, visited: &mut Grid<bool>) -> u64 {
|
||||
if visited.get(pos) == Some(&true) {
|
||||
return 0;
|
||||
} else {
|
||||
visited.set(pos, true);
|
||||
}
|
||||
let our_val = self.map.get(pos).unwrap();
|
||||
if *our_val == needle {
|
||||
return 1;
|
||||
}
|
||||
// adjacents that are +1
|
||||
[(-1, 0), (1, 0), (0, -1), (0, 1)] // left, right, up, down
|
||||
.iter()
|
||||
.map(|(x_ofs, y_ofs)| (pos.0 + x_ofs, pos.1 + y_ofs)) // get target position
|
||||
.map(|target_pos| (target_pos, self.map.get(&target_pos))) // get value at that position
|
||||
.filter(|(_, val)| *val == Some(&(our_val + 1))) // only interested if it's our value + 1
|
||||
.map(|(pos, _)| pos) // discard the value
|
||||
.map(|pos| self.count_reachable_from(&pos, needle, visited))
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn count_paths_to(&self, pos: &(i64, i64), needle: u8) -> u64 {
|
||||
let our_val = self.map.get(pos).unwrap();
|
||||
if *our_val == needle {
|
||||
return 1;
|
||||
}
|
||||
[(-1, 0), (1, 0), (0, -1), (0, 1)] // left, right, up, down
|
||||
.iter()
|
||||
.map(|(x_ofs, y_ofs)| (pos.0 + x_ofs, pos.1 + y_ofs)) // get target position
|
||||
.map(|target_pos| (target_pos, self.map.get(&target_pos))) // get value at that position
|
||||
.filter(|(_, val)| *val == Some(&(our_val + 1))) // only interested if it's our value + 1
|
||||
.map(|(pos, _)| pos) // discard the value
|
||||
.map(|mov| self.count_paths_to(&mov, needle))
|
||||
.sum::<u64>()
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day10, part1)]
|
||||
pub fn part1(map: &TrailMap) -> u64 {
|
||||
map.trailheads()
|
||||
.iter()
|
||||
.map(|pos| {
|
||||
let mut visited = Grid::with_shape(map.map.width(), map.map.height(), false);
|
||||
map.count_reachable_from(pos, b'9', &mut visited)
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day10, part2)]
|
||||
pub fn part2(map: &TrailMap) -> u64 {
|
||||
map.trailheads()
|
||||
.iter()
|
||||
.map(|pos| map.count_paths_to(pos, b'9'))
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day10::*;
|
||||
|
||||
const EXAMPLE: &[u8] = b"89010123
|
||||
78121874
|
||||
87430965
|
||||
96549874
|
||||
45678903
|
||||
32019012
|
||||
01329801
|
||||
10456732";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&get_input(EXAMPLE)), 36);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&get_input(EXAMPLE)), 81);
|
||||
}
|
||||
}
|
103
src/day11.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::iter::repeat;
|
||||
|
||||
type IntType = u64;
|
||||
type CacheType = FxHashMap<Stone, IntType>;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
struct Stone(IntType);
|
||||
struct Stones(Vec<Stone>);
|
||||
|
||||
enum BlinkResult {
|
||||
One(Stone),
|
||||
Two(Stone, Stone),
|
||||
}
|
||||
|
||||
impl From<&str> for Stones {
|
||||
fn from(input: &str) -> Self {
|
||||
Stones(
|
||||
input
|
||||
.split_ascii_whitespace()
|
||||
.map(|v| Stone(v.parse().unwrap()))
|
||||
.collect_vec(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Stones {
|
||||
Stones::from(input)
|
||||
}
|
||||
|
||||
impl Stone {
|
||||
fn blink_once(&self) -> BlinkResult {
|
||||
let n_digits = if self.0 == 0 { 1 } else { self.0.ilog10() + 1 };
|
||||
if self.0 == 0 {
|
||||
BlinkResult::One(Stone(1))
|
||||
} else if n_digits % 2 == 0 {
|
||||
let split_factor = (10 as IntType).pow(n_digits / 2);
|
||||
let parts = (self.0 / split_factor, self.0 % split_factor);
|
||||
BlinkResult::Two(Stone(parts.0), Stone(parts.1))
|
||||
} else {
|
||||
BlinkResult::One(Stone(&self.0 * 2024))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn count_blinks(stone: &Stone, blink: usize, cache: &mut Vec<CacheType>) -> IntType {
|
||||
if cache[blink].contains_key(stone) {
|
||||
return cache[blink][stone];
|
||||
}
|
||||
let stones = stone.blink_once();
|
||||
let result = if blink == 0 {
|
||||
match stones {
|
||||
BlinkResult::One(_) => 1,
|
||||
BlinkResult::Two(_, _) => 2,
|
||||
}
|
||||
} else {
|
||||
match stones {
|
||||
BlinkResult::One(s) => count_blinks(&s, blink - 1, cache),
|
||||
BlinkResult::Two(s1, s2) => count_blinks(&s1, blink - 1, cache) + count_blinks(&s2, blink - 1, cache),
|
||||
}
|
||||
};
|
||||
cache[blink].insert(stone.clone(), result);
|
||||
cache[blink][stone]
|
||||
}
|
||||
|
||||
fn blink_stones(stones: Stones, blinks: usize) -> IntType {
|
||||
let mut cache = Vec::from_iter(repeat(CacheType::default()).take(blinks));
|
||||
stones
|
||||
.0
|
||||
.iter()
|
||||
.map(|stone| count_blinks(stone, blinks - 1, &mut cache))
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[aoc(day11, part1)]
|
||||
pub fn part1(input: &str) -> IntType {
|
||||
let stones = parse(input);
|
||||
blink_stones(stones, 25)
|
||||
}
|
||||
|
||||
#[aoc(day11, part2)]
|
||||
pub fn part2(input: &str) -> IntType {
|
||||
let stones = parse(input);
|
||||
blink_stones(stones, 75)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
pub const EXAMPLE: &str = "125 17";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE), 55312);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE), 65601038650482);
|
||||
}
|
||||
}
|
176
src/day12.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use aoc_runner_derive::aoc;
|
||||
use grid::{Coord2d, Grid};
|
||||
|
||||
pub struct Farm {
|
||||
map: Grid<u8>,
|
||||
}
|
||||
|
||||
impl FromStr for Farm {
|
||||
type Err = <Grid<u8> as FromStr>::Err;
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self { map: input.parse()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl Farm {
|
||||
fn compute_region(&self, pos: &Coord2d, visited: &mut Grid<bool>) -> (u64, u64) {
|
||||
let our_plant = self.map.get(pos).unwrap();
|
||||
|
||||
visited.set(pos, true);
|
||||
|
||||
[(-1i64, 0i64), (1, 0), (0, -1), (0, 1)]
|
||||
.map(|ofs| pos + ofs)
|
||||
.iter()
|
||||
.fold((1, 0), |(area, perimeter), adj| {
|
||||
match self.map.get(adj) {
|
||||
Some(plant) if plant == our_plant => {
|
||||
if visited.get(adj) == Some(&false) {
|
||||
// add the perimeter of the growth from there if not visited yet
|
||||
let (add_area, add_perimeter) = self.compute_region(adj, visited);
|
||||
(area + add_area, perimeter + add_perimeter)
|
||||
} else {
|
||||
(area, perimeter)
|
||||
}
|
||||
}
|
||||
Some(_) | None => (area, perimeter + 1),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn regions_cost(&self) -> u64 {
|
||||
let mut visited = Grid::with_shape(self.map.width(), self.map.height(), false);
|
||||
let mut cost = 0;
|
||||
for y in 0..self.map.height() {
|
||||
for x in 0..self.map.width() {
|
||||
cost += match visited.get(&(x, y)) {
|
||||
Some(false) => {
|
||||
let (area, perim) = self.compute_region(
|
||||
&Coord2d {
|
||||
x: x as i64,
|
||||
y: y as i64,
|
||||
},
|
||||
&mut visited,
|
||||
);
|
||||
area * perim
|
||||
}
|
||||
Some(_) | None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
cost
|
||||
}
|
||||
fn count_corners(&self, pos: &Coord2d) -> u64 {
|
||||
// NOTE: Iterating twice is faster than combining conditions in one pass
|
||||
// BAB
|
||||
// AAA has 4 inside corners (pos at centre). check that for AA A's exist and B doesn't for each rotation
|
||||
// BAB AB
|
||||
let our_plant = self.map.get(pos);
|
||||
let inside_corners = [(1i64, 1i64), (-1, 1), (1, -1), (-1, -1)]
|
||||
.iter()
|
||||
.filter(|inside_corner| {
|
||||
self.map.get(&(pos + **inside_corner)) != our_plant
|
||||
&& self.map.get(&(pos + (inside_corner.0, 0))) == our_plant
|
||||
&& self.map.get(&(pos + (0, inside_corner.1))) == our_plant
|
||||
})
|
||||
.count();
|
||||
// BBB
|
||||
// BAB has 4 outside corners (pos at centre). check that for AB the B are both not equal to A for each rot
|
||||
// BBB BB B
|
||||
let outside_corners = [(1i64, 1i64), (-1, 1), (1, -1), (-1, -1)]
|
||||
.iter()
|
||||
.filter(|outside_corner| {
|
||||
self.map.get(&(pos + (outside_corner.0, 0))) != our_plant
|
||||
&& self.map.get(&(pos + (0, outside_corner.1))) != our_plant
|
||||
})
|
||||
.count();
|
||||
(inside_corners + outside_corners) as u64
|
||||
}
|
||||
fn region_corners(&self, pos: &Coord2d, visited: &mut Grid<bool>) -> (u64, u64) {
|
||||
let our_plant = self.map.get(pos).unwrap();
|
||||
|
||||
visited.set(pos, true);
|
||||
|
||||
[(-1i64, 0i64), (1, 0), (0, -1), (0, 1)]
|
||||
.map(|ofs| pos + ofs)
|
||||
.iter()
|
||||
.fold((1, self.count_corners(pos)), |(area, corners), adj| {
|
||||
match self.map.get(adj) {
|
||||
Some(plant) if plant == our_plant => {
|
||||
if visited.get(adj) == Some(&false) {
|
||||
// add the perimeter of the growth from there if not visited yet
|
||||
let (n_area, n_corners) = self.region_corners(adj, visited);
|
||||
(area + n_area, corners + n_corners)
|
||||
} else {
|
||||
(area, corners)
|
||||
}
|
||||
}
|
||||
Some(_) | None => (area, corners),
|
||||
}
|
||||
})
|
||||
}
|
||||
fn regions_discount_cost(&self) -> u64 {
|
||||
let mut visited = self.map.same_shape(false);
|
||||
let mut cost = 0;
|
||||
for y in 0..self.map.height() {
|
||||
for x in 0..self.map.width() {
|
||||
cost += match visited.get(&(x, y)) {
|
||||
Some(false) => {
|
||||
let (area, corners) = self.region_corners(
|
||||
&Coord2d {
|
||||
x: x as i64,
|
||||
y: y as i64,
|
||||
},
|
||||
&mut visited,
|
||||
);
|
||||
area * corners
|
||||
}
|
||||
Some(_) | None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
cost
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Farm {
|
||||
input.parse().unwrap()
|
||||
}
|
||||
|
||||
#[aoc(day12, part1)]
|
||||
pub fn part1(input: &str) -> u64 {
|
||||
let farm = parse(input);
|
||||
farm.regions_cost()
|
||||
}
|
||||
|
||||
#[aoc(day12, part2)]
|
||||
pub fn part2(input: &str) -> u64 {
|
||||
let farm = parse(input);
|
||||
farm.regions_discount_cost()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "RRRRIICCFF
|
||||
RRRRIICCCF
|
||||
VVRRRCCFFF
|
||||
VVRCCCJFFF
|
||||
VVVVCJJCFE
|
||||
VVIVCCJJEE
|
||||
VVIIICJJEE
|
||||
MIIIIIJJEE
|
||||
MIIISIJEEE
|
||||
MMMISSJEEE";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE), 1930);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE), 1206);
|
||||
}
|
||||
}
|
135
src/day13.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use std::str::Lines;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MachineAction(i64, i64);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ClawMachine {
|
||||
button_a: MachineAction,
|
||||
button_b: MachineAction,
|
||||
prize: (i64, i64),
|
||||
}
|
||||
|
||||
impl ClawMachine {
|
||||
fn consume_from_input(input: &mut Lines) -> Option<Self> {
|
||||
// consume any empty lines at the front
|
||||
let ofs_re = Regex::new(r"X([+-]\d+), Y([+-]\d+)").unwrap();
|
||||
let prize_re = Regex::new(r"X=(\d+), Y=(\d+)").unwrap();
|
||||
// consume 3 lines - a, b, prize
|
||||
if let Some((a_line, b_line, prize_line)) = input.filter(|l| !l.is_empty()).take(3).collect_tuple() {
|
||||
let a_caps = ofs_re.captures(a_line).unwrap();
|
||||
let b_caps = ofs_re.captures(b_line).unwrap();
|
||||
let prize_caps = prize_re.captures(prize_line).unwrap();
|
||||
let button_a = MachineAction(
|
||||
a_caps.get(1).unwrap().as_str().parse().unwrap(),
|
||||
a_caps.get(2).unwrap().as_str().parse().unwrap(),
|
||||
);
|
||||
let button_b = MachineAction(
|
||||
b_caps.get(1).unwrap().as_str().parse().unwrap(),
|
||||
b_caps.get(2).unwrap().as_str().parse().unwrap(),
|
||||
);
|
||||
let prize = (
|
||||
prize_caps.get(1).unwrap().as_str().parse().unwrap(),
|
||||
prize_caps.get(2).unwrap().as_str().parse().unwrap(),
|
||||
);
|
||||
Some(Self {
|
||||
button_a,
|
||||
button_b,
|
||||
prize,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn cost(moves: (i64, i64)) -> i64 {
|
||||
moves.0 * 3 + moves.1
|
||||
}
|
||||
fn cheapest_prize(&self) -> Option<i64> {
|
||||
let remainder_a = (self.button_b.0 * self.prize.1 - self.button_b.1 * self.prize.0)
|
||||
% (self.button_b.0 * self.button_a.1 - self.button_a.0 * self.button_b.1);
|
||||
let a = (self.button_b.0 * self.prize.1 - self.button_b.1 * self.prize.0)
|
||||
/ (self.button_b.0 * self.button_a.1 - self.button_a.0 * self.button_b.1);
|
||||
let remainder_b = (self.prize.0 - (self.button_a.0 * a)) % self.button_b.0;
|
||||
let b = (self.prize.0 - (self.button_a.0 * a)) / self.button_b.0;
|
||||
if remainder_a == 0 && remainder_b == 0 {
|
||||
Some(Self::cost((a, b)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn offset(&mut self, offset: i64) {
|
||||
self.prize = (self.prize.0 + offset, self.prize.1 + offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ClawMachines {
|
||||
machines: Vec<ClawMachine>,
|
||||
}
|
||||
|
||||
impl From<&str> for ClawMachines {
|
||||
fn from(input: &str) -> Self {
|
||||
let mut machines = Vec::new();
|
||||
let mut lines = input.lines();
|
||||
while let Some(machine) = ClawMachine::consume_from_input(&mut lines) {
|
||||
machines.push(machine);
|
||||
}
|
||||
Self { machines }
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> ClawMachines {
|
||||
ClawMachines::from(input)
|
||||
}
|
||||
|
||||
#[aoc(day13, part1)]
|
||||
fn part1(input: &str) -> i64 {
|
||||
let machines = parse(input);
|
||||
machines.machines.iter().filter_map(|m| m.cheapest_prize()).sum()
|
||||
}
|
||||
|
||||
#[aoc(day13, part2)]
|
||||
fn part2(input: &str) -> i64 {
|
||||
let mut machines = parse(input);
|
||||
machines
|
||||
.machines
|
||||
.iter_mut()
|
||||
.filter_map(|m| {
|
||||
m.offset(10000000000000);
|
||||
m.cheapest_prize()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "Button A: X+94, Y+34
|
||||
Button B: X+22, Y+67
|
||||
Prize: X=8400, Y=5400
|
||||
|
||||
Button A: X+26, Y+66
|
||||
Button B: X+67, Y+21
|
||||
Prize: X=12748, Y=12176
|
||||
|
||||
Button A: X+17, Y+86
|
||||
Button B: X+84, Y+37
|
||||
Prize: X=7870, Y=6450
|
||||
|
||||
Button A: X+69, Y+23
|
||||
Button B: X+27, Y+71
|
||||
Prize: X=18641, Y=10279";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE), 480);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE), 875318608908);
|
||||
}
|
||||
}
|
178
src/day14.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use colored::Colorize;
|
||||
use grid::{AsCoord2d, Grid};
|
||||
use misc::CustomWrapped;
|
||||
use nom::{
|
||||
bytes::complete::tag,
|
||||
character::complete::digit1,
|
||||
combinator::{map_res, opt, recognize},
|
||||
sequence::{preceded, separated_pair},
|
||||
IResult,
|
||||
};
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
type Coord = (CustomWrapped<i64>, CustomWrapped<i64>);
|
||||
struct Robot {
|
||||
pos: Coord,
|
||||
vel: (i64, i64),
|
||||
}
|
||||
|
||||
struct Robots {
|
||||
robots: Vec<Robot>,
|
||||
width: i64,
|
||||
height: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum Quadrant {
|
||||
NW = 0,
|
||||
NE = 1,
|
||||
SW = 2,
|
||||
SE = 3,
|
||||
}
|
||||
|
||||
fn nom_i64(input: &str) -> IResult<&str, i64> {
|
||||
let (i, number) = map_res(recognize(preceded(opt(tag("-")), digit1)), i64::from_str)(input)?;
|
||||
Ok((i, number))
|
||||
}
|
||||
fn nom_i64_pair(input: &str) -> IResult<&str, (i64, i64)> {
|
||||
let (i, pair) = separated_pair(nom_i64, tag(","), nom_i64)(input)?;
|
||||
Ok((i, pair))
|
||||
}
|
||||
|
||||
impl Robot {
|
||||
fn from_str(s: &str, bounds: (i64, i64)) -> Self {
|
||||
let (s, pos) = preceded(tag("p="), nom_i64_pair)(s).unwrap();
|
||||
let (_, vel) = preceded(tag(" v="), nom_i64_pair)(s).unwrap();
|
||||
Self {
|
||||
pos: (CustomWrapped::new(pos.0, bounds.0), CustomWrapped::new(pos.1, bounds.1)),
|
||||
vel,
|
||||
}
|
||||
}
|
||||
fn step(&mut self, count: i64) {
|
||||
self.pos.0 += self.vel.x() * count;
|
||||
self.pos.1 += self.vel.y() * count;
|
||||
}
|
||||
fn quad(&self, bounds: (i64, i64)) -> Option<Quadrant> {
|
||||
let splits = (bounds.0 / 2, bounds.1 / 2);
|
||||
if self.pos.0 < splits.0 && self.pos.1 < splits.1 {
|
||||
Some(Quadrant::NW)
|
||||
} else if self.pos.0 > splits.0 && self.pos.1 < splits.1 {
|
||||
Some(Quadrant::NE)
|
||||
} else if self.pos.0 < splits.0 && self.pos.1 > splits.1 {
|
||||
Some(Quadrant::SW)
|
||||
} else if self.pos.0 > splits.0 && self.pos.1 > splits.1 {
|
||||
Some(Quadrant::SE)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Robots {
|
||||
fn from_vec(robots: Vec<Robot>, width: i64, height: i64) -> Self {
|
||||
Self { robots, width, height }
|
||||
}
|
||||
fn as_grid(&self) -> Grid<usize> {
|
||||
let mut grid = Grid::with_shape(self.width as usize, self.height as usize, 0usize);
|
||||
for r in &self.robots {
|
||||
grid.increment(&(r.pos.0.val, r.pos.1.val), 1usize);
|
||||
}
|
||||
grid
|
||||
}
|
||||
fn count_quads(&self) -> [u64; 4] {
|
||||
let mut counts = [0; 4];
|
||||
for r in &self.robots {
|
||||
if let Some(q) = r.quad((self.width, self.height)) {
|
||||
counts[q as usize] += 1
|
||||
}
|
||||
}
|
||||
counts
|
||||
}
|
||||
fn step(&mut self, count: i64) {
|
||||
for robot in &mut self.robots {
|
||||
robot.step(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Robots {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let grid = self.as_grid();
|
||||
for row in 0..grid.height() {
|
||||
for col in 0..grid.width() {
|
||||
if *grid.get(&(col, row)).unwrap() != 0 {
|
||||
"█".green().fmt(f)?;
|
||||
} else {
|
||||
" ".color(colored::Color::Black).fmt(f)?;
|
||||
}
|
||||
}
|
||||
writeln!(f)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str, width: i64, height: i64) -> Vec<Robot> {
|
||||
input.lines().map(|l| Robot::from_str(l, (width, height))).collect()
|
||||
}
|
||||
|
||||
fn part1_impl(input: &str, width: i64, height: i64) -> u64 {
|
||||
let mut robots = Robots::from_vec(parse(input, width, height), width, height);
|
||||
robots.step(100);
|
||||
let counts = robots.count_quads();
|
||||
counts.iter().product()
|
||||
}
|
||||
|
||||
#[aoc(day14, part1)]
|
||||
pub fn part1(input: &str) -> u64 {
|
||||
part1_impl(input, 101, 103)
|
||||
}
|
||||
|
||||
#[aoc(day14, part2)]
|
||||
pub fn part2(input: &str) -> u64 {
|
||||
let width = 101;
|
||||
let height = 103;
|
||||
let mut robots = Robots::from_vec(parse(input, width, height), width, height);
|
||||
for i in 1.. {
|
||||
robots.step(1);
|
||||
// collect into lines
|
||||
let g = robots.as_grid();
|
||||
if g.data
|
||||
.chunk_by(|a, b| *a != 0 && *b != 0)
|
||||
.filter(|c| !c.is_empty() && c[0] != 0)
|
||||
.any(|c| c.len() > width as usize / 10)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "p=0,4 v=3,-3
|
||||
p=6,3 v=-1,-3
|
||||
p=10,3 v=-1,2
|
||||
p=2,0 v=2,-1
|
||||
p=0,0 v=1,3
|
||||
p=3,0 v=-2,-2
|
||||
p=7,6 v=-1,-3
|
||||
p=3,0 v=-1,-2
|
||||
p=9,3 v=2,3
|
||||
p=7,3 v=-1,2
|
||||
p=2,4 v=2,-3
|
||||
p=9,5 v=-3,-3";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1_impl(EXAMPLE, 11, 7), 12);
|
||||
}
|
||||
|
||||
// part 2 does not converge using the test vector
|
||||
// #[test]
|
||||
// fn part2_example() {
|
||||
// // assert_eq!(part2(EXAMPLE), 0);
|
||||
// }
|
||||
}
|
241
src/day15.rs
Normal file
@ -0,0 +1,241 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use grid::{AsCoord2d, Coord2d, Grid};
|
||||
use itertools::Itertools;
|
||||
use std::{fmt::Display, io::Cursor, str::FromStr};
|
||||
|
||||
struct Warehouse {
|
||||
map: Grid<u8>,
|
||||
robot_pos: Coord2d,
|
||||
}
|
||||
|
||||
impl Display for Warehouse {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.map.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Warehouse {
|
||||
fn step_robot(&mut self, dir: Move) {
|
||||
let start = self.robot_pos;
|
||||
if self.push(&start, &dir) {
|
||||
self.robot_pos = &self.robot_pos + dir.ofs();
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, pos: &Coord2d, dir: &Move) -> bool {
|
||||
if self.can_push(pos, dir) {
|
||||
let target = pos + dir.ofs();
|
||||
match self.map.get(&target).unwrap() {
|
||||
b'#' => {}
|
||||
b'.' => self.map.swap(target, pos),
|
||||
b'O' => {
|
||||
self.push(&target, dir);
|
||||
self.map.swap(target, pos);
|
||||
}
|
||||
b'[' | b']' if *dir == Move::Left || *dir == Move::Right => {
|
||||
self.push(&target, dir);
|
||||
self.map.swap(target, pos)
|
||||
}
|
||||
b']' => {
|
||||
// move both parts
|
||||
self.push(&target, dir);
|
||||
self.push(&(&target + (-1, 0)), dir);
|
||||
self.map.swap(target, pos);
|
||||
}
|
||||
b'[' => {
|
||||
self.push(&target, dir);
|
||||
self.push(&(&target + (1, 0)), dir);
|
||||
self.map.swap(target, pos);
|
||||
}
|
||||
c => panic!("unexpected char {}", c),
|
||||
}
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn can_push(&mut self, pos: &Coord2d, dir: &Move) -> bool {
|
||||
let target = pos + dir.ofs();
|
||||
match self.map.get(&target).unwrap() {
|
||||
b'#' => false,
|
||||
b'.' => true,
|
||||
b'O' => self.can_push(&target, dir),
|
||||
b'[' | b']' if *dir == Move::Left || *dir == Move::Right => self.can_push(&target, dir),
|
||||
b']' => self.can_push(&target, dir) && self.can_push(&(&target + (-1, 0)), dir),
|
||||
b'[' => self.can_push(&target, dir) && self.can_push(&(&target + (1, 0)), dir),
|
||||
c => panic!("unexpected char {}", c),
|
||||
}
|
||||
}
|
||||
|
||||
fn embiggen(&mut self) {
|
||||
let new_lines = (0..self.map.height())
|
||||
.map(|r| self.map.row(r as i64).unwrap())
|
||||
.map(|row| {
|
||||
row.iter()
|
||||
.flat_map(|c| match c {
|
||||
b'#' => ['#', '#'],
|
||||
b'O' => ['[', ']'],
|
||||
b'.' => ['.', '.'],
|
||||
b'@' => ['@', '.'],
|
||||
c => panic!("unexpected character {}", c),
|
||||
})
|
||||
.collect::<String>()
|
||||
})
|
||||
.join("\n");
|
||||
self.map = Grid::from(Cursor::new(new_lines.as_str()));
|
||||
self.robot_pos = self.map.find(&b'@').unwrap().to_coord();
|
||||
}
|
||||
|
||||
fn score(&self) -> i64 {
|
||||
self.map
|
||||
.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, v)| **v == b'O' || **v == b'[')
|
||||
.map(|(i, _)| self.map.coord(i as i64).unwrap().y() * 100 + self.map.coord(i as i64).unwrap().x())
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum Move {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
impl Display for Move {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Left => f.write_str("Left"),
|
||||
Self::Right => f.write_str("Right"),
|
||||
Self::Up => f.write_str("Up"),
|
||||
Self::Down => f.write_str("Down"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for Move {
|
||||
fn from(c: char) -> Self {
|
||||
match c {
|
||||
'<' => Self::Left,
|
||||
'>' => Self::Right,
|
||||
'^' => Self::Up,
|
||||
'v' => Self::Down,
|
||||
c => panic!("invalid move {}", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Move {
|
||||
fn ofs(&self) -> (i64, i64) {
|
||||
match self {
|
||||
Move::Left => (-1, 0),
|
||||
Move::Right => (1, 0),
|
||||
Move::Up => (0, -1),
|
||||
Move::Down => (0, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MovePlan(Vec<Move>);
|
||||
|
||||
impl FromStr for MovePlan {
|
||||
type Err = Box<dyn std::error::Error>;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(MovePlan(s.chars().filter(|c| *c != '\n').map(Move::from).collect()))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> (Warehouse, MovePlan) {
|
||||
let lines = input.lines().collect_vec();
|
||||
let parts = lines.split(|l| l.is_empty()).map(|ls| ls.join("\n")).collect_vec();
|
||||
let map: Grid<u8> = parts[0].parse().unwrap();
|
||||
let wh = Warehouse {
|
||||
robot_pos: map.find(&b'@').unwrap().to_coord(),
|
||||
map,
|
||||
};
|
||||
let moves = parts[1].parse().unwrap();
|
||||
|
||||
(wh, moves)
|
||||
}
|
||||
|
||||
#[aoc(day15, part1)]
|
||||
pub fn part1(input: &str) -> i64 {
|
||||
let (mut wh, moves) = parse(input);
|
||||
for m in moves.0 {
|
||||
wh.step_robot(m);
|
||||
}
|
||||
wh.score()
|
||||
}
|
||||
|
||||
#[aoc(day15, part2)]
|
||||
pub fn part2(input: &str) -> i64 {
|
||||
let (mut wh, moves) = parse(input);
|
||||
wh.embiggen();
|
||||
|
||||
for m in moves.0 {
|
||||
wh.step_robot(m);
|
||||
}
|
||||
wh.score()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE1: &str = "########
|
||||
#..O.O.#
|
||||
##@.O..#
|
||||
#...O..#
|
||||
#.#.O..#
|
||||
#...O..#
|
||||
#......#
|
||||
########
|
||||
|
||||
<^^>>>vv<v>>v<<";
|
||||
const EXAMPLE2: &str = "##########
|
||||
#..O..O.O#
|
||||
#......O.#
|
||||
#.OO..O.O#
|
||||
#..O@..O.#
|
||||
#O#..O...#
|
||||
#O..O..O.#
|
||||
#.OO.O.OO#
|
||||
#....O...#
|
||||
##########
|
||||
|
||||
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
|
||||
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
|
||||
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
|
||||
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
|
||||
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
|
||||
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
|
||||
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
|
||||
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
|
||||
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
|
||||
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^";
|
||||
|
||||
const EXAMPLE3: &str = "#######
|
||||
#...#.#
|
||||
#.....#
|
||||
#..OO@#
|
||||
#..O..#
|
||||
#.....#
|
||||
#######
|
||||
|
||||
<vv<<^^<<^^";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE1), 2028);
|
||||
assert_eq!(part1(EXAMPLE2), 10092);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE3), 618);
|
||||
assert_eq!(part2(EXAMPLE2), 9021);
|
||||
}
|
||||
}
|
294
src/day16.rs
Normal file
@ -0,0 +1,294 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use grid::{Coord2d, Grid};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BinaryHeap, HashMap},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
type CoordType = i16;
|
||||
type Coord = (CoordType, CoordType);
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
|
||||
enum FacingDirection {
|
||||
East,
|
||||
South,
|
||||
West,
|
||||
North,
|
||||
}
|
||||
|
||||
impl FacingDirection {
|
||||
fn ofs(&self) -> (CoordType, CoordType) {
|
||||
match self {
|
||||
FacingDirection::East => (1, 0),
|
||||
FacingDirection::South => (0, 1),
|
||||
FacingDirection::West => (-1, 0),
|
||||
FacingDirection::North => (0, -1),
|
||||
}
|
||||
}
|
||||
fn reachable(&self) -> &[FacingDirection; 3] {
|
||||
// Can move perpendicularly or the same direction, backwards would always increase path cost
|
||||
match self {
|
||||
FacingDirection::East => &[FacingDirection::East, FacingDirection::North, FacingDirection::South],
|
||||
FacingDirection::West => &[FacingDirection::West, FacingDirection::North, FacingDirection::South],
|
||||
FacingDirection::South => &[FacingDirection::South, FacingDirection::East, FacingDirection::West],
|
||||
FacingDirection::North => &[FacingDirection::North, FacingDirection::East, FacingDirection::West],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
struct State {
|
||||
cost: usize,
|
||||
position: Coord,
|
||||
facing: FacingDirection,
|
||||
}
|
||||
|
||||
impl Ord for State {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
other
|
||||
.cost
|
||||
.cmp(&self.cost)
|
||||
.then_with(|| self.position.cmp(&other.position))
|
||||
.then_with(|| self.facing.cmp(&other.facing))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for State {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
struct PathState {
|
||||
state: State,
|
||||
path: Vec<Coord>,
|
||||
}
|
||||
|
||||
impl Ord for PathState {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.state.cmp(&other.state)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for PathState {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
struct Maze {
|
||||
map: Grid<u8>,
|
||||
}
|
||||
|
||||
impl FromStr for Maze {
|
||||
type Err = Box<dyn std::error::Error>;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let map: Grid<u8> = s.parse()?;
|
||||
|
||||
Ok(Self { map })
|
||||
}
|
||||
}
|
||||
|
||||
impl Maze {
|
||||
fn valid_moves<'a>(&'a self, state: &'a State) -> impl Iterator<Item = State> + use<'a> {
|
||||
let reachable = state.facing.reachable();
|
||||
reachable
|
||||
.iter()
|
||||
.map(|dir| (dir, (state.position.0 + dir.ofs().0, state.position.1 + dir.ofs().1)))
|
||||
.filter(|(_, pos)| self.map.get(pos).is_some_and(|c| *c != b'#'))
|
||||
.map(|(dir, pos)| State {
|
||||
facing: *dir,
|
||||
position: pos,
|
||||
cost: if *dir == state.facing {
|
||||
state.cost + 1
|
||||
} else {
|
||||
state.cost + 1001
|
||||
},
|
||||
})
|
||||
}
|
||||
fn dijkstra(&self) -> usize {
|
||||
let Coord2d {x: start_x, y: start_y} = self.map.find(&b'S').expect("can't find start");
|
||||
let start = (start_x as CoordType, start_y as CoordType);
|
||||
|
||||
let Coord2d {x: finish_x, y: finish_y} = self.map.find(&b'E').expect("can't find finish");
|
||||
let finish = (finish_x as CoordType, finish_y as CoordType);
|
||||
|
||||
let mut distances = HashMap::new();
|
||||
let mut queue = BinaryHeap::new();
|
||||
|
||||
distances.insert((start, FacingDirection::East), 0);
|
||||
queue.push(State {
|
||||
cost: 0,
|
||||
position: start,
|
||||
facing: FacingDirection::East,
|
||||
});
|
||||
|
||||
while let Some(state) = queue.pop() {
|
||||
if state.position == finish {
|
||||
return state.cost;
|
||||
}
|
||||
|
||||
if distances
|
||||
.get(&(state.position, state.facing))
|
||||
.is_some_and(|v| state.cost > *v)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for new_state in self.valid_moves(&state) {
|
||||
if distances
|
||||
.get(&(new_state.position, new_state.facing))
|
||||
.is_none_or(|best_cost| new_state.cost < *best_cost)
|
||||
{
|
||||
distances.insert((new_state.position, new_state.facing), new_state.cost);
|
||||
queue.push(new_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
usize::MAX
|
||||
}
|
||||
fn path_dijkstra(&mut self) -> (usize, Vec<Vec<Coord>>) {
|
||||
let Coord2d {x: start_x, y: start_y} = self.map.find(&b'S').expect("can't find start");
|
||||
let start = (start_x as CoordType, start_y as CoordType);
|
||||
|
||||
let Coord2d {x: finish_x, y: finish_y} = self.map.find(&b'E').expect("can't find finish");
|
||||
let finish = (finish_x as CoordType, finish_y as CoordType);
|
||||
|
||||
let mut distances = HashMap::new();
|
||||
let mut queue = BinaryHeap::with_capacity(self.map.data.len());
|
||||
let mut best_paths = Vec::new();
|
||||
let mut best_cost = usize::MAX;
|
||||
|
||||
distances.insert((start, FacingDirection::East), 0);
|
||||
queue.push(PathState {
|
||||
state: State {
|
||||
cost: 0,
|
||||
position: start,
|
||||
facing: FacingDirection::East,
|
||||
},
|
||||
path: Vec::new(),
|
||||
});
|
||||
|
||||
while let Some(PathState { state, mut path }) = queue.pop() {
|
||||
if distances
|
||||
.get(&(state.position, state.facing))
|
||||
.is_some_and(|v| state.cost > *v)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if state.position == finish {
|
||||
match state.cost.cmp(&best_cost) {
|
||||
Ordering::Less => {
|
||||
path.push(state.position);
|
||||
best_paths.clear();
|
||||
best_paths.push(path);
|
||||
best_cost = state.cost
|
||||
}
|
||||
Ordering::Equal => {
|
||||
path.push(state.position);
|
||||
best_paths.push(path);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for new_state in self.valid_moves(&state) {
|
||||
if distances
|
||||
.get(&(new_state.position, new_state.facing))
|
||||
.is_none_or(|best_cost| new_state.cost <= *best_cost)
|
||||
{
|
||||
let mut new_path = path.clone();
|
||||
new_path.push(state.position);
|
||||
distances.insert((new_state.position, new_state.facing), new_state.cost);
|
||||
queue.push(PathState {
|
||||
state: new_state,
|
||||
path: new_path,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
(best_cost, best_paths)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Maze {
|
||||
input.parse().unwrap()
|
||||
}
|
||||
|
||||
#[aoc(day16, part1)]
|
||||
pub fn part1(input: &str) -> usize {
|
||||
let maze = parse(input);
|
||||
maze.dijkstra()
|
||||
}
|
||||
|
||||
#[aoc(day16, part2)]
|
||||
pub fn part2(input: &str) -> usize {
|
||||
let mut maze = parse(input);
|
||||
let best_paths = maze.path_dijkstra();
|
||||
|
||||
let mut path_map = maze.map.same_shape(false);
|
||||
for tile in best_paths.1.iter().flatten() {
|
||||
path_map.set(tile, true);
|
||||
}
|
||||
path_map.count(&true)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE1: &str = "###############
|
||||
#.......#....E#
|
||||
#.#.###.#.###.#
|
||||
#.....#.#...#.#
|
||||
#.###.#####.#.#
|
||||
#.#.#.......#.#
|
||||
#.#.#####.###.#
|
||||
#...........#.#
|
||||
###.#.#####.#.#
|
||||
#...#.....#.#.#
|
||||
#.#.#.###.#.#.#
|
||||
#.....#...#.#.#
|
||||
#.###.#.#.#.#.#
|
||||
#S..#.....#...#
|
||||
###############";
|
||||
|
||||
const EXAMPLE2: &str = "#################
|
||||
#...#...#...#..E#
|
||||
#.#.#.#.#.#.#.#.#
|
||||
#.#.#.#...#...#.#
|
||||
#.#.#.#.###.#.#.#
|
||||
#...#.#.#.....#.#
|
||||
#.#.#.#.#.#####.#
|
||||
#.#...#.#.#.....#
|
||||
#.#.#####.#.###.#
|
||||
#.#.#.......#...#
|
||||
#.#.###.#####.###
|
||||
#.#.#...#.....#.#
|
||||
#.#.#.#####.###.#
|
||||
#.#.#.........#.#
|
||||
#.#.#.#########.#
|
||||
#S#.............#
|
||||
#################";
|
||||
|
||||
#[test]
|
||||
fn part1_example1() {
|
||||
assert_eq!(part1(EXAMPLE1), 7036);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part1_example2() {
|
||||
assert_eq!(part1(EXAMPLE2), 11048);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example1() {
|
||||
assert_eq!(part2(EXAMPLE1), 45);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example2() {
|
||||
assert_eq!(part2(EXAMPLE2), 64);
|
||||
}
|
||||
}
|
298
src/day17.rs
Normal file
@ -0,0 +1,298 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Register {
|
||||
A = 0,
|
||||
B = 1,
|
||||
C = 2,
|
||||
}
|
||||
impl From<usize> for Register {
|
||||
fn from(value: usize) -> Self {
|
||||
match value {
|
||||
0 => Register::A,
|
||||
1 => Register::B,
|
||||
2 => Register::C,
|
||||
v => panic!("invalid register address {}", v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Opcode {
|
||||
Adv = 0,
|
||||
Bxl = 1,
|
||||
Bst = 2,
|
||||
Jnz = 3,
|
||||
Bxc = 4,
|
||||
Out = 5,
|
||||
Bdv = 6,
|
||||
Cdv = 7,
|
||||
}
|
||||
|
||||
impl From<i64> for Opcode {
|
||||
fn from(value: i64) -> Self {
|
||||
match value {
|
||||
0 => Opcode::Adv,
|
||||
1 => Opcode::Bxl,
|
||||
2 => Opcode::Bst,
|
||||
3 => Opcode::Jnz,
|
||||
4 => Opcode::Bxc,
|
||||
5 => Opcode::Out,
|
||||
6 => Opcode::Bdv,
|
||||
7 => Opcode::Cdv,
|
||||
v => panic!("invalid opcode {}", v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Opcode {
|
||||
fn interp_operand(&self, value: i64) -> Operand {
|
||||
match self {
|
||||
Self::Adv | Self::Bst | Self::Out | Self::Bdv | Self::Cdv => Operand::new_combo(value),
|
||||
Self::Bxl | Self::Jnz => Operand::Literal(value),
|
||||
Self::Bxc => Operand::Ignore,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Operand {
|
||||
Literal(i64),
|
||||
Load(Register),
|
||||
Ignore,
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
fn new_combo(value: i64) -> Self {
|
||||
match value {
|
||||
0..=3 => Operand::Literal(value),
|
||||
4 => Operand::Load(Register::A),
|
||||
5 => Operand::Load(Register::B),
|
||||
6 => Operand::Load(Register::C),
|
||||
7 => panic!("reserved combo operand 7"),
|
||||
i => panic!("invalid combo operand {}", i),
|
||||
}
|
||||
}
|
||||
fn value(self, m: &Machine) -> i64 {
|
||||
match self {
|
||||
Self::Literal(i) => i,
|
||||
Self::Load(reg) => *m.registers.load(reg),
|
||||
Self::Ignore => panic!("can't read ignored operand"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RegisterFile<const SIZE: usize, T> {
|
||||
file: [T; SIZE],
|
||||
}
|
||||
|
||||
impl<T: Clone + From<i64>, const SIZE: usize> RegisterFile<SIZE, T> {
|
||||
fn load(&self, reg: Register) -> &T {
|
||||
&self.file[reg as usize]
|
||||
}
|
||||
fn store(&mut self, reg: Register, val: T) {
|
||||
self.file[reg as usize] = val;
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.file.fill(0.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Instruction {
|
||||
opcode: Opcode,
|
||||
operand: Operand,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
fn exec(&self, m: &mut Machine) {
|
||||
match self.opcode {
|
||||
Opcode::Adv => self.adv(m),
|
||||
Opcode::Bxl => self.bxl(m),
|
||||
Opcode::Bst => self.bst(m),
|
||||
Opcode::Jnz => self.jnz(m),
|
||||
Opcode::Bxc => self.bxc(m),
|
||||
Opcode::Out => self.out(m),
|
||||
Opcode::Bdv => self.bdv(m),
|
||||
Opcode::Cdv => self.cdv(m),
|
||||
}
|
||||
}
|
||||
fn adv(&self, m: &mut Machine) {
|
||||
let num = m.registers.load(Register::A);
|
||||
let denom = 1 << self.operand.value(m);
|
||||
m.registers.store(Register::A, num / denom);
|
||||
m.advance();
|
||||
}
|
||||
fn bxl(&self, m: &mut Machine) {
|
||||
let lhs = self.operand.value(m);
|
||||
let rhs = m.registers.load(Register::B);
|
||||
m.registers.store(Register::B, lhs ^ rhs);
|
||||
m.advance();
|
||||
}
|
||||
fn bst(&self, m: &mut Machine) {
|
||||
m.registers.store(Register::B, self.operand.value(m) % 8);
|
||||
m.advance();
|
||||
}
|
||||
fn jnz(&self, m: &mut Machine) {
|
||||
if *m.registers.load(Register::A) == 0 {
|
||||
m.advance();
|
||||
} else {
|
||||
m.jump(self.operand.value(m) as usize);
|
||||
}
|
||||
}
|
||||
fn bxc(&self, m: &mut Machine) {
|
||||
let lhs = m.registers.load(Register::B);
|
||||
let rhs = m.registers.load(Register::C);
|
||||
m.registers.store(Register::B, lhs ^ rhs);
|
||||
m.advance();
|
||||
}
|
||||
fn out(&self, m: &mut Machine) {
|
||||
m.out_file.push(self.operand.value(m) % 8);
|
||||
m.advance();
|
||||
}
|
||||
fn bdv(&self, m: &mut Machine) {
|
||||
let num = m.registers.load(Register::A);
|
||||
let denom = 1 << self.operand.value(m);
|
||||
m.registers.store(Register::B, num / denom);
|
||||
m.advance();
|
||||
}
|
||||
fn cdv(&self, m: &mut Machine) {
|
||||
let num = m.registers.load(Register::A);
|
||||
let denom = 1 << self.operand.value(m);
|
||||
m.registers.store(Register::C, num / denom);
|
||||
m.advance();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Machine {
|
||||
registers: RegisterFile<3, i64>,
|
||||
program: Vec<Instruction>,
|
||||
program_raw: Vec<i64>,
|
||||
ip: usize,
|
||||
out_file: Vec<i64>,
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
fn run(&mut self) {
|
||||
let program = self.program.clone();
|
||||
while let Some(inst) = program.get(self.ip) {
|
||||
inst.exec(self);
|
||||
}
|
||||
}
|
||||
fn advance(&mut self) {
|
||||
self.ip += 1;
|
||||
}
|
||||
fn jump(&mut self, addr: usize) {
|
||||
self.ip = addr;
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.registers.reset();
|
||||
self.ip = 0;
|
||||
self.out_file.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Machine {
|
||||
let reg_re = Regex::new(r"Register ([ABC]): (\d+)").unwrap();
|
||||
let prog_re = Regex::new(r"Program: ((\d+,)*\d+)").unwrap();
|
||||
|
||||
let mut registers: RegisterFile<3, i64> = RegisterFile { file: [0; 3] };
|
||||
let mut program = Vec::new();
|
||||
let mut program_raw = Vec::new();
|
||||
for line in input.lines() {
|
||||
if let Some(caps) = reg_re.captures(line) {
|
||||
let address = (caps[1].as_bytes()[0] - b'A') as usize;
|
||||
let value = caps[2].parse().unwrap();
|
||||
registers.store(address.into(), value);
|
||||
continue;
|
||||
}
|
||||
if let Some(caps) = prog_re.captures(line) {
|
||||
let instructions = caps[1].split(',');
|
||||
for (inst, operand) in instructions.tuples() {
|
||||
let opcode = inst.parse::<i64>().unwrap();
|
||||
let operand = operand.parse::<i64>().unwrap();
|
||||
program_raw.push(opcode);
|
||||
program_raw.push(operand);
|
||||
let opcode: Opcode = opcode.into();
|
||||
program.push(Instruction {
|
||||
operand: opcode.interp_operand(operand),
|
||||
opcode,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Machine {
|
||||
registers,
|
||||
program,
|
||||
program_raw,
|
||||
out_file: Vec::new(),
|
||||
ip: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[aoc(day17, part1)]
|
||||
pub fn part1(input: &str) -> String {
|
||||
let mut machine = parse(input);
|
||||
machine.run();
|
||||
machine.out_file.iter().map(|n| n.to_string()).join(",")
|
||||
}
|
||||
|
||||
// The program must be of the correct form to be solvable
|
||||
pub fn solve(m: &mut Machine, guess: i64, i: usize) -> Option<i64> {
|
||||
if i == m.program_raw.len() {
|
||||
return Some(guess);
|
||||
}
|
||||
let program_pos = m.program_raw.len() - 1 - i;
|
||||
let goal_digit = m.program_raw[program_pos];
|
||||
|
||||
for digit in 0..8 {
|
||||
let local_guess = (digit << (program_pos * 3)) + guess;
|
||||
m.reset();
|
||||
m.registers.store(Register::A, local_guess);
|
||||
m.run();
|
||||
if m.out_file.len() == m.program_raw.len() && m.out_file[program_pos] == goal_digit {
|
||||
if let Some(sol) = solve(m, local_guess, i + 1) {
|
||||
return Some(sol);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[aoc(day17, part2)]
|
||||
pub fn part2(input: &str) -> i64 {
|
||||
let mut machine = parse(input);
|
||||
|
||||
solve(&mut machine, 0, 0).expect("expected a solution")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE1: &str = "Register A: 729
|
||||
Register B: 0
|
||||
Register C: 0
|
||||
|
||||
Program: 0,1,5,4,3,0";
|
||||
|
||||
const EXAMPLE2: &str = "Register A: 2024
|
||||
Register B: 0
|
||||
Register C: 0
|
||||
|
||||
Program: 0,3,5,4,3,0";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE1), "4,6,3,5,6,3,5,2,1,0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE2), 117440);
|
||||
}
|
||||
}
|
286
src/day18.rs
Normal file
@ -0,0 +1,286 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use grid::Grid;
|
||||
use itertools::Itertools;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
collections::{BinaryHeap, VecDeque},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MemoryMap {
|
||||
map: Grid<bool>,
|
||||
byte_stream: Vec<(i64, i64)>,
|
||||
}
|
||||
|
||||
trait PathTrack {
|
||||
const DOES_WORK: bool = true;
|
||||
fn new() -> Self;
|
||||
fn push(&mut self, pos: (i64, i64));
|
||||
fn finalize(&mut self) {}
|
||||
}
|
||||
|
||||
struct LengthPath(usize);
|
||||
impl PathTrack for LengthPath {
|
||||
fn new() -> Self {
|
||||
LengthPath(0)
|
||||
}
|
||||
fn push(&mut self, _: (i64, i64)) {
|
||||
self.0 += 1
|
||||
}
|
||||
}
|
||||
|
||||
impl PathTrack for Vec<(i64, i64)> {
|
||||
fn new() -> Self {
|
||||
Vec::new()
|
||||
}
|
||||
fn push(&mut self, pos: (i64, i64)) {
|
||||
self.push(pos);
|
||||
}
|
||||
fn finalize(&mut self) {
|
||||
self.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
struct NoopTrack {}
|
||||
impl PathTrack for NoopTrack {
|
||||
const DOES_WORK: bool = false;
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
fn push(&mut self, _: (i64, i64)) {}
|
||||
}
|
||||
|
||||
impl MemoryMap {
|
||||
fn from_str(input: &str, width: usize, height: usize) -> Self {
|
||||
let map = Grid::with_shape(width, height, true);
|
||||
let mut byte_stream = Vec::new();
|
||||
|
||||
for line in input.lines() {
|
||||
if let Some((x, y)) = line.split_once(',') {
|
||||
let pos: (i64, i64) = (x.parse().unwrap(), y.parse().unwrap());
|
||||
byte_stream.push(pos);
|
||||
}
|
||||
}
|
||||
|
||||
Self { map, byte_stream }
|
||||
}
|
||||
|
||||
fn place_byte(&mut self, i: usize) {
|
||||
let pos = self.byte_stream[i];
|
||||
self.map.set(&pos, false);
|
||||
}
|
||||
|
||||
fn place_bytes(&mut self, start: usize, end: usize) {
|
||||
for i in start..=end {
|
||||
self.place_byte(i);
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_moves<'a>(&'a self, pos: &'a (i64, i64)) -> impl Iterator<Item = (i64, i64)> + 'a {
|
||||
([(0, 1), (1, 0), (0, -1), (-1, 0)])
|
||||
.iter()
|
||||
.filter(|ofs| self.map.get(&(pos.0 + ofs.0, pos.1 + ofs.1)).is_some_and(|v| *v))
|
||||
.map(|ofs| (pos.0 + ofs.0, pos.1 + ofs.1))
|
||||
}
|
||||
|
||||
fn bfs<T: PathTrack>(&self, start: (i64, i64)) -> Option<T> {
|
||||
let goal = (self.map.width() as i64 - 1, self.map.height() as i64 - 1);
|
||||
|
||||
let mut visited = self.map.same_shape(false);
|
||||
let mut prev = self.map.same_shape((i64::MAX, i64::MAX));
|
||||
let mut queue = VecDeque::new();
|
||||
|
||||
visited.set(&start, true);
|
||||
queue.push_back((0, start));
|
||||
|
||||
while let Some((depth, pos)) = queue.pop_front() {
|
||||
if pos == goal {
|
||||
if T::DOES_WORK {
|
||||
let mut visited_pos = goal;
|
||||
let mut path = T::new();
|
||||
path.push(pos);
|
||||
while let Some(next) = prev.get(&visited_pos) {
|
||||
visited_pos = *next;
|
||||
path.push(*next);
|
||||
if *next == start {
|
||||
path.finalize();
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Some(T::new());
|
||||
}
|
||||
}
|
||||
|
||||
// if visited.get(&pos).is_some_and(|v| *v) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
let moves = self.valid_moves(&pos);
|
||||
for new_pos in moves {
|
||||
if visited.get(&new_pos).is_none_or(|v| !v) {
|
||||
visited.set(&new_pos, true);
|
||||
if T::DOES_WORK {
|
||||
prev.set(&new_pos, pos);
|
||||
}
|
||||
queue.push_back((depth + 1, new_pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // will be moved to Grid at some point
|
||||
fn dijkstra<T: PathTrack>(&self, start: (i64, i64)) -> Option<T> {
|
||||
let goal = (self.map.width() as i64 - 1, self.map.height() as i64 - 1);
|
||||
|
||||
let mut costs = self.map.same_shape(i64::MAX);
|
||||
let mut prev = self.map.same_shape((i64::MAX, i64::MAX));
|
||||
let mut queue = BinaryHeap::new();
|
||||
|
||||
costs.set(&start, 0);
|
||||
queue.push((Reverse(0), start));
|
||||
|
||||
while let Some((cost, pos)) = queue.pop() {
|
||||
if pos == goal {
|
||||
if T::DOES_WORK {
|
||||
let mut visited_pos = goal;
|
||||
let mut path = T::new();
|
||||
path.push(pos);
|
||||
while let Some(next) = prev.get(&visited_pos) {
|
||||
visited_pos = *next;
|
||||
path.push(*next);
|
||||
if *next == start {
|
||||
path.finalize();
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Some(T::new());
|
||||
}
|
||||
}
|
||||
|
||||
if costs.get(&pos).is_some_and(|v| cost.0 > *v) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let moves = self.valid_moves(&pos);
|
||||
for new_pos in moves {
|
||||
if costs.get(&new_pos).is_none_or(|best_cost| cost.0 + 1 < *best_cost) {
|
||||
costs.set(&new_pos, cost.0 + 1);
|
||||
if T::DOES_WORK {
|
||||
prev.set(&new_pos, pos);
|
||||
}
|
||||
queue.push((Reverse(cost.0 + 1), new_pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn part1_impl(input: &str, width: usize, height: usize, initial_safe_byte_count: usize) -> usize {
|
||||
let mut map = MemoryMap::from_str(input, width, height);
|
||||
map.place_bytes(0, initial_safe_byte_count - 1);
|
||||
let path = map.bfs::<LengthPath>((0, 0)).expect("no path found");
|
||||
|
||||
path.0 - 1 // count edges, not visited nodes (start doesn't count)
|
||||
}
|
||||
|
||||
// My original devised solution
|
||||
pub fn part2_impl_brute(input: &str, width: usize, height: usize, initial_safe_byte_count: usize) -> (i64, i64) {
|
||||
let mut input_map = MemoryMap::from_str(input, width, height);
|
||||
input_map.place_bytes(0, initial_safe_byte_count - 1);
|
||||
|
||||
let mut path = input_map.bfs::<Vec<(i64, i64)>>((0, 0)).expect("no path found");
|
||||
|
||||
for byte in initial_safe_byte_count..input_map.byte_stream.len() {
|
||||
input_map.place_byte(byte);
|
||||
// If it obstructs our best path, we need to do a new path search
|
||||
if let Some((obs_at, _)) = path.iter().find_position(|v| *v == &input_map.byte_stream[byte]) {
|
||||
let (before, _) = path.split_at(obs_at);
|
||||
|
||||
if let Some(new_path) = input_map.bfs::<Vec<(i64, i64)>>(path[obs_at - 1]) {
|
||||
path = [before, &new_path].concat();
|
||||
} else {
|
||||
return input_map.byte_stream[byte];
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("no bytes block route");
|
||||
}
|
||||
|
||||
// Optimized based on others' ideas
|
||||
pub fn part2_impl(input: &str, width: usize, height: usize, initial_safe_byte_count: usize) -> (i64, i64) {
|
||||
let mut input_map = MemoryMap::from_str(input, width, height);
|
||||
|
||||
input_map.place_bytes(0, initial_safe_byte_count - 1);
|
||||
|
||||
// for the unplaced bytes, binary search for the partition point, given the predicate that a path is reachable
|
||||
// when all bytes up to that n have been placed
|
||||
let possible_problems = (initial_safe_byte_count..input_map.byte_stream.len()).collect_vec();
|
||||
let solution = possible_problems.partition_point(|byte| {
|
||||
// avoiding this clone by rolling back the byte placements instead is slower
|
||||
let mut local_map = input_map.clone();
|
||||
local_map.place_bytes(initial_safe_byte_count, *byte);
|
||||
local_map.bfs::<NoopTrack>((0, 0)).is_some()
|
||||
}) + initial_safe_byte_count;
|
||||
|
||||
input_map.byte_stream[solution]
|
||||
}
|
||||
|
||||
#[aoc(day18, part1)]
|
||||
pub fn part1(input: &str) -> usize {
|
||||
part1_impl(input, 71, 71, 1024)
|
||||
}
|
||||
|
||||
#[aoc(day18, part2)]
|
||||
pub fn part2(input: &str) -> String {
|
||||
let sol = part2_impl(input, 71, 71, 1024);
|
||||
format!("{},{}", sol.0, sol.1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "5,4
|
||||
4,2
|
||||
4,5
|
||||
3,0
|
||||
2,1
|
||||
6,3
|
||||
2,4
|
||||
1,5
|
||||
0,6
|
||||
3,3
|
||||
2,6
|
||||
5,1
|
||||
1,2
|
||||
5,5
|
||||
2,5
|
||||
6,5
|
||||
1,4
|
||||
0,4
|
||||
6,4
|
||||
1,1
|
||||
6,1
|
||||
1,0
|
||||
0,5
|
||||
1,6
|
||||
2,0";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1_impl(EXAMPLE, 7, 7, 12), 22);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2_impl(EXAMPLE, 7, 7, 12), (6, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example_brute() {
|
||||
assert_eq!(part2_impl_brute(EXAMPLE, 7, 7, 12,), (6, 1));
|
||||
}
|
||||
}
|
192
src/day19.rs
Normal file
@ -0,0 +1,192 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use itertools::Itertools;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
enum Stripe {
|
||||
White = b'w',
|
||||
Blue = b'u',
|
||||
Black = b'b',
|
||||
Red = b'r',
|
||||
Green = b'g',
|
||||
}
|
||||
|
||||
impl From<&u8> for Stripe {
|
||||
fn from(value: &u8) -> Self {
|
||||
match value {
|
||||
b'w' => Self::White,
|
||||
b'u' => Self::Blue,
|
||||
b'b' => Self::Black,
|
||||
b'r' => Self::Red,
|
||||
b'g' => Self::Green,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Stripe> for char {
|
||||
fn from(val: &Stripe) -> Self {
|
||||
let v = *val as u8;
|
||||
v.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Stripe {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_char(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Design {
|
||||
stripes: Vec<Stripe>,
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Design {
|
||||
fn from(input: &[u8]) -> Self {
|
||||
let stripes = input.iter().map(|c| c.into()).collect();
|
||||
Self { stripes }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Design {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for stripe in &self.stripes {
|
||||
f.write_char(stripe.into())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Onsen {
|
||||
towels: Vec<Design>,
|
||||
designs: Vec<Design>,
|
||||
}
|
||||
|
||||
impl Display for Onsen {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.towels.iter().join(", "))?;
|
||||
writeln!(f)?;
|
||||
writeln!(f)?;
|
||||
for d in &self.designs {
|
||||
d.fmt(f)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Onsen {
|
||||
fn possible(&self, d: &[Stripe]) -> bool {
|
||||
if d.is_empty() {
|
||||
return true;
|
||||
}
|
||||
for t in &self.towels {
|
||||
if d.starts_with(&t.stripes) && self.possible(d.split_at(t.stripes.len()).1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
// Count the ways to construct a given substring
|
||||
fn ways<'a>(
|
||||
&'a self,
|
||||
d: &'a [Stripe],
|
||||
mut cache: FxHashMap<&'a [Stripe], i64>,
|
||||
) -> (FxHashMap<&'a [Stripe], i64>, i64) {
|
||||
if d.is_empty() {
|
||||
return (cache, 1);
|
||||
}
|
||||
if cache.contains_key(d) {
|
||||
let val = cache[d];
|
||||
return (cache, val);
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
for t in &self.towels {
|
||||
if d.starts_with(&t.stripes) {
|
||||
let res_count;
|
||||
(cache, res_count) = self.ways(&d[t.stripes.len()..d.len()], cache);
|
||||
count += res_count;
|
||||
}
|
||||
}
|
||||
cache.insert(d, count);
|
||||
(cache, count)
|
||||
}
|
||||
fn count_possible(&self) -> i64 {
|
||||
self.designs
|
||||
.clone()
|
||||
.into_par_iter()
|
||||
.map(|d| self.possible(&d.stripes))
|
||||
.filter(|p| *p)
|
||||
.count() as i64
|
||||
}
|
||||
fn count_ways(&self) -> i64 {
|
||||
self.designs
|
||||
.clone()
|
||||
.into_par_iter()
|
||||
.map(|d| self.ways(&d.stripes, FxHashMap::default()).1)
|
||||
.sum::<i64>()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Onsen {
|
||||
let mut lines = input.lines();
|
||||
|
||||
let towels = lines
|
||||
.next()
|
||||
.unwrap()
|
||||
.split(&[',', ' '])
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.as_bytes().into())
|
||||
.collect();
|
||||
|
||||
lines.next().unwrap(); // discard empty line
|
||||
|
||||
let designs = lines.map(|l| l.as_bytes().into()).collect();
|
||||
|
||||
Onsen { towels, designs }
|
||||
}
|
||||
|
||||
#[aoc(day19, part1)]
|
||||
fn part1(input: &str) -> i64 {
|
||||
let onsen = parse(input);
|
||||
|
||||
onsen.count_possible()
|
||||
}
|
||||
|
||||
#[aoc(day19, part2)]
|
||||
fn part2(input: &str) -> i64 {
|
||||
let onsen = parse(input);
|
||||
|
||||
onsen.count_ways()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "r, wr, b, g, bwu, rb, gb, br
|
||||
|
||||
brwrr
|
||||
bggr
|
||||
gbbr
|
||||
rrbgbr
|
||||
ubwu
|
||||
bwurrg
|
||||
brgr
|
||||
bbrgwb";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE), 16);
|
||||
}
|
||||
}
|
105
src/day2.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use std::io::{BufRead, Lines};
|
||||
|
||||
#[aoc_generator(day2)]
|
||||
pub fn get_input(input: &[u8]) -> Reports {
|
||||
Reports::from(input.lines())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Reports {
|
||||
reports: Vec<Vec<u64>>,
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<Lines<T>> for Reports {
|
||||
fn from(lines: Lines<T>) -> Self {
|
||||
let mut reports = Vec::new();
|
||||
for line in lines.map(|i| i.unwrap()) {
|
||||
reports.push(
|
||||
line.split_ascii_whitespace()
|
||||
.map(|record| record.parse::<u64>().unwrap())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
Reports { reports }
|
||||
}
|
||||
}
|
||||
|
||||
impl Reports {
|
||||
fn is_safe(report: &[u64]) -> bool {
|
||||
let mut ascending: bool = true;
|
||||
let mut descending: bool = true;
|
||||
for (a, b) in report.iter().zip(report.iter().skip(1)) {
|
||||
if a > b {
|
||||
ascending = false
|
||||
}
|
||||
if a < b {
|
||||
descending = false;
|
||||
}
|
||||
let ad = a.abs_diff(*b);
|
||||
if !(1..=3).contains(&ad) || (!ascending && !descending) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
true
|
||||
}
|
||||
fn count_safe(&self) -> u64 {
|
||||
self.reports.iter().filter(|report| Self::is_safe(report)).count() as u64
|
||||
}
|
||||
fn is_dumb_dampened_safe(report: &[u64]) -> bool {
|
||||
if Self::is_safe(report) {
|
||||
return true;
|
||||
}
|
||||
for i in 0..report.len() {
|
||||
let mut new_vec = report.to_owned();
|
||||
new_vec.remove(i);
|
||||
if Self::is_safe(&new_vec) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn dampened_count_safe(&self) -> u64 {
|
||||
self.reports
|
||||
.iter()
|
||||
.filter(|report| Self::is_dumb_dampened_safe(report))
|
||||
.count() as u64
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day2, part1)]
|
||||
pub fn part1(input: &Reports) -> u64 {
|
||||
input.count_safe()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day2, part2)]
|
||||
pub fn part2(input: &Reports) -> u64 {
|
||||
input.dampened_count_safe()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day2::*;
|
||||
|
||||
const EXAMPLE: &[u8] = b"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";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
let input = get_input(EXAMPLE);
|
||||
println!("{:?}", input);
|
||||
assert_eq!(part1(&input), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
let input = get_input(EXAMPLE);
|
||||
assert_eq!(part2(&input), 4);
|
||||
}
|
||||
}
|
186
src/day20.rs
Normal file
@ -0,0 +1,186 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use grid::{AsCoord2d, Coord2d, Grid};
|
||||
use itertools::Itertools;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
struct RaceTrack {
|
||||
map: Grid<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
struct State {
|
||||
pos: Coord2d,
|
||||
cost: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct CheatState {
|
||||
s: State,
|
||||
}
|
||||
|
||||
const DIRECTIONS: [(i64, i64); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
|
||||
|
||||
impl RaceTrack {
|
||||
fn valid_moves<'a>(&'a self, CheatState { s: state }: &'a CheatState) -> impl Iterator<Item = CheatState> + 'a {
|
||||
DIRECTIONS
|
||||
.iter()
|
||||
.map(|dir| state.pos + dir)
|
||||
.filter_map(move |pos| match &self.map.get(&pos) {
|
||||
Some(b'.') | Some(b'S') | Some(b'E') => Some(CheatState {
|
||||
s: State {
|
||||
pos,
|
||||
cost: state.cost + 1,
|
||||
},
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
fn path_costs(&self, start: Coord2d, goal: Coord2d) -> Grid<Option<u64>> {
|
||||
let mut queue = VecDeque::new();
|
||||
let mut visited = self.map.same_shape(None);
|
||||
|
||||
let start_state = CheatState {
|
||||
s: State { pos: start, cost: 0 },
|
||||
};
|
||||
visited.set(&start, Some(0));
|
||||
queue.push_back(start_state);
|
||||
|
||||
while let Some(state) = queue.pop_front() {
|
||||
if state.s.pos == goal {
|
||||
return visited;
|
||||
}
|
||||
|
||||
let moves = self.valid_moves(&state);
|
||||
for new_state in moves {
|
||||
if visited.get(&new_state.s.pos).unwrap().is_some() {
|
||||
continue;
|
||||
}
|
||||
visited.set(&new_state.s.pos, Some(new_state.s.cost));
|
||||
queue.push_back(new_state);
|
||||
}
|
||||
}
|
||||
panic!("no path");
|
||||
}
|
||||
|
||||
fn find_cheats(&self, path: &Vec<Coord2d>, costs: &Grid<Option<u64>>, min: u64) -> i64 {
|
||||
let mut n = 0;
|
||||
for pos in path {
|
||||
let local_cost = costs.get(pos).unwrap().unwrap();
|
||||
for ofs in DIRECTIONS {
|
||||
let cheat_exit = (pos.x() + ofs.0 * 2, pos.y() + ofs.1 * 2);
|
||||
if let Some(Some(cheat_cost)) = costs.get(&cheat_exit) {
|
||||
if *cheat_cost > local_cost + 2 {
|
||||
let cheat_savings = cheat_cost - local_cost - 2;
|
||||
if cheat_savings >= min {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
fn taxi_dist<A: AsCoord2d, B: AsCoord2d>(from: &A, to: &B) -> u64 {
|
||||
from.x().abs_diff(to.x()) + from.y().abs_diff(to.y())
|
||||
}
|
||||
|
||||
fn find_cheats_n(&self, path: &Vec<Coord2d>, costs: &Grid<Option<u64>>, max_length: u64, min: u64) -> i64 {
|
||||
path.par_iter()
|
||||
.map_with(costs, |costs, pos| {
|
||||
let from_cost = costs.get(pos).unwrap().unwrap();
|
||||
let mut n = 0;
|
||||
for x in pos.x - max_length as i64 - 1..=pos.x + max_length as i64 {
|
||||
for y in pos.y - max_length as i64 - 1..=pos.y + max_length as i64 {
|
||||
let dist = Self::taxi_dist(pos, &(x, y));
|
||||
if dist <= max_length && dist >= 2 {
|
||||
if let Some(Some(to_cost)) = costs.get(&(x, y)) {
|
||||
if *to_cost > (from_cost + dist) && (to_cost - (from_cost + dist) >= min) {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
n
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> RaceTrack {
|
||||
let map = input.as_bytes().into();
|
||||
RaceTrack { map }
|
||||
}
|
||||
|
||||
fn part1_impl(input: &str, cheat_min: u64) -> i64 {
|
||||
let track = parse(input);
|
||||
let start = track.map.find(&b'S').unwrap();
|
||||
let goal = track.map.find(&b'E').unwrap();
|
||||
let costs = track.path_costs(start, goal);
|
||||
let path_squares = costs
|
||||
.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_i, c)| c.is_some())
|
||||
.filter_map(|(i, _)| track.map.coord(i as i64))
|
||||
.collect_vec();
|
||||
track.find_cheats(&path_squares, &costs, cheat_min)
|
||||
}
|
||||
|
||||
fn part2_impl(input: &str, max_length: u64, cheat_min: u64) -> i64 {
|
||||
let track = parse(input);
|
||||
let start = track.map.find(&b'S').unwrap();
|
||||
let goal = track.map.find(&b'E').unwrap();
|
||||
let costs = track.path_costs(start, goal);
|
||||
let path_squares = costs
|
||||
.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_i, c)| c.is_some())
|
||||
.filter_map(|(i, _)| track.map.coord(i as i64))
|
||||
.collect_vec();
|
||||
track.find_cheats_n(&path_squares, &costs, max_length, cheat_min)
|
||||
}
|
||||
|
||||
#[aoc(day20, part1)]
|
||||
pub fn part1(input: &str) -> i64 {
|
||||
part1_impl(input, 100)
|
||||
}
|
||||
|
||||
#[aoc(day20, part2)]
|
||||
pub fn part2(input: &str) -> i64 {
|
||||
part2_impl(input, 20, 100)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "###############
|
||||
#...#...#.....#
|
||||
#.#.#.#.#.###.#
|
||||
#S#...#.#.#...#
|
||||
#######.#.#.###
|
||||
#######.#.#...#
|
||||
#######.#.###.#
|
||||
###..E#...#...#
|
||||
###.#######.###
|
||||
#...###...#...#
|
||||
#.#####.#.###.#
|
||||
#.#...#.#.#...#
|
||||
#.#.#.#.#.#.###
|
||||
#...#...#...###
|
||||
###############";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1_impl(EXAMPLE, 0), 44);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2_impl(EXAMPLE, 2, 0), 44);
|
||||
assert_eq!(part2_impl(EXAMPLE, 20, 50), 285);
|
||||
}
|
||||
}
|
240
src/day21.rs
Normal file
@ -0,0 +1,240 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::iter::repeat_n;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum KeypadRobot {
|
||||
Number(NumberKeypadRobot),
|
||||
Direction(DirectionKeypadRobot),
|
||||
}
|
||||
|
||||
impl KeypadRobot {
|
||||
fn press(&mut self, target: u8) -> Vec<FxHashMap<Vec<u8>, usize>> {
|
||||
match self {
|
||||
Self::Number(r) => r.press(target),
|
||||
Self::Direction(r) => r.press(target),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct NumberKeypadRobot {
|
||||
pointing_at: u8,
|
||||
}
|
||||
|
||||
impl NumberKeypadRobot {
|
||||
fn pos_of(button: u8) -> (i8, i8) {
|
||||
match button {
|
||||
b'7' => (0, 0),
|
||||
b'8' => (1, 0),
|
||||
b'9' => (2, 0),
|
||||
b'4' => (0, 1),
|
||||
b'5' => (1, 1),
|
||||
b'6' => (2, 1),
|
||||
b'1' => (0, 2),
|
||||
b'2' => (1, 2),
|
||||
b'3' => (2, 2),
|
||||
b'X' => (0, 3),
|
||||
b'0' => (1, 3),
|
||||
b'A' => (2, 3),
|
||||
c => unimplemented!("unexpected character {}", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl NumberKeypadRobot {
|
||||
fn new() -> Self {
|
||||
Self { pointing_at: b'A' }
|
||||
}
|
||||
fn press(&mut self, target: u8) -> Vec<FxHashMap<Vec<u8>, usize>> {
|
||||
let cur_pos = Self::pos_of(self.pointing_at);
|
||||
let goal_pos = Self::pos_of(target);
|
||||
let x_ofs = goal_pos.0 - cur_pos.0;
|
||||
let y_ofs = goal_pos.1 - cur_pos.1;
|
||||
|
||||
let mut paths = Vec::new();
|
||||
// NOTE: no need to consider zig-zags since those paths will always require more button presses going back and forth
|
||||
if (cur_pos.0 + x_ofs, cur_pos.1) != Self::pos_of(b'X') {
|
||||
let mut x_first = Vec::new();
|
||||
x_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize));
|
||||
x_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize));
|
||||
x_first.push(b'A');
|
||||
paths.push({
|
||||
let mut h = FxHashMap::default();
|
||||
h.insert(x_first, 1);
|
||||
h
|
||||
});
|
||||
}
|
||||
if (cur_pos.0, cur_pos.1 + y_ofs) != Self::pos_of(b'X') {
|
||||
let mut y_first = Vec::new();
|
||||
y_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize));
|
||||
y_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize));
|
||||
y_first.push(b'A');
|
||||
paths.push({
|
||||
let mut h = FxHashMap::default();
|
||||
h.insert(y_first, 1);
|
||||
h
|
||||
});
|
||||
}
|
||||
if paths.is_empty() {
|
||||
panic!("all paths lead to the void");
|
||||
}
|
||||
paths.dedup();
|
||||
self.pointing_at = target;
|
||||
paths
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct DirectionKeypadRobot {
|
||||
pointing_at: u8,
|
||||
child: Option<Box<KeypadRobot>>,
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl DirectionKeypadRobot {
|
||||
fn pos_of(target: u8) -> (i8, i8) {
|
||||
match target {
|
||||
b'X' => (0, 0),
|
||||
b'^' => (1, 0),
|
||||
b'A' => (2, 0),
|
||||
b'<' => (0, 1),
|
||||
b'v' => (1, 1),
|
||||
b'>' => (2, 1),
|
||||
c => unimplemented!("unexpected char {}", c),
|
||||
}
|
||||
}
|
||||
fn move_to(&mut self, target: u8) -> Vec<u8> {
|
||||
let cur_pos = Self::pos_of(self.pointing_at);
|
||||
let goal_pos = Self::pos_of(target);
|
||||
let x_ofs = goal_pos.0 - cur_pos.0;
|
||||
let y_ofs = goal_pos.1 - cur_pos.1;
|
||||
|
||||
self.pointing_at = target;
|
||||
|
||||
if (cur_pos.0 + x_ofs, cur_pos.1) != Self::pos_of(b'X') {
|
||||
let mut x_first = Vec::new();
|
||||
x_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize));
|
||||
x_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize));
|
||||
x_first.push(b'A');
|
||||
return x_first;
|
||||
}
|
||||
if (cur_pos.0, cur_pos.1 + y_ofs) != Self::pos_of(b'X') {
|
||||
let mut y_first = Vec::new();
|
||||
y_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize));
|
||||
y_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize));
|
||||
y_first.push(b'A');
|
||||
return y_first;
|
||||
}
|
||||
panic!("all routes lead to the void");
|
||||
}
|
||||
fn path_to(&mut self, moves: &Vec<u8>) -> Vec<u8> {
|
||||
let prev_point = self.pointing_at;
|
||||
let mut path = Vec::new();
|
||||
for m in moves {
|
||||
path.append(&mut self.move_to(*m));
|
||||
}
|
||||
self.pointing_at = prev_point;
|
||||
path
|
||||
}
|
||||
|
||||
fn new(id: usize, child: Option<Box<KeypadRobot>>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
pointing_at: b'A',
|
||||
child,
|
||||
}
|
||||
}
|
||||
fn press(&mut self, target: u8) -> Vec<FxHashMap<Vec<u8>, usize>> {
|
||||
let child_frequencies = self.child.as_mut().unwrap().press(target);
|
||||
let mut my_frequencies = Vec::new();
|
||||
|
||||
for freq_set in child_frequencies {
|
||||
let mut local_freqs = FxHashMap::default();
|
||||
for (moves, count) in freq_set {
|
||||
assert_eq!(self.pointing_at, b'A');
|
||||
let path = self.path_to(&moves);
|
||||
for path_move in path.split_inclusive(|m| *m == b'A') {
|
||||
let entry = local_freqs.entry(path_move.to_vec()).or_insert(0);
|
||||
*entry = *entry + count;
|
||||
}
|
||||
}
|
||||
my_frequencies.push(local_freqs);
|
||||
}
|
||||
my_frequencies
|
||||
}
|
||||
}
|
||||
|
||||
struct Code(Vec<u8>);
|
||||
|
||||
impl Code {
|
||||
fn num_val(&self) -> i64 {
|
||||
String::from_utf8_lossy(&self.0.as_slice()[0..3]).parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Vec<Code> {
|
||||
let mut codes = Vec::new();
|
||||
for code in input.lines() {
|
||||
codes.push(Code(code.as_bytes().to_vec()))
|
||||
}
|
||||
codes
|
||||
}
|
||||
|
||||
fn run_robots(code: &Code, n: usize) -> i64 {
|
||||
let numpad = Box::new(KeypadRobot::Number(NumberKeypadRobot::new()));
|
||||
|
||||
let mut robot = Box::new(KeypadRobot::Direction(DirectionKeypadRobot::new(0, Some(numpad))));
|
||||
for i in 1..n {
|
||||
let new_robot = Box::new(KeypadRobot::Direction(DirectionKeypadRobot::new(i, Some(robot))));
|
||||
robot = new_robot
|
||||
}
|
||||
|
||||
// let mut sol_freqs = Vec::new();
|
||||
let mut sum = 0;
|
||||
for button in &code.0 {
|
||||
let paths = robot.press(*button).to_vec();
|
||||
let best = paths
|
||||
.iter()
|
||||
.map(|bp| bp.iter().map(|(k, v)| k.len() * v).sum::<usize>())
|
||||
.min()
|
||||
.unwrap();
|
||||
sum += best;
|
||||
}
|
||||
return sum as i64 * code.num_val();
|
||||
}
|
||||
|
||||
#[aoc(day21, part1)]
|
||||
fn part1(input: &str) -> i64 {
|
||||
let codes = parse(input);
|
||||
codes.iter().map(|c| run_robots(c, 2)).sum::<i64>() as i64
|
||||
}
|
||||
|
||||
#[aoc(day21, part2)]
|
||||
fn part2(input: &str) -> i64 {
|
||||
let codes = parse(input);
|
||||
for i in 0..26 {
|
||||
let res = codes.iter().map(|c| run_robots(c, i)).sum::<i64>() as i64;
|
||||
println!("{i}: {res}");
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "029A
|
||||
980A
|
||||
179A
|
||||
456A
|
||||
379A";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE), 126384);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE), 0);
|
||||
}
|
||||
}
|
155
src/day22.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use itertools::Itertools;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
|
||||
struct Change {
|
||||
price: i8,
|
||||
delta: i8,
|
||||
}
|
||||
|
||||
type Secret = u64;
|
||||
|
||||
fn evolve_secret(mut n: Secret) -> Secret {
|
||||
n = ((n * 64) ^ n) % 16777216;
|
||||
n = ((n / 32) ^ n) % 16777216;
|
||||
n = ((n * 2048) ^ n) % 16777216;
|
||||
n
|
||||
}
|
||||
|
||||
fn rounds(mut secret: Secret, n: Secret) -> Secret {
|
||||
for _ in 0..n {
|
||||
secret = evolve_secret(secret)
|
||||
}
|
||||
secret
|
||||
}
|
||||
|
||||
fn prices(mut secret: Secret, n: usize) -> Vec<i8> {
|
||||
let mut prices = vec![(secret % 10) as i8];
|
||||
for _ in 1..n {
|
||||
secret = evolve_secret(secret);
|
||||
prices.push((secret % 10) as i8);
|
||||
}
|
||||
prices
|
||||
}
|
||||
|
||||
fn build_profit_map(prices: &[i8]) -> FxHashMap<[i8; 4], i8> {
|
||||
let mut profits = FxHashMap::default();
|
||||
let changes = prices
|
||||
.windows(2)
|
||||
.map(|a| Change {
|
||||
price: a[1],
|
||||
delta: a[1] - a[0],
|
||||
})
|
||||
.collect_vec();
|
||||
for i in 3..changes.len() {
|
||||
let seq: [i8; 4] = changes[i - 3..=i]
|
||||
.iter()
|
||||
.map(|c| c.delta)
|
||||
.collect_vec()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
profits.entry(seq).or_insert(changes[i].price);
|
||||
}
|
||||
profits
|
||||
}
|
||||
|
||||
fn profit_for_sequence(changes: &[FxHashMap<[i8; 4], i8>], seq: &[i8]) -> i64 {
|
||||
changes
|
||||
.iter()
|
||||
.filter_map(|inner| inner.get(seq).map(|v| *v as i64))
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn find_best_sequence(changes: &[FxHashMap<[i8; 4], i8>]) -> [i8; 4] {
|
||||
let possible_seqs = (0..4).map(|_| (-9..=9i8)).multi_cartesian_product().collect_vec();
|
||||
let (best_seq, _best_profit) = possible_seqs
|
||||
.par_iter()
|
||||
.map_with(changes, |changes, seq| (seq, profit_for_sequence(changes, seq)))
|
||||
.max_by(|(_, a), (_, b)| a.cmp(b))
|
||||
.unwrap();
|
||||
|
||||
best_seq.as_slice().try_into().unwrap()
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Vec<Secret> {
|
||||
input.lines().map(|l| l.parse().unwrap()).collect()
|
||||
}
|
||||
|
||||
#[aoc(day22, part1)]
|
||||
pub fn part1(input: &str) -> Secret {
|
||||
let secrets = parse(input);
|
||||
|
||||
secrets.iter().map(|s| rounds(*s, 2000)).sum::<Secret>()
|
||||
}
|
||||
|
||||
#[aoc(day22, part2)]
|
||||
pub fn part2(input: &str) -> i64 {
|
||||
let secrets = parse(input);
|
||||
|
||||
let price_changes = secrets
|
||||
.iter()
|
||||
.map(|s| build_profit_map(&prices(*s, 2000)))
|
||||
.collect_vec();
|
||||
|
||||
let seq = find_best_sequence(&price_changes);
|
||||
profit_for_sequence(&price_changes, &seq)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "1
|
||||
10
|
||||
100
|
||||
2024";
|
||||
|
||||
const EXAMPLE2: &str = "1
|
||||
2
|
||||
3
|
||||
2024";
|
||||
|
||||
#[test]
|
||||
fn evolution() {
|
||||
assert_eq!(evolve_secret(123), 15887950);
|
||||
assert_eq!(evolve_secret(15887950), 16495136);
|
||||
assert_eq!(evolve_secret(16495136), 527345);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rounds() {
|
||||
assert_eq!(rounds(1, 2000), 8685429);
|
||||
assert_eq!(rounds(10, 2000), 4700978);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prices() {
|
||||
assert_eq!(prices(123, 10), vec![3, 0, 6, 5, 4, 4, 6, 4, 4, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_profit() {
|
||||
assert_eq!(
|
||||
profit_for_sequence(&vec![build_profit_map(&prices(123, 10))], &[-1, -1, 0, 2]),
|
||||
6
|
||||
);
|
||||
let secrets = parse(EXAMPLE2);
|
||||
|
||||
let price_changes = secrets
|
||||
.iter()
|
||||
.map(|s| build_profit_map(&prices(*s, 2000)))
|
||||
.collect_vec();
|
||||
assert_eq!(profit_for_sequence(&price_changes, &[-2, 1, -1, 3]), 23);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE), 37327623);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE2), 23);
|
||||
}
|
||||
}
|
194
src/day23.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
struct Node([char; 2]);
|
||||
|
||||
impl Debug for Node {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("{}", self.0.iter().join("")))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Node {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("{}", self.0.iter().join("")))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[char; 2]> for Node {
|
||||
fn from(value: [char; 2]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<char>> for Node {
|
||||
type Error = <[char; 2] as TryFrom<Vec<char>>>::Error;
|
||||
fn try_from(value: Vec<char>) -> Result<Self, Self::Error> {
|
||||
let array: [char; 2] = value.try_into()?;
|
||||
Ok(Self(array))
|
||||
}
|
||||
}
|
||||
|
||||
struct Network {
|
||||
nodes: FxHashSet<Node>,
|
||||
edges: FxHashMap<Node, FxHashSet<Node>>,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
fn groups_3(&self) -> FxHashSet<Vec<Node>> {
|
||||
let mut sets = FxHashSet::default();
|
||||
for n in &self.nodes {
|
||||
let neighbours = self.edges.get(n).unwrap();
|
||||
for neigh in neighbours {
|
||||
let neighbours2 = self.edges.get(neigh).unwrap();
|
||||
for neigh2 in neighbours2 {
|
||||
let neighbours3 = self.edges.get(neigh2).unwrap();
|
||||
if neighbours3.contains(n) {
|
||||
let mut set = vec![*n, *neigh, *neigh2];
|
||||
set.sort();
|
||||
sets.insert(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sets
|
||||
}
|
||||
// Had to study Wikipedia for this one
|
||||
// https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm
|
||||
fn bron_kerbosch(
|
||||
&self,
|
||||
r: FxHashSet<Node>,
|
||||
mut p: FxHashSet<Node>,
|
||||
mut x: FxHashSet<Node>,
|
||||
) -> Vec<FxHashSet<Node>> {
|
||||
let mut results = Vec::new();
|
||||
if p.is_empty() && x.is_empty() {
|
||||
return vec![r];
|
||||
} else if p.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
// choose the pivot with the most neighbours, to minimize the size of p_iter
|
||||
let p_iter = if let Some(pivot) = p.union(&x).max_by(|a, b| self.edges[a].len().cmp(&self.edges[b].len())) {
|
||||
FxHashSet::from_iter(p.difference(self.edges.get(pivot).unwrap()).copied())
|
||||
} else {
|
||||
p.clone()
|
||||
};
|
||||
for node in &p_iter {
|
||||
let mut new_r = r.clone();
|
||||
new_r.insert(*node);
|
||||
|
||||
let neighbours = FxHashSet::from_iter(self.edges.get(node).unwrap().iter().copied());
|
||||
let new_p = FxHashSet::from_iter(p.intersection(&neighbours).copied());
|
||||
let new_x = FxHashSet::from_iter(x.intersection(&neighbours).copied());
|
||||
|
||||
results.extend(self.bron_kerbosch(new_r, new_p, new_x).into_iter());
|
||||
p.remove(node);
|
||||
x.insert(*node);
|
||||
}
|
||||
results
|
||||
}
|
||||
fn maximal_subgraphs(&self) -> Vec<FxHashSet<Node>> {
|
||||
self.bron_kerbosch(
|
||||
FxHashSet::default(),
|
||||
FxHashSet::from_iter(self.nodes.iter().copied()),
|
||||
FxHashSet::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Network {
|
||||
fn from(input: &str) -> Self {
|
||||
let mut nodes = FxHashSet::default();
|
||||
let mut edges = FxHashMap::default();
|
||||
for line in input.lines() {
|
||||
let (node1, node2) = line.split_once('-').unwrap();
|
||||
let (node1, node2): (Node, Node) = (
|
||||
node1.chars().collect_vec().try_into().unwrap(),
|
||||
node2.chars().collect_vec().try_into().unwrap(),
|
||||
);
|
||||
if !nodes.contains(&node1) {
|
||||
nodes.insert(node1);
|
||||
}
|
||||
if !nodes.contains(&node2) {
|
||||
nodes.insert(node2);
|
||||
}
|
||||
edges.entry(node1).or_insert(FxHashSet::default()).insert(node2);
|
||||
edges.entry(node2).or_insert(FxHashSet::default()).insert(node1);
|
||||
}
|
||||
Self { nodes, edges }
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Network {
|
||||
input.into()
|
||||
}
|
||||
|
||||
#[aoc(day23, part1)]
|
||||
pub fn part1(input: &str) -> i64 {
|
||||
let network = parse(input);
|
||||
|
||||
let sets = network.groups_3();
|
||||
let t_count = sets.iter().filter(|set| set.iter().any(|s| s.0[0] == 't')).count();
|
||||
|
||||
t_count as i64
|
||||
}
|
||||
|
||||
#[aoc(day23, part2)]
|
||||
pub fn part2(input: &str) -> String {
|
||||
let network = parse(input);
|
||||
let best_sets = network.maximal_subgraphs();
|
||||
let largest_set = best_sets.iter().max_by(|a, b| a.len().cmp(&b.len())).unwrap();
|
||||
let mut largest = largest_set.iter().collect_vec();
|
||||
largest.sort();
|
||||
largest.iter().join(",")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "kh-tc
|
||||
qp-kh
|
||||
de-cg
|
||||
ka-co
|
||||
yn-aq
|
||||
qp-ub
|
||||
cg-tb
|
||||
vc-aq
|
||||
tb-ka
|
||||
wh-tc
|
||||
yn-cg
|
||||
kh-ub
|
||||
ta-co
|
||||
de-co
|
||||
tc-td
|
||||
tb-wq
|
||||
wh-td
|
||||
ta-ka
|
||||
td-qp
|
||||
aq-cg
|
||||
wq-ub
|
||||
ub-vc
|
||||
de-ta
|
||||
wq-aq
|
||||
wq-vc
|
||||
wh-yn
|
||||
ka-de
|
||||
kh-ta
|
||||
co-tc
|
||||
wh-qp
|
||||
tb-vc
|
||||
td-yn";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE), "co,de,ka,ta");
|
||||
}
|
||||
}
|
229
src/day24.rs
Normal file
@ -0,0 +1,229 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use itertools::Itertools;
|
||||
use nom::And;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Op {
|
||||
And,
|
||||
Or,
|
||||
Xor,
|
||||
Constant,
|
||||
}
|
||||
|
||||
impl From<&str> for Op {
|
||||
fn from(value: &str) -> Self {
|
||||
match value {
|
||||
"AND" => Self::And,
|
||||
"OR" => Self::Or,
|
||||
"XOR" => Self::Xor,
|
||||
s => panic!("invalid operation {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Gate {
|
||||
op: Op,
|
||||
value: Option<bool>,
|
||||
arguments: [String; 2],
|
||||
}
|
||||
|
||||
impl Gate {
|
||||
fn eval(&self, machine: &GateMachine) -> bool {
|
||||
match self.op {
|
||||
Op::And => machine.val_of(&self.arguments[0]) && machine.val_of(&self.arguments[1]),
|
||||
Op::Or => machine.val_of(&self.arguments[0]) || machine.val_of(&self.arguments[1]),
|
||||
Op::Xor => machine.val_of(&self.arguments[0]) ^ machine.val_of(&self.arguments[1]),
|
||||
Op::Constant => self.value.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GateMachine {
|
||||
gates: FxHashMap<String, Gate>,
|
||||
}
|
||||
|
||||
impl GateMachine {
|
||||
fn val_of(&self, gate: &str) -> bool {
|
||||
println!("gate: {}", gate);
|
||||
if let Some(val) = self.gates[gate].value {
|
||||
val
|
||||
} else {
|
||||
self.gates[gate].eval(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> GateMachine {
|
||||
let mut gates = FxHashMap::default();
|
||||
for line in input.lines() {
|
||||
println!("{line}");
|
||||
let const_re = Regex::new(r"^([xyz][0-9]{2}): ([01])$").unwrap();
|
||||
let gate_re = Regex::new(r"^([a-z0-9]{3}) (AND|XOR|OR) ([a-z0-9]{3}) -> ([a-z0-9]{3})$").unwrap();
|
||||
|
||||
if let Some(caps) = const_re.captures(line) {
|
||||
println!(" is const: {:?}", caps);
|
||||
gates.insert(
|
||||
caps[1].to_string(),
|
||||
Gate {
|
||||
op: Op::Constant,
|
||||
value: if &caps[2] == "1" { Some(true) } else { Some(false) },
|
||||
arguments: [String::new(), String::new()],
|
||||
},
|
||||
);
|
||||
} else if let Some(caps) = gate_re.captures(line) {
|
||||
println!(" is gate: {:?}", caps);
|
||||
gates.insert(
|
||||
caps[4].to_string(),
|
||||
Gate {
|
||||
op: Op::from(&caps[2]),
|
||||
value: None,
|
||||
arguments: [caps[1].to_string(), caps[3].to_string()],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
GateMachine { gates }
|
||||
}
|
||||
|
||||
#[aoc(day24, part1)]
|
||||
pub fn part1(input: &str) -> i64 {
|
||||
let machine = parse(input);
|
||||
let z_gates = machine
|
||||
.gates
|
||||
.keys()
|
||||
.filter(|k| k.starts_with('z'))
|
||||
.map(|s| (s, s.split_at(1).1.parse::<usize>().unwrap()));
|
||||
let bit_vals = z_gates
|
||||
.map(|(name, bit)| if machine.val_of(name) { 1 << bit } else { 0 })
|
||||
.fold(0, |accum, val| accum | val);
|
||||
bit_vals
|
||||
}
|
||||
|
||||
#[aoc(day24, part2)]
|
||||
pub fn part2(input: &str) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE1: &str = "x00: 1
|
||||
x01: 1
|
||||
x02: 1
|
||||
y00: 0
|
||||
y01: 1
|
||||
y02: 0
|
||||
|
||||
x00 AND y00 -> z00
|
||||
x01 XOR y01 -> z01x00: 1
|
||||
x01: 0
|
||||
x02: 1
|
||||
x03: 1
|
||||
x04: 0
|
||||
y00: 1
|
||||
y01: 1
|
||||
y02: 1
|
||||
y03: 1
|
||||
y04: 1
|
||||
|
||||
ntg XOR fgs -> mjb
|
||||
y02 OR x01 -> tnw
|
||||
kwq OR kpj -> z05
|
||||
x00 OR x03 -> fst
|
||||
tgd XOR rvg -> z01
|
||||
vdt OR tnw -> bfw
|
||||
bfw AND frj -> z10
|
||||
ffh OR nrd -> bqk
|
||||
y00 AND y03 -> djm
|
||||
y03 OR y00 -> psh
|
||||
bqk OR frj -> z08
|
||||
tnw OR fst -> frj
|
||||
gnj AND tgd -> z11
|
||||
bfw XOR mjb -> z00
|
||||
x03 OR x00 -> vdt
|
||||
gnj AND wpb -> z02
|
||||
x04 AND y00 -> kjc
|
||||
djm OR pbm -> qhw
|
||||
nrd AND vdt -> hwm
|
||||
kjc AND fst -> rvg
|
||||
y04 OR y02 -> fgs
|
||||
y01 AND x02 -> pbm
|
||||
ntg OR kjc -> kwq
|
||||
psh XOR fgs -> tgd
|
||||
qhw XOR tgd -> z09
|
||||
pbm OR djm -> kpj
|
||||
x03 XOR y03 -> ffh
|
||||
x00 XOR y04 -> ntg
|
||||
bfw OR bqk -> z06
|
||||
nrd XOR fgs -> wpb
|
||||
frj XOR qhw -> z04
|
||||
bqk OR frj -> z07
|
||||
y03 OR x01 -> nrd
|
||||
hwm AND bqk -> z03
|
||||
tgd XOR rvg -> z12
|
||||
tnw OR pbm -> gnj
|
||||
x02 OR y02 -> z02";
|
||||
|
||||
const EXAMPLE2: &str = "x00: 1
|
||||
x01: 0
|
||||
x02: 1
|
||||
x03: 1
|
||||
x04: 0
|
||||
y00: 1
|
||||
y01: 1
|
||||
y02: 1
|
||||
y03: 1
|
||||
y04: 1
|
||||
|
||||
ntg XOR fgs -> mjb
|
||||
y02 OR x01 -> tnw
|
||||
kwq OR kpj -> z05
|
||||
x00 OR x03 -> fst
|
||||
tgd XOR rvg -> z01
|
||||
vdt OR tnw -> bfw
|
||||
bfw AND frj -> z10
|
||||
ffh OR nrd -> bqk
|
||||
y00 AND y03 -> djm
|
||||
y03 OR y00 -> psh
|
||||
bqk OR frj -> z08
|
||||
tnw OR fst -> frj
|
||||
gnj AND tgd -> z11
|
||||
bfw XOR mjb -> z00
|
||||
x03 OR x00 -> vdt
|
||||
gnj AND wpb -> z02
|
||||
x04 AND y00 -> kjc
|
||||
djm OR pbm -> qhw
|
||||
nrd AND vdt -> hwm
|
||||
kjc AND fst -> rvg
|
||||
y04 OR y02 -> fgs
|
||||
y01 AND x02 -> pbm
|
||||
ntg OR kjc -> kwq
|
||||
psh XOR fgs -> tgd
|
||||
qhw XOR tgd -> z09
|
||||
pbm OR djm -> kpj
|
||||
x03 XOR y03 -> ffh
|
||||
x00 XOR y04 -> ntg
|
||||
bfw OR bqk -> z06
|
||||
nrd XOR fgs -> wpb
|
||||
frj XOR qhw -> z04
|
||||
bqk OR frj -> z07
|
||||
y03 OR x01 -> nrd
|
||||
hwm AND bqk -> z03
|
||||
tgd XOR rvg -> z12
|
||||
tnw OR pbm -> gnj";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE1), 4);
|
||||
assert_eq!(part1(EXAMPLE2), 2024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(EXAMPLE1), 0);
|
||||
}
|
||||
}
|
143
src/day25.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use itertools::Itertools;
|
||||
|
||||
enum LockKey {
|
||||
Lock,
|
||||
Key,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct LockPile {
|
||||
keys: Vec<Vec<usize>>,
|
||||
locks: Vec<Vec<usize>>,
|
||||
}
|
||||
|
||||
fn parse_grid(lines: &Vec<&str>) -> (LockKey, Vec<usize>) {
|
||||
assert_eq!(lines.len(), 7);
|
||||
if lines[0].chars().all(|c| c == '#') {
|
||||
// lock
|
||||
let mut pins = vec![0; 5];
|
||||
for row in 1..lines.len() {
|
||||
let row_s = lines[row];
|
||||
for i in 0..row_s.len() {
|
||||
if row_s.chars().nth(i) == Some('#') {
|
||||
pins[i] = row
|
||||
}
|
||||
}
|
||||
}
|
||||
(LockKey::Lock, pins)
|
||||
} else if lines[6].chars().all(|c| c == '#') {
|
||||
// key
|
||||
let mut pins = vec![5; 5];
|
||||
for row in (1..lines.len()).rev() {
|
||||
let row_s = lines[row];
|
||||
for i in 0..row_s.len() {
|
||||
if row_s.chars().nth(i) == Some('#') {
|
||||
pins[i] = 6 - row
|
||||
}
|
||||
}
|
||||
}
|
||||
(LockKey::Key, pins)
|
||||
} else {
|
||||
panic!("not a lock or a key: {:?}", lines);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> LockPile {
|
||||
let mut locks = Vec::new();
|
||||
let mut keys = Vec::new();
|
||||
let mut accum: Vec<&str> = Vec::new();
|
||||
for line in input.lines() {
|
||||
if line == "" {
|
||||
let (lk, pins) = parse_grid(&accum);
|
||||
match lk {
|
||||
LockKey::Lock => locks.push(pins),
|
||||
LockKey::Key => keys.push(pins),
|
||||
}
|
||||
accum.clear();
|
||||
} else {
|
||||
accum.push(line);
|
||||
}
|
||||
}
|
||||
if accum.len() != 0 {
|
||||
let (lk, pins) = parse_grid(&accum);
|
||||
match lk {
|
||||
LockKey::Lock => locks.push(pins),
|
||||
LockKey::Key => keys.push(pins),
|
||||
}
|
||||
}
|
||||
LockPile { keys, locks }
|
||||
}
|
||||
|
||||
fn test_lock_key(lock: &Vec<usize>, key: &Vec<usize>) -> bool {
|
||||
!lock.iter().zip(key.iter()).any(|(lp, kp)| lp + kp > 5)
|
||||
}
|
||||
|
||||
#[aoc(day25, part1)]
|
||||
pub fn part1(input: &str) -> i64 {
|
||||
let lockpile = parse(input);
|
||||
|
||||
lockpile
|
||||
.locks
|
||||
.iter()
|
||||
.cartesian_product(lockpile.keys.iter())
|
||||
.filter(|(l, k)| test_lock_key(l, k))
|
||||
.count() as i64
|
||||
}
|
||||
|
||||
#[aoc(day25, part2)]
|
||||
pub fn part2(_input: &str) -> String {
|
||||
"run the other solutions for day 25 part 2!".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const EXAMPLE: &str = "#####
|
||||
.####
|
||||
.####
|
||||
.####
|
||||
.#.#.
|
||||
.#...
|
||||
.....
|
||||
|
||||
#####
|
||||
##.##
|
||||
.#.##
|
||||
...##
|
||||
...#.
|
||||
...#.
|
||||
.....
|
||||
|
||||
.....
|
||||
#....
|
||||
#....
|
||||
#...#
|
||||
#.#.#
|
||||
#.###
|
||||
#####
|
||||
|
||||
.....
|
||||
.....
|
||||
#.#..
|
||||
###..
|
||||
###.#
|
||||
###.#
|
||||
#####
|
||||
|
||||
.....
|
||||
.....
|
||||
.....
|
||||
#....
|
||||
#.#..
|
||||
#.#.#
|
||||
#####";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(EXAMPLE), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {}
|
||||
}
|
64
src/day3.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use atoi::FromRadix10;
|
||||
use regex::bytes::Regex;
|
||||
use std::io::BufRead;
|
||||
|
||||
#[aoc_generator(day3)]
|
||||
pub fn get_input(input: &[u8]) -> Vec<String> {
|
||||
input.lines().map(|l| l.unwrap()).collect()
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day3, part1)]
|
||||
pub fn part1(input: &[String]) -> u64 {
|
||||
let re = Regex::new(r"(?-u)mul\((\d+),(\d+)\)").unwrap();
|
||||
input
|
||||
.iter()
|
||||
.map(|line| {
|
||||
re.captures_iter(line.as_bytes())
|
||||
.map(|m| u64::from_radix_10(&m[1]).0 * u64::from_radix_10(&m[2]).0)
|
||||
.sum::<u64>()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day3, part2)]
|
||||
pub fn part2(input: &[String]) -> u64 {
|
||||
let mut sum = 0u64;
|
||||
let mut do_mul: u64 = 1;
|
||||
let re = Regex::new(r"(?-u)(do\(\)|don't\(\)|mul\((\d+),(\d+)\))").unwrap();
|
||||
for line in input {
|
||||
let line = line.as_bytes();
|
||||
for m in re.captures_iter(line) {
|
||||
match &m[1] {
|
||||
b"do()" => do_mul = 1,
|
||||
b"don't()" => do_mul = 0,
|
||||
_ => {
|
||||
sum += u64::from_radix_10(&m[2]).0 * u64::from_radix_10(&m[3]).0 * do_mul;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day3::*;
|
||||
|
||||
const EXAMPLE1: &[u8] = b"xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))";
|
||||
const EXAMPLE2: &[u8] = b"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
let input = get_input(EXAMPLE1);
|
||||
assert_eq!(part1(&input), 161);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
let input = get_input(EXAMPLE2);
|
||||
assert_eq!(part2(&input), 48);
|
||||
}
|
||||
}
|
@ -1,40 +1,12 @@
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Lines};
|
||||
use std::time::{Duration, Instant};
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use std::io::{BufRead, Lines};
|
||||
|
||||
// BOILERPLATE
|
||||
type InputIter = Lines<BufReader<File>>;
|
||||
|
||||
pub fn get_input() -> InputIter {
|
||||
let f = File::open("input").unwrap();
|
||||
let br = BufReader::new(f);
|
||||
br.lines()
|
||||
#[aoc_generator(day4)]
|
||||
pub fn get_input(input: &[u8]) -> WordSearch {
|
||||
WordSearch::from(input.lines())
|
||||
}
|
||||
|
||||
fn duration_format(duration: Duration) -> String {
|
||||
match duration.as_secs_f64() {
|
||||
x if x > 1.0 => format!("{:.3}s", x),
|
||||
x if x > 0.010 => format!("{:.3}ms", x * 1e3),
|
||||
x => format!("{:.3}us", x * 1e6),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = get_input();
|
||||
let start = Instant::now();
|
||||
let ans1 = problem1(input);
|
||||
let duration1 = start.elapsed();
|
||||
println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1));
|
||||
|
||||
let input = get_input();
|
||||
let start = Instant::now();
|
||||
let ans2 = problem2(input);
|
||||
let duration2 = start.elapsed();
|
||||
println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2));
|
||||
println!("Total duration: {}", duration_format(duration1 + duration2));
|
||||
}
|
||||
|
||||
struct WordSearch {
|
||||
pub struct WordSearch {
|
||||
rows: Vec<String>,
|
||||
}
|
||||
|
||||
@ -81,25 +53,21 @@ impl WordSearch {
|
||||
for x in 0..width {
|
||||
for y in 0..height {
|
||||
// check down-right
|
||||
if x <= width - needle.len() && y <= height - needle.len() {
|
||||
if (0..needle.len())
|
||||
.into_iter()
|
||||
.all(|i| self.get(x + i, y + i) == needle.as_bytes()[i].into())
|
||||
if x <= width - needle.len()
|
||||
&& y <= height - needle.len()
|
||||
&& (0..needle.len()).all(|i| self.get(x + i, y + i) == needle.as_bytes()[i].into())
|
||||
{
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
// check down-left
|
||||
if x >= needle.len() - 1 && y <= height - needle.len() {
|
||||
if (0..needle.len())
|
||||
.into_iter()
|
||||
.all(|i| self.get(x - i, y + i) == needle.as_bytes()[i].into())
|
||||
if x >= needle.len() - 1
|
||||
&& y <= height - needle.len()
|
||||
&& (0..needle.len()).all(|i| self.get(x - i, y + i) == needle.as_bytes()[i].into())
|
||||
{
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
@ -138,11 +106,10 @@ impl WordSearch {
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
|
||||
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
#[aoc(day4, part1)]
|
||||
pub fn part1(ws: &WordSearch) -> u64 {
|
||||
let needle = "XMAS";
|
||||
let needle_rev: String = needle.chars().rev().collect();
|
||||
let ws = WordSearch::from(input);
|
||||
ws.count_forward(needle)
|
||||
+ ws.count_forward(&needle_rev)
|
||||
+ ws.count_vertical(needle)
|
||||
@ -152,17 +119,16 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let ws = WordSearch::from(input);
|
||||
#[aoc(day4, part2)]
|
||||
pub fn part2(ws: &WordSearch) -> u64 {
|
||||
ws.count_x_mas()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
use std::io::Cursor;
|
||||
use crate::day4::*;
|
||||
|
||||
const EXAMPLE: &str = &"MMMSXXMASM
|
||||
const EXAMPLE: &[u8] = b"MMMSXXMASM
|
||||
MSAMXMSMSA
|
||||
AMXSXMAAMM
|
||||
MSAMASMSMX
|
||||
@ -174,14 +140,12 @@ MAMMMXMMMM
|
||||
MXMXAXMASX";
|
||||
|
||||
#[test]
|
||||
fn problem1_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem1(c.lines()), 18);
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&get_input(EXAMPLE)), 18);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn problem2_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem2(c.lines()), 9);
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&get_input(EXAMPLE)), 9);
|
||||
}
|
||||
}
|
148
src/day5.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
use std::io::BufRead;
|
||||
|
||||
type HashMap<K, V> = FxHashMap<K, V>;
|
||||
|
||||
#[aoc_generator(day5)]
|
||||
pub fn get_input(input: &[u8]) -> (OrderingRules, Vec<Vec<u64>>) {
|
||||
let mut lines = input.lines();
|
||||
|
||||
let pairs = HashMap::from_iter(
|
||||
lines
|
||||
.by_ref()
|
||||
.map_while(|l| match l {
|
||||
Ok(line) if !line.is_empty() => {
|
||||
let rule = BeforeRule::from(line);
|
||||
Some(vec![
|
||||
((rule.a, rule.b), Ordering::Less),
|
||||
((rule.b, rule.a), Ordering::Greater),
|
||||
])
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flatten(),
|
||||
);
|
||||
let updates: Vec<Vec<u64>> = lines
|
||||
.by_ref()
|
||||
.map(|l| l.unwrap().split(',').map(|n| n.parse::<u64>().unwrap()).collect())
|
||||
.collect();
|
||||
(OrderingRules { pairs }, updates)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BeforeRule {
|
||||
a: u64,
|
||||
b: u64,
|
||||
}
|
||||
|
||||
impl From<String> for BeforeRule {
|
||||
fn from(line: String) -> BeforeRule {
|
||||
let nums = line.split_once('|').unwrap();
|
||||
BeforeRule {
|
||||
a: nums.0.parse().unwrap(),
|
||||
b: nums.1.parse().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OrderingRules {
|
||||
pairs: HashMap<(u64, u64), Ordering>,
|
||||
}
|
||||
|
||||
impl OrderingRules {
|
||||
fn check(&self, pages: &[u64]) -> bool {
|
||||
pages.is_sorted_by(|a, b| self.is_sorted(*a, *b))
|
||||
}
|
||||
fn cmp(&self, a: u64, b: u64) -> Ordering {
|
||||
if let Some(ord) = self.pairs.get(&(a, b)) {
|
||||
*ord
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
fn is_sorted(&self, a: u64, b: u64) -> bool {
|
||||
matches!(self.pairs.get(&(a, b)), Some(Ordering::Less) | Some(Ordering::Equal))
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a, T: Iterator<Item = &'a str>> From<&mut T> for OrderingRules {
|
||||
// fn from(input: &mut T) -> Self {
|
||||
// let mut rules = Vec::new();
|
||||
// for line in input {
|
||||
// rules.push(line.into())
|
||||
// }
|
||||
// Self { rules }
|
||||
// }
|
||||
// }
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day5, part1)]
|
||||
pub fn part1((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 {
|
||||
updates
|
||||
.iter()
|
||||
.filter(|update| rules.check(update))
|
||||
.map(|update| update[update.len() / 2])
|
||||
.sum()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day5, part2)]
|
||||
pub fn part2((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 {
|
||||
let mut updates = updates.clone();
|
||||
updates
|
||||
.iter_mut()
|
||||
.filter(|update| !rules.check(update))
|
||||
.map(|update| {
|
||||
update.sort_by(|a, b| rules.cmp(*a, *b));
|
||||
update[update.len() / 2]
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day5::*;
|
||||
|
||||
const EXAMPLE: &[u8] = b"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";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&get_input(EXAMPLE)), 143);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&get_input(EXAMPLE)), 123);
|
||||
}
|
||||
}
|
215
src/day6.rs
Normal file
@ -0,0 +1,215 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use bitflags::bitflags;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use rayon::slice::ParallelSlice;
|
||||
use std::fmt;
|
||||
use std::io::BufRead;
|
||||
use std::ops::BitAnd;
|
||||
|
||||
use grid::Grid;
|
||||
|
||||
#[aoc_generator(day6)]
|
||||
pub fn get_input(input: &[u8]) -> Map {
|
||||
Map::from(input)
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
enum FacingDirection {
|
||||
Up = 1,
|
||||
Down = 2,
|
||||
Left = 4,
|
||||
Right = 8,
|
||||
}
|
||||
|
||||
impl FacingDirection {
|
||||
fn next(&self) -> FacingDirection {
|
||||
match self {
|
||||
FacingDirection::Up => FacingDirection::Right,
|
||||
FacingDirection::Down => FacingDirection::Left,
|
||||
FacingDirection::Left => FacingDirection::Up,
|
||||
FacingDirection::Right => FacingDirection::Down,
|
||||
}
|
||||
}
|
||||
fn pos_ofs(&self, pos: (i64, i64)) -> (i64, i64) {
|
||||
match self {
|
||||
FacingDirection::Up => (pos.0, pos.1 + -1),
|
||||
FacingDirection::Down => (pos.0, pos.1 + 1),
|
||||
FacingDirection::Left => (pos.0 + -1, pos.1),
|
||||
FacingDirection::Right => (pos.0 + 1, pos.1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum StepOutcome {
|
||||
LeftMap,
|
||||
LoopFound,
|
||||
Continue,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum RunOutcome {
|
||||
LeftMap,
|
||||
LoopFound,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub struct DirectionSet: u8 {
|
||||
const Up = 1;
|
||||
const Down = 2;
|
||||
const Left = 4;
|
||||
const Right = 8;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FacingDirection> for DirectionSet {
|
||||
fn from(value: FacingDirection) -> Self {
|
||||
match value {
|
||||
FacingDirection::Up => DirectionSet::Up,
|
||||
FacingDirection::Down => DirectionSet::Down,
|
||||
FacingDirection::Left => DirectionSet::Left,
|
||||
FacingDirection::Right => DirectionSet::Right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DirectionSet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd<FacingDirection> for DirectionSet {
|
||||
type Output = DirectionSet;
|
||||
fn bitand(self, rhs: FacingDirection) -> Self::Output {
|
||||
self & DirectionSet::from(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Map {
|
||||
grid: Grid<u8>,
|
||||
visited_from: Grid<DirectionSet>,
|
||||
guard_facing: FacingDirection,
|
||||
guard_pos: (i64, i64),
|
||||
path: Vec<((i64, i64), FacingDirection)>,
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<T> for Map {
|
||||
fn from(input: T) -> Self {
|
||||
let grid = Grid::from(input);
|
||||
let visited_from = grid.same_shape(DirectionSet::empty());
|
||||
let guard_pos = grid.find(&b'^').expect("Guard not found");
|
||||
let guard_facing = FacingDirection::Up;
|
||||
Self {
|
||||
grid,
|
||||
guard_pos: guard_pos.into(),
|
||||
guard_facing,
|
||||
visited_from,
|
||||
path: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Map {
|
||||
fn look(&self, dir: &FacingDirection) -> Option<&u8> {
|
||||
self.grid.get(&dir.pos_ofs(self.guard_pos))
|
||||
}
|
||||
/// Move one step in the facing direction, return if we are still inside the bounds
|
||||
fn step_guard<const RECORD_PATH: bool>(&mut self) -> StepOutcome {
|
||||
let new_pos = self.guard_facing.pos_ofs(self.guard_pos);
|
||||
if self
|
||||
.visited_from
|
||||
.get(&new_pos)
|
||||
.is_some_and(|dirs| dirs.contains(self.guard_facing.into()))
|
||||
{
|
||||
StepOutcome::LoopFound
|
||||
} else if self.grid.set(&new_pos, b'X').is_some() {
|
||||
if RECORD_PATH {
|
||||
self.path.push((new_pos, self.guard_facing));
|
||||
}
|
||||
self.visited_from.set(
|
||||
&new_pos,
|
||||
*self.visited_from.get(&new_pos).unwrap() | self.guard_facing.into(),
|
||||
);
|
||||
self.guard_pos = new_pos;
|
||||
StepOutcome::Continue
|
||||
} else {
|
||||
StepOutcome::LeftMap
|
||||
}
|
||||
}
|
||||
fn run_guard<const RECORD_PATH: bool>(&mut self) -> RunOutcome {
|
||||
while let Some(val) = self.look(&self.guard_facing) {
|
||||
match val {
|
||||
b'#' => {
|
||||
// obstacle, turn right
|
||||
self.guard_facing = self.guard_facing.next();
|
||||
}
|
||||
_ => match self.step_guard::<RECORD_PATH>() {
|
||||
StepOutcome::LeftMap => return RunOutcome::LeftMap,
|
||||
StepOutcome::LoopFound => return RunOutcome::LoopFound,
|
||||
StepOutcome::Continue => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
RunOutcome::LeftMap
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day6, part1)]
|
||||
pub fn part1(map: &Map) -> u64 {
|
||||
let mut map = map.clone();
|
||||
map.run_guard::<false>();
|
||||
|
||||
map.grid.count(&b'X') as u64
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day6, part2)]
|
||||
pub fn part2(input_map: &Map) -> u64 {
|
||||
// Use the solution from problem 1 to reduce the number of positions where obstacle placement will change the path
|
||||
let mut path_map = input_map.clone();
|
||||
path_map.run_guard::<true>();
|
||||
|
||||
path_map
|
||||
.path
|
||||
.par_windows(2)
|
||||
.filter(|prev_cur| {
|
||||
let last_posdir = prev_cur[0];
|
||||
let mut test_map = input_map.clone();
|
||||
test_map.grid.set(&prev_cur[1].0, b'#').unwrap();
|
||||
test_map.guard_pos = last_posdir.0;
|
||||
test_map.guard_facing = last_posdir.1;
|
||||
|
||||
test_map.run_guard::<false>() == RunOutcome::LoopFound
|
||||
})
|
||||
.count() as u64
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day6::*;
|
||||
|
||||
const EXAMPLE: &[u8] = b"....#.....
|
||||
.........#
|
||||
..........
|
||||
..#.......
|
||||
.......#..
|
||||
..........
|
||||
.#..^.....
|
||||
........#.
|
||||
#.........
|
||||
......#...";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&get_input(EXAMPLE)), 41);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&get_input(EXAMPLE)), 6);
|
||||
}
|
||||
}
|
121
src/day7.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use std::io::{BufRead, Lines};
|
||||
|
||||
#[aoc_generator(day7)]
|
||||
pub fn get_input(input: &[u8]) -> Calibrations {
|
||||
Calibrations::from(input.lines())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Calibration {
|
||||
result: u64,
|
||||
numbers: Vec<u64>,
|
||||
}
|
||||
|
||||
impl From<&str> for Calibration {
|
||||
fn from(value: &str) -> Self {
|
||||
let (result, rest) = value.split_once(':').unwrap();
|
||||
Self {
|
||||
result: result.parse().unwrap(),
|
||||
numbers: rest.split_ascii_whitespace().map(|s| s.parse().unwrap()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Calibrations {
|
||||
cals: Vec<Calibration>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Operator {
|
||||
Add,
|
||||
Multiply,
|
||||
Concatenate,
|
||||
}
|
||||
|
||||
impl Operator {
|
||||
fn exec(&self, a: u64, b: u64) -> u64 {
|
||||
match self {
|
||||
Operator::Add => a + b,
|
||||
Operator::Multiply => a * b,
|
||||
Operator::Concatenate => u64::pow(10, b.ilog10() + 1) * a + b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<Lines<T>> for Calibrations {
|
||||
fn from(input: Lines<T>) -> Self {
|
||||
let cals = input.map(|l| l.unwrap().as_str().into()).collect();
|
||||
Self { cals }
|
||||
}
|
||||
}
|
||||
|
||||
impl Calibrations {
|
||||
fn possible(&self, operators: &[Operator]) -> u64 {
|
||||
self.cals
|
||||
.par_iter()
|
||||
.map(|cal| eval_calibration(operators, cal.result, cal.numbers[0], &cal.numbers[1..]))
|
||||
.map(|result| result.unwrap_or(0))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_calibration(operators: &[Operator], expect: u64, left: u64, right: &[u64]) -> Option<u64> {
|
||||
if left > expect {
|
||||
// all operations make the number larger, so this branch is hopeless, early exit
|
||||
return None;
|
||||
}
|
||||
if right.is_empty() {
|
||||
// base case - no further operations
|
||||
if left == expect {
|
||||
return Some(left);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
operators
|
||||
.iter()
|
||||
.map(|oper| eval_calibration(operators, expect, oper.exec(left, right[0]), &right[1..]))
|
||||
.find_map(|result| result)
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day7, part1)]
|
||||
pub fn part1(cals: &Calibrations) -> u64 {
|
||||
let operators = [Operator::Multiply, Operator::Add];
|
||||
cals.possible(&operators)
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day7, part2)]
|
||||
pub fn part2(cals: &Calibrations) -> u64 {
|
||||
let operators = [Operator::Multiply, Operator::Add, Operator::Concatenate];
|
||||
cals.possible(&operators)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day7::*;
|
||||
|
||||
const EXAMPLE: &[u8] = b"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";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&get_input(EXAMPLE)), 3749);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&get_input(EXAMPLE)), 11387);
|
||||
}
|
||||
}
|
99
src/day8.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use grid::Grid;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::io::BufRead;
|
||||
|
||||
type HashSet<T> = FxHashSet<T>;
|
||||
|
||||
#[aoc_generator(day8)]
|
||||
pub fn get_input(input: &[u8]) -> AntennaMap {
|
||||
AntennaMap::from(input)
|
||||
}
|
||||
|
||||
pub struct AntennaMap {
|
||||
map: Grid<u8>,
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<T> for AntennaMap {
|
||||
fn from(input: T) -> Self {
|
||||
Self { map: Grid::from(input) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AntennaMap {
|
||||
fn find_antinodes(&self, start: usize, reps: Option<usize>) -> Grid<bool> {
|
||||
let mut antinodes = Grid::with_shape(self.map.width(), self.map.height(), false);
|
||||
// find the unique frequencies in a dumb way
|
||||
// NOTE: The dumb way is faster than the slightly-smarter ways I tried
|
||||
let freq_set: HashSet<&u8> = HashSet::from_iter(self.map.data.iter().filter(|c| **c != b'.'));
|
||||
|
||||
// for each unique frequency, get all the pairs' positions
|
||||
for freq in freq_set {
|
||||
for pair in self
|
||||
.map
|
||||
.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| *c == freq)
|
||||
.map(|(i, _)| self.map.coord(i as i64).unwrap())
|
||||
.permutations(2)
|
||||
{
|
||||
// permutations generates both pairs, ie. ((1,2),(2,1)) and ((2,1),(1,2)) so we don't need
|
||||
// to consider the 'negative' side of the line, which will be generated by the other pair
|
||||
let (a, b) = (pair[0], pair[1]);
|
||||
let offset = (a.x - b.x, a.y - b.y);
|
||||
for i in (start..).map_while(|i| if Some(i - start) != reps { Some(i as i64) } else { None }) {
|
||||
let node_pos = (a.x + i * offset.0, a.y + i * offset.1);
|
||||
if antinodes.set(&node_pos, true).is_none() {
|
||||
// left the grid
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
antinodes
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day8, part1)]
|
||||
pub fn part1(map: &AntennaMap) -> u64 {
|
||||
let antinodes = map.find_antinodes(1, Some(1));
|
||||
antinodes.count(&true) as u64
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day8, part2)]
|
||||
pub fn part2(map: &AntennaMap) -> u64 {
|
||||
let antinodes = map.find_antinodes(0, None);
|
||||
antinodes.count(&true) as u64
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day8::*;
|
||||
|
||||
const EXAMPLE: &[u8] = b"............
|
||||
........0...
|
||||
.....0......
|
||||
.......0....
|
||||
....0.......
|
||||
......A.....
|
||||
............
|
||||
............
|
||||
........A...
|
||||
.........A..
|
||||
............
|
||||
............";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&get_input(EXAMPLE)), 14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&get_input(EXAMPLE)), 34);
|
||||
}
|
||||
}
|
166
src/day9.rs
Normal file
@ -0,0 +1,166 @@
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use itertools::Itertools;
|
||||
use std::fmt::{Display, Write};
|
||||
use std::io::{BufRead, Lines};
|
||||
|
||||
#[aoc_generator(day9)]
|
||||
pub fn get_input(input: &[u8]) -> DiskMap {
|
||||
DiskMap::from(input.lines())
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
enum Unit {
|
||||
File(usize),
|
||||
Free,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Inode {
|
||||
id: usize,
|
||||
pos: usize,
|
||||
len: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DiskMap {
|
||||
map: Vec<Unit>,
|
||||
files: Vec<Inode>,
|
||||
frees: Vec<Inode>,
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<Lines<T>> for DiskMap {
|
||||
fn from(mut input: Lines<T>) -> Self {
|
||||
let line_s = input.next().unwrap().unwrap();
|
||||
let line = line_s.as_bytes();
|
||||
let mut file_id = 0;
|
||||
let mut map = Vec::new();
|
||||
let mut files = Vec::new();
|
||||
let mut frees = Vec::new();
|
||||
for (i, c) in line.iter().enumerate() {
|
||||
let len = c - b'0';
|
||||
if i % 2 == 0 {
|
||||
// file
|
||||
files.push(Inode {
|
||||
id: file_id,
|
||||
pos: map.len(),
|
||||
len,
|
||||
});
|
||||
for _ in 0..len {
|
||||
map.push(Unit::File(file_id))
|
||||
}
|
||||
file_id += 1;
|
||||
} else {
|
||||
// free
|
||||
frees.push(Inode {
|
||||
id: 0,
|
||||
pos: map.len(),
|
||||
len,
|
||||
});
|
||||
for _ in 0..len {
|
||||
map.push(Unit::Free)
|
||||
}
|
||||
}
|
||||
}
|
||||
Self { map, files, frees }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DiskMap {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for unit in &self.map {
|
||||
match unit {
|
||||
Unit::File(i) => f.write_char((b'0' + (*i % 10) as u8) as char)?,
|
||||
Unit::Free => f.write_char('.')?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskMap {
|
||||
fn checksum(&self) -> u64 {
|
||||
self.map
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, u)| match u {
|
||||
Unit::File(id) => i * id,
|
||||
Unit::Free => 0,
|
||||
})
|
||||
.sum::<usize>() as u64
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
#[aoc(day9, part1)]
|
||||
pub fn part1(map: &DiskMap) -> u64 {
|
||||
let mut map = map.to_owned();
|
||||
let mut last_free = 0;
|
||||
for file in map.files.iter().rev() {
|
||||
let frees = map
|
||||
.map
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(last_free) // we greedy fill, so no need to check for free space before the last one we used
|
||||
.take(file.pos + file.len as usize) // and we only need to search until the end of the current file
|
||||
.filter(|(_i, u)| **u == Unit::Free || **u == Unit::File(file.id)) // look for free space or our existing space
|
||||
.map(|(i, _u)| i)
|
||||
.take(file.len as usize) // get the first file.len free blocks
|
||||
.collect_vec();
|
||||
// Note: no need to test for too small frees list here, since we are guaranteed at worst to find our current position
|
||||
if frees[0] >= file.pos {
|
||||
// if the first available free is > file.pos, it's fully packed, job done
|
||||
break;
|
||||
}
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for j in 0..file.len as usize {
|
||||
map.map.swap(frees[j], file.pos + j);
|
||||
}
|
||||
last_free = frees[file.len as usize - 1]
|
||||
}
|
||||
map.checksum()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
#[aoc(day9, part2)]
|
||||
pub fn part2(map: &DiskMap) -> u64 {
|
||||
let mut map = map.to_owned();
|
||||
for file in map.files.iter().rev() {
|
||||
let free = map.frees.iter_mut().find(|inode| inode.len >= file.len); // find the first entry in the free space map large enough
|
||||
if let Some(free) = free {
|
||||
if free.pos >= file.pos {
|
||||
// if it's past our position, continue, but can't break since there might be free space for future files
|
||||
continue;
|
||||
}
|
||||
for j in 0..file.len {
|
||||
map.map.swap(free.pos + j as usize, file.pos + j as usize);
|
||||
}
|
||||
// Note: It is slightly faster to keep these hanging around in the free map with size = 0 then to remove them from the vec
|
||||
free.len -= file.len;
|
||||
free.pos += file.len as usize;
|
||||
|
||||
map.frees.push(Inode {
|
||||
id: 0,
|
||||
pos: file.pos,
|
||||
len: file.len,
|
||||
});
|
||||
}
|
||||
}
|
||||
map.checksum()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::day9::*;
|
||||
|
||||
const EXAMPLE: &[u8] = b"2333133121414131402";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&get_input(EXAMPLE)), 1928);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&get_input(EXAMPLE)), 2858);
|
||||
}
|
||||
}
|
28
src/lib.rs
Normal file
@ -0,0 +1,28 @@
|
||||
pub mod day1;
|
||||
pub mod day10;
|
||||
pub mod day11;
|
||||
pub mod day12;
|
||||
pub mod day13;
|
||||
pub mod day14;
|
||||
pub mod day15;
|
||||
pub mod day16;
|
||||
pub mod day17;
|
||||
pub mod day18;
|
||||
pub mod day19;
|
||||
pub mod day2;
|
||||
pub mod day20;
|
||||
pub mod day21;
|
||||
pub mod day22;
|
||||
pub mod day23;
|
||||
pub mod day24;
|
||||
pub mod day25;
|
||||
pub mod day3;
|
||||
pub mod day4;
|
||||
pub mod day5;
|
||||
pub mod day6;
|
||||
pub mod day7;
|
||||
pub mod day8;
|
||||
pub mod day9;
|
||||
|
||||
use aoc_runner_derive::aoc_lib;
|
||||
aoc_lib! { year = 2024 }
|
3
src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use aoc_runner_derive::aoc_main;
|
||||
|
||||
aoc_main! { lib = aoc2024 }
|
2
1/Cargo.lock → utils/grid/Cargo.lock
generated
@ -3,5 +3,5 @@
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "day1"
|
||||
name = "grid"
|
||||
version = "0.1.0"
|
@ -1,6 +1,9 @@
|
||||
[package]
|
||||
name = "day2"
|
||||
name = "grid"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
495
utils/grid/lib.rs
Normal file
@ -0,0 +1,495 @@
|
||||
use std::{
|
||||
fmt::{Debug, Display, Formatter, Write},
|
||||
io::{BufRead, Cursor},
|
||||
iter::repeat_n,
|
||||
mem::swap,
|
||||
ops::{Add, AddAssign, Sub},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Coord2d {
|
||||
pub x: i64,
|
||||
pub y: i64,
|
||||
}
|
||||
|
||||
pub trait AsCoord2d {
|
||||
fn to_coord(self) -> Coord2d;
|
||||
fn x(&self) -> i64;
|
||||
fn y(&self) -> i64;
|
||||
}
|
||||
|
||||
impl<T: AsCoord2d> Sub<T> for &Coord2d {
|
||||
type Output = Coord2d;
|
||||
fn sub(self, rhs: T) -> Self::Output {
|
||||
Coord2d {
|
||||
x: self.x() - rhs.x(),
|
||||
y: self.y() - rhs.y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsCoord2d> Add<T> for &Coord2d {
|
||||
type Output = Coord2d;
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
Coord2d {
|
||||
x: self.x() + rhs.x(),
|
||||
y: self.y() + rhs.y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsCoord2d> Add<&T> for Coord2d {
|
||||
type Output = Coord2d;
|
||||
fn add(self, rhs: &T) -> Self::Output {
|
||||
Coord2d {
|
||||
x: self.x() + rhs.x(),
|
||||
y: self.y() + rhs.y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsCoord2d for Coord2d {
|
||||
fn to_coord(self) -> Coord2d {
|
||||
self
|
||||
}
|
||||
fn x(&self) -> i64 {
|
||||
self.x
|
||||
}
|
||||
fn y(&self) -> i64 {
|
||||
self.y
|
||||
}
|
||||
}
|
||||
|
||||
impl AsCoord2d for &Coord2d {
|
||||
fn to_coord(self) -> Coord2d {
|
||||
self.to_owned()
|
||||
}
|
||||
fn x(&self) -> i64 {
|
||||
self.x
|
||||
}
|
||||
fn y(&self) -> i64 {
|
||||
self.y
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsCoord2d for (T, T)
|
||||
where
|
||||
T: Copy + TryInto<i64>,
|
||||
<T as TryInto<i64>>::Error: Debug,
|
||||
{
|
||||
fn to_coord(self) -> Coord2d {
|
||||
Coord2d {
|
||||
x: self.0.try_into().unwrap(),
|
||||
y: self.1.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
fn x(&self) -> i64 {
|
||||
self.0.try_into().unwrap()
|
||||
}
|
||||
fn y(&self) -> i64 {
|
||||
self.1.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsCoord2d for &(T, T)
|
||||
where
|
||||
T: Copy + TryInto<i64>,
|
||||
<T as TryInto<i64>>::Error: Debug,
|
||||
{
|
||||
fn to_coord(self) -> Coord2d {
|
||||
Coord2d {
|
||||
x: self.0.try_into().unwrap(),
|
||||
y: self.1.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
fn x(&self) -> i64 {
|
||||
self.0.try_into().unwrap()
|
||||
}
|
||||
fn y(&self) -> i64 {
|
||||
self.1.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Coord2d> for (i64, i64) {
|
||||
fn from(value: Coord2d) -> Self {
|
||||
(value.x, value.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GridRowIter<'a, T> {
|
||||
iter: std::slice::Iter<'a, T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Clone + Eq + PartialEq + Debug> GridRowIter<'a, T> {
|
||||
fn new(grid: &'a Grid<T>, y: i64) -> Self {
|
||||
let iter = grid.data[y as usize * grid.width()..(y as usize + 1) * grid.width()].iter();
|
||||
Self { iter }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for GridRowIter<'a, T> {
|
||||
type Item = &'a T;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next()
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GridColIter<'a, T> {
|
||||
grid: &'a Grid<T>,
|
||||
stride: usize,
|
||||
cur: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: Clone + Eq + PartialEq + Debug> GridColIter<'a, T> {
|
||||
fn new(grid: &'a Grid<T>, x: i64) -> Self {
|
||||
Self {
|
||||
grid,
|
||||
stride: grid.width(),
|
||||
cur: x as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for GridColIter<'a, T> {
|
||||
type Item = &'a T;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let cur = self.cur;
|
||||
self.cur += self.stride;
|
||||
if cur < self.grid.data.len() {
|
||||
Some(&self.grid.data[cur])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.grid.height() - 1, Some(self.grid.height() - 1))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub struct Grid<T> {
|
||||
pub data: Vec<T>,
|
||||
width: i64,
|
||||
}
|
||||
|
||||
impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
||||
/// Returns a new [Grid] with the same shape (width x height) as `self`, filled with `fill`
|
||||
pub fn same_shape<NT: Clone + Eq + PartialEq + Debug>(&self, fill: NT) -> Grid<NT> {
|
||||
Grid::with_shape(self.width(), self.height(), fill)
|
||||
}
|
||||
/// Returns a new [Grid] with the given shape (width x height), filled with `fill`
|
||||
pub fn with_shape(width: usize, height: usize, fill: T) -> Self {
|
||||
Self {
|
||||
data: Vec::from_iter(repeat_n(fill, width * height)),
|
||||
width: width as i64,
|
||||
}
|
||||
}
|
||||
pub fn width(&self) -> usize {
|
||||
self.width as usize
|
||||
}
|
||||
pub fn height(&self) -> usize {
|
||||
self.data.len() / self.width()
|
||||
}
|
||||
pub fn pos<C: AsCoord2d>(&self, c: &C) -> i64 {
|
||||
c.y() * self.width + c.x()
|
||||
}
|
||||
pub fn coord(&self, pos: i64) -> Option<Coord2d> {
|
||||
if pos < 0 || pos >= self.data.len() as i64 {
|
||||
None
|
||||
} else {
|
||||
Some(Coord2d {
|
||||
x: pos % self.width,
|
||||
y: pos / self.width,
|
||||
})
|
||||
}
|
||||
}
|
||||
// pub fn coord_iter(&self) -> CoordIter<_> {
|
||||
// CoordIter { pos: 0, grid: self }
|
||||
// }
|
||||
pub fn is_valid<C: AsCoord2d>(&self, c: &C) -> bool {
|
||||
if c.x() < 0 || c.x() >= self.width {
|
||||
return false;
|
||||
}
|
||||
if c.y() < 0 || c.y() as usize >= self.height() {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
fn valid_pos<C: AsCoord2d>(&self, c: &C) -> Option<usize> {
|
||||
if c.x() < 0 || c.x() >= self.width {
|
||||
return None;
|
||||
}
|
||||
if c.y() < 0 || c.y() as usize >= self.height() {
|
||||
return None;
|
||||
}
|
||||
let pos = self.pos(c);
|
||||
if pos < 0 || pos as usize >= self.data.len() {
|
||||
return None;
|
||||
}
|
||||
self.pos(c).try_into().ok()
|
||||
}
|
||||
pub fn get<C: AsCoord2d>(&self, c: &C) -> Option<&T> {
|
||||
match self.valid_pos(c) {
|
||||
Some(pos) => Some(&self.data[pos]),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn get_mut<C: AsCoord2d>(&mut self, c: &C) -> Option<&mut T> {
|
||||
match self.valid_pos(c) {
|
||||
Some(pos) => Some(self.data.get_mut(pos).unwrap()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn set<C: AsCoord2d>(&mut self, c: &C, mut val: T) -> Option<T> {
|
||||
match self.valid_pos(c) {
|
||||
Some(pos) => {
|
||||
swap(&mut self.data[pos], &mut val);
|
||||
Some(val)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn increment<'a, A, C: AsCoord2d>(&'a mut self, c: &C, i: A) -> Option<&'a T>
|
||||
where
|
||||
T: AddAssign<A>,
|
||||
{
|
||||
match self.valid_pos(c) {
|
||||
Some(pos) => {
|
||||
self.data[pos] += i;
|
||||
Some(&self.data[pos])
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn row(&self, y: i64) -> Option<&[T]> {
|
||||
if y < self.height() as i64 && y >= 0 {
|
||||
Some(&self.data[self.pos(&(0, y)) as usize..self.pos(&(self.width, y)) as usize])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_iter(&self, y: i64) -> Option<GridRowIter<T>> {
|
||||
if (y as usize) < self.height() {
|
||||
Some(GridRowIter::new(self, y))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn col(&self, x: i64) -> Option<Vec<&T>> {
|
||||
if let Some(iter) = self.col_iter(x) {
|
||||
Some(iter.collect())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn col_iter(&self, x: i64) -> Option<GridColIter<T>> {
|
||||
if (x as usize) < self.width() {
|
||||
Some(GridColIter::new(self, x))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find(&self, haystack: &T) -> Option<Coord2d> {
|
||||
self.coord(
|
||||
self.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(pos, val)| if val == haystack { Some(pos as i64) } else { None })
|
||||
.unwrap_or(-1),
|
||||
)
|
||||
}
|
||||
pub fn count(&self, haystack: &T) -> usize {
|
||||
self.data.iter().filter(|item| *item == haystack).count()
|
||||
}
|
||||
|
||||
pub fn forward_slice<C: AsCoord2d>(&self, start: &C, len: i64) -> Option<&[T]> {
|
||||
let pos = (self.valid_pos(start), self.valid_pos(&(start.x() + len - 1, start.y())));
|
||||
match pos {
|
||||
(Some(pos1), Some(pos2)) => Some(&self.data[pos1..pos2 + 1]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap<A: AsCoord2d, B: AsCoord2d>(&mut self, a: A, b: B) {
|
||||
if let (Some(a), Some(b)) = (self.valid_pos(&a), self.valid_pos(&b)) {
|
||||
self.data.swap(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
// fn window_compare_impl<const REV: bool>(&self, needle: &[T]) -> Vec<(i64, i64)> {
|
||||
// if (self.width as usize) < needle.len() {
|
||||
// return Vec::new();
|
||||
// }
|
||||
// let mut res = Vec::new();
|
||||
// for y in 0..self.height() as i64 {
|
||||
// let mut windows_tmp = self.row(y).unwrap().windows(needle.len());
|
||||
// let windows = if REV {
|
||||
// windows_tmp.rev()
|
||||
// } else {
|
||||
// windows_tmp
|
||||
// };
|
||||
|
||||
// res.extend(
|
||||
// windows
|
||||
// .enumerate()
|
||||
// .filter_map(|(x, w)| if w == needle { Some((x as i64, y)) } else { None }),
|
||||
// );
|
||||
// }
|
||||
// res
|
||||
// }
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<T> for Grid<u8> {
|
||||
fn from(input: T) -> Grid<u8> {
|
||||
let mut data = Vec::new();
|
||||
let mut width = 0;
|
||||
for line in input.split(b'\n').map(|i| i.unwrap()) {
|
||||
if width == 0 {
|
||||
width = line.len() as i64
|
||||
} else if line.len() as i64 != width {
|
||||
panic!("Grids must have fixed length rows")
|
||||
}
|
||||
data.extend_from_slice(&line);
|
||||
}
|
||||
Grid { data, width }
|
||||
}
|
||||
}
|
||||
|
||||
// Should be Grid<char>?
|
||||
impl FromStr for Grid<u8> {
|
||||
type Err = Box<dyn std::error::Error>;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Cursor::new(s).into())
|
||||
}
|
||||
}
|
||||
|
||||
// impl<T: Copy + Eq + PartialEq + Display + Debug + Into<char>> Display for Grid<T> {
|
||||
// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
// for y in 0..self.height() {
|
||||
// for x in 0..self.width() {
|
||||
// f.write_fmt(format_args!("{}",self.get(x as i64, y as i64).unwrap() as char))?;
|
||||
// }
|
||||
// f.write_char('\n')?;
|
||||
// }
|
||||
// f.write_char('\n')
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Display for Grid<u8> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for y in 0..self.height() {
|
||||
for x in 0..self.width() {
|
||||
f.write_fmt(format_args!("{}", *self.get(&(x as i64, y as i64)).unwrap() as char))?;
|
||||
}
|
||||
f.write_char('\n')?;
|
||||
}
|
||||
f.write_char('\n')
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Grid<bool> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for y in 0..self.height() {
|
||||
for x in 0..self.width() {
|
||||
f.write_fmt(format_args!(
|
||||
"{}",
|
||||
match *self.get(&(x as i64, y as i64)).unwrap() {
|
||||
true => '.',
|
||||
false => '#',
|
||||
}
|
||||
))?;
|
||||
}
|
||||
f.write_char('\n')?;
|
||||
}
|
||||
f.write_char('\n')
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static TEST_VECTOR: &[u8] = b"ABCD
|
||||
EFGH
|
||||
IJKL
|
||||
FBCG";
|
||||
|
||||
static TEST_VECTOR_S: &str = "ABCD
|
||||
EFGH
|
||||
IJKL
|
||||
FBCG";
|
||||
|
||||
fn unchecked_load() -> Grid<u8> {
|
||||
Grid::from(TEST_VECTOR)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string() {
|
||||
let grid = unchecked_load();
|
||||
assert_eq!(grid.data, "ABCDEFGHIJKLFBCG".as_bytes());
|
||||
assert_eq!(
|
||||
TEST_VECTOR_S.parse::<Grid<u8>>().unwrap().data,
|
||||
"ABCDEFGHIJKLFBCG".as_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexing() {
|
||||
let grid = unchecked_load();
|
||||
assert_eq!(grid.get(&(0, 0)), Some(b'A').as_ref());
|
||||
assert_eq!(grid.get(&(3, 3)), Some(b'G').as_ref());
|
||||
assert_eq!(grid.get(&(-1, 0)), None);
|
||||
assert_eq!(grid.get(&(0, -1)), None);
|
||||
assert_eq!(grid.get(&(5, 0)), None);
|
||||
assert_eq!(grid.get(&(0, 5)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forward_slice() {
|
||||
let grid = unchecked_load();
|
||||
assert_eq!(grid.forward_slice(&(0, 0), 2), Some(b"AB".as_slice()));
|
||||
assert_eq!(grid.forward_slice(&(2, 0), 2), Some(b"CD".as_slice()));
|
||||
assert_eq!(grid.forward_slice(&(2, 0), 3), None);
|
||||
assert_eq!(grid.forward_slice(&(0, 2), 4), Some(b"IJKL".as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_iter() {
|
||||
let grid = unchecked_load();
|
||||
assert_eq!(
|
||||
grid.row_iter(2).unwrap().collect::<Vec<_>>(),
|
||||
[&b'I', &b'J', &b'K', &b'L']
|
||||
);
|
||||
assert!(grid.row_iter(-1).is_none());
|
||||
assert!(grid.row_iter(4).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn col_iter() {
|
||||
let grid = unchecked_load();
|
||||
assert_eq!(
|
||||
grid.col_iter(2).unwrap().collect::<Vec<_>>(),
|
||||
[&b'C', &b'G', &b'K', &b'C']
|
||||
);
|
||||
assert!(grid.col_iter(-1).is_none());
|
||||
assert!(grid.col_iter(4).is_none());
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn window_compare() {
|
||||
// let grid = unchecked_load();
|
||||
// assert_eq!(grid.window_compare(b"IJKL"), &[(0, 2)]);
|
||||
// assert_eq!(grid.window_compare(b"BC"), &[(1, 0), (1, 3)]);
|
||||
// assert_eq!(grid.window_compare(b"LF").len(), 0);
|
||||
// }
|
||||
}
|
25
utils/misc/Cargo.lock
generated
Normal file
@ -0,0 +1,25 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "misc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "day1"
|
||||
name = "misc"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
num-traits = "0.2.19"
|
98
utils/misc/src/lib.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use num_traits::Signed;
|
||||
use std::fmt::Display;
|
||||
use std::ops::{Add, AddAssign};
|
||||
|
||||
/// Wrapped signed integer with custom upper bound with wrapping of 0s to the upper bound
|
||||
#[derive(Eq, Clone, Copy)]
|
||||
pub struct CustomWrapped<T: Signed + Copy> {
|
||||
pub val: T,
|
||||
pub bound: T,
|
||||
}
|
||||
|
||||
impl<T: Signed + Copy> Add<T> for CustomWrapped<T> {
|
||||
type Output = CustomWrapped<T>;
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
Self {
|
||||
val: ((self.val + rhs % self.bound) + self.bound) % self.bound,
|
||||
bound: self.bound,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Signed + Copy> Add<T> for &CustomWrapped<T> {
|
||||
type Output = CustomWrapped<T>;
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
CustomWrapped {
|
||||
val: ((self.val + rhs % self.bound) + self.bound) % self.bound,
|
||||
bound: self.bound,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Signed + Copy> AddAssign<T> for CustomWrapped<T> {
|
||||
fn add_assign(&mut self, rhs: T) {
|
||||
self.val = ((self.val + rhs % self.bound) + self.bound) % self.bound
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Signed + Copy> CustomWrapped<T> {
|
||||
pub fn new(val: T, bound: T) -> Self {
|
||||
Self { val, bound }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Signed + Copy + PartialEq> PartialEq for CustomWrapped<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.val.eq(&other.val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Signed + PartialOrd + Copy> PartialOrd for CustomWrapped<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.val.partial_cmp(&other.val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Signed + Ord + Copy> Ord for CustomWrapped<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.val.cmp(&other.val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Signed + PartialEq + Copy> PartialEq<T> for CustomWrapped<T> {
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
self.val == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Signed + PartialOrd + Copy> PartialOrd<T> for CustomWrapped<T> {
|
||||
fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
|
||||
self.val.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + Signed + Copy> Display for CustomWrapped<T>
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.val.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
// impl<T> Into<T> for CustomWrapped<T> {
|
||||
// fn into(self) -> T {
|
||||
// self.val
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|