Compare commits

..

59 Commits

Author SHA1 Message Date
a7cda04308 add 2024 day 4 2024-12-10 20:55:29 -05:00
ec3b438f40 2024 day 3 part 2 2024-12-06 20:39:16 -05:00
e1496aa25a add 2024 day 3 part 1 2024-12-06 11:44:57 -05:00
8b265bc3ec add 2024 day 2 2024-12-03 15:45:52 -05:00
1f6d987c53 add 2024 day 1 2024-12-02 09:58:45 -05:00
9ef31dde55 finish day 9 2022-12-09 22:41:21 -08:00
Joseph Montanaro
9236e70abe broken implementation of day 9 2022-12-09 17:49:56 -08:00
Joseph Montanaro
2e2d668269 add day 8 2022-12-08 14:29:47 -08:00
Joseph Montanaro
6abb28f96f finish day7 2022-12-08 10:14:48 -08:00
393db1d3bf broken implementation of 7.1 2022-12-07 22:06:52 -08:00
Joseph Montanaro
a998e68b71 half-baked partial start to day 7 2022-12-07 16:43:51 -08:00
166008ebb9 suspiciously straightforward 2022-12-06 18:48:04 -08:00
3b9a3eed0c it's ugly but it works 2022-12-05 20:12:57 -08:00
Joseph Montanaro
a10ff091da start day 5 2022-12-05 16:46:00 -08:00
19d28ea991 day 4 2022-12-04 21:43:02 -08:00
3bc97158bc day 3 2022-12-04 20:38:46 -08:00
Joseph Montanaro
86eb94514e day 2 2022-12-02 16:11:46 -08:00
Joseph Montanaro
c7c444bce9 aoc 2022 day 1 2022-12-02 14:52:51 -08:00
8c6ffc2eb9 finish day 18 2022-01-08 19:50:46 -08:00
Joseph Montanaro
61374b8655 completely rework day 18 2022-01-07 14:57:22 -08:00
54053779aa work on exploding and splitting 2022-01-06 21:37:13 -08:00
Joseph Montanaro
63818ee2ee day 18 data parsing 2022-01-06 16:05:19 -08:00
Joseph Montanaro
5abd82efff day 17 2022-01-05 16:03:23 -08:00
6492fd5c03 day 16 2022-01-02 10:55:42 -08:00
5633d37773 day 15 2021-12-31 08:25:28 -08:00
33e9af89be day 14 2021-12-29 13:51:59 -08:00
0f6f5cfc76 remove sample data from day 13 2021-12-29 07:05:22 -08:00
50db6dafd0 day 13 2021-12-29 07:05:02 -08:00
dffb8a8673 day 12 2021-12-28 15:14:08 -08:00
df55d78dec day 11 2021-12-25 16:57:34 -08:00
584cad1690 day 10 part 1 2021-12-16 06:04:25 -08:00
Joseph Montanaro
bf72a26b2b finish day 9 2021-12-15 13:20:33 -08:00
8991100b59 completely rework day 9, still broken though 2021-12-14 21:08:49 -08:00
Joseph Montanaro
6cc7cb8752 add Vec2 to lib.rs 2021-12-14 11:26:24 -08:00
5bcc40b030 day 9 part 1 (not working) 2021-12-12 21:55:21 -08:00
24b88815f2 I hate day 8 so much 2021-12-12 15:53:05 -08:00
Joseph Montanaro
3dc38678ff a bunch of crap that's probably worthless 2021-12-10 14:57:31 -08:00
Joseph Montanaro
754b296abf add lookup table 2021-12-09 15:38:15 -08:00
Joseph Montanaro
aca62e1c44 day 8 part 1 2021-12-09 15:16:13 -08:00
Joseph Montanaro
2133290565 simplify day 7 2021-12-09 13:18:32 -08:00
Joseph Montanaro
19e219bcc3 day 7 2021-12-09 12:56:44 -08:00
d77c4ed0c9 suspiciously straightforward 2021-12-08 22:20:01 -08:00
2269a7e086 at least part 2 was easy 2021-12-08 21:52:32 -08:00
Joseph Montanaro
444a7f6e47 start work on day 5 2021-12-08 14:53:33 -08:00
Joseph Montanaro
2fbc98de3d well that was easy 2021-12-07 10:37:24 -08:00
Joseph Montanaro
ca360f7baa remove I/O from timed section 2021-12-07 10:26:22 -08:00
fb8ec81683 fix day 4 2021-12-07 06:04:53 -08:00
Joseph Montanaro
6577bae991 day 4 part 1 (not currently working, also massively overcomplicated) 2021-12-06 16:30:10 -08:00
Joseph Montanaro
c55a721067 use the fun functional approach 2021-12-06 08:59:20 -08:00
340ef2d4d3 part 2 should not have taken this long 2021-12-04 13:41:55 -08:00
Joseph Montanaro
b1a8ab8bcd refactor in preparation for part 2 2021-12-03 14:48:34 -08:00
Joseph Montanaro
b519e0bad2 day 3 part 1 2021-12-03 14:08:03 -08:00
Joseph Montanaro
335b7a25cf remove unnecessary lifetime 2021-12-03 12:23:08 -08:00
Joseph Montanaro
7681bbc760 add timekeeping, also day1 that got left out somehow 2021-12-03 11:28:22 -08:00
Joseph Montanaro
cf1fb4c792 more refactoring 2021-12-03 11:06:30 -08:00
Joseph Montanaro
161e5f7a5f add day 2 2021-12-03 10:34:31 -08:00
Joseph Montanaro
cde23745ef I feel like this shouldn't have been so difficult 2021-12-03 08:42:11 -08:00
Joseph Montanaro
6f60951b94 use color-eyre for error handling 2021-12-01 15:38:32 -08:00
Joseph Montanaro
7b4cb884c1 2021 day 1 2021-12-01 12:54:36 -08:00
39 changed files with 4580 additions and 0 deletions

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
*/data/* */data/*
*/target/*
**.exe **.exe
*.out

351
2021/Cargo.lock generated Normal file
View File

@ -0,0 +1,351 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "advent-2021"
version = "0.1.0"
dependencies = [
"color-eyre",
"num",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color-eyre"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7"
dependencies = [
"backtrace",
"color-spantrace",
"eyre",
"indenter",
"once_cell",
"owo-colors",
"tracing-error",
]
[[package]]
name = "color-spantrace"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1"
dependencies = [
"once_cell",
"owo-colors",
"tracing-core",
"tracing-error",
]
[[package]]
name = "eyre"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "gimli"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
"adler",
"autocfg",
]
[[package]]
name = "num"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "owo-colors"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
[[package]]
name = "pin-project-lite"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]]
name = "proc-macro2"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "syn"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
dependencies = [
"lazy_static",
]
[[package]]
name = "tracing-error"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24"
dependencies = [
"tracing",
"tracing-subscriber",
]
[[package]]
name = "tracing-subscriber"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
dependencies = [
"sharded-slab",
"thread_local",
"tracing-core",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"

11
2021/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "advent-2021"
version = "0.1.0"
authors = ["Joseph Montanaro <jfmontanaro@modg.org>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
color-eyre = "^0.5.11"
num = "^0.4.0"

29
2021/src/day1.rs Normal file
View File

@ -0,0 +1,29 @@
use color_eyre::eyre;
use crate::lib::ParseLines;
pub fn run(data: &str) -> eyre::Result<(i32, i32)> {
let mut single_increases = 0;
let mut window_increases = 0;
let mut prev = (None, None, None);
for res in data.parse_lines::<i32>() {
let n = res?;
if let Some(p) = prev.2 {
// apparently we can't combine "if let" and regular boolean conditions
if n > p {
single_increases += 1;
}
}
if let (Some(first), Some(second), Some(third)) = prev {
if (n + second + third) > (first + second + third) {
window_increases += 1;
}
}
// would have forgotten this if not for the warning triggered by "let mut" without mutation
prev = (prev.1, prev.2, Some(n));
}
Ok((single_increases, window_increases))
}

108
2021/src/day10.rs Normal file
View File

@ -0,0 +1,108 @@
use color_eyre::eyre;
use eyre::eyre;
#[derive(Copy, Clone, PartialEq)]
enum BracketKind {
Paren,
Square,
Angle,
Curly,
}
use BracketKind::*;
#[derive(Copy, Clone, PartialEq)]
enum GroupDelimiter {
Start(BracketKind),
End(BracketKind),
}
use GroupDelimiter::*;
enum LineStatus {
Valid,
Incomplete(Vec<BracketKind>),
Corrupted(usize),
}
fn score_corrupted(bk: BracketKind) -> usize {
match bk {
Paren => 3,
Square => 57,
Curly => 1197,
Angle => 25137,
}
}
fn score_incomplete(brackets: &[BracketKind]) -> usize {
let mut total = 0;
for bk in brackets.iter().rev() {
let additional = match bk {
Paren => 1,
Square => 2,
Curly => 3,
Angle => 4,
};
total = total * 5 + additional;
}
total
}
fn parse_bracket(b: char) -> eyre::Result<GroupDelimiter> {
match b {
'(' => Ok(Start(Paren)),
')' => Ok(End(Paren)),
'[' => Ok(Start(Square)),
']' => Ok(End(Square)),
'<' => Ok(Start(Angle)),
'>' => Ok(End(Angle)),
'{' => Ok(Start(Curly)),
'}' => Ok(End(Curly)),
_ => Err(eyre!("Invalid value for bracket: {}", b)),
}
}
fn parse_line(line: &str) -> eyre::Result<LineStatus> {
let mut state = Vec::new();
for c in line.chars() {
match parse_bracket(c)? {
Start(current) => state.push(current),
End(current) => {
match state.last() {
Some(&prev) if prev == current => {state.pop();},
_ => return Ok(LineStatus::Corrupted(score_corrupted(current))),
}
}
}
}
if state.len() > 0 {
Ok(LineStatus::Incomplete(state))
}
else {
Ok(LineStatus::Valid)
}
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let mut total_corrupted = 0;
let mut completions = Vec::new();
for line in data.lines() {
match parse_line(line)? {
LineStatus::Corrupted(s) => total_corrupted += s,
LineStatus::Incomplete(state) => completions.push(score_incomplete(&state)),
_ => (),
}
}
completions.sort();
let middle = completions.len() / 2 + 1; // given an odd length, this is the middle value
Ok((total_corrupted, completions[middle]))
}

76
2021/src/day11.rs Normal file
View File

@ -0,0 +1,76 @@
use std::collections::HashSet;
use color_eyre::eyre;
use eyre::eyre;
use crate::lib::Vec2;
struct Octopi {
grid: Vec2<u8>,
flashed: HashSet<(usize, usize)>,
}
impl Octopi {
fn step(&mut self) -> usize {
for row in 0..self.grid.row_count() {
for col in 0..self.grid.col_count() {
self.inc(row, col);
}
}
let count = self.flashed.len();
self.flashed.clear();
count
}
fn inc(&mut self, row: usize, col: usize) {
if self.flashed.contains(&(row, col)) {
return;
}
let energy = self.grid.get_mut(row, col);
*energy += 1;
if *energy > 9 {
self.flashed.insert((row, col));
*energy = 0;
for (n_row, n_col) in self.grid.neighbor_coords(row, col) {
self.inc(n_row, n_col);
}
}
}
}
fn load(data: &str) -> eyre::Result<Octopi> {
let mut grid = Vec2::new(10);
for line in data.lines() {
let mut row = Vec::with_capacity(10); // kinda inefficient but whatever, it only happens once
for c in line.chars() {
match c.to_digit(10) {
Some(d) => row.push(d as u8),
None => return Err(eyre!("Invalid digit: {}", c)),
}
}
grid.push(row);
}
Ok(Octopi {grid, flashed: HashSet::new()})
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let mut octopi = load(data)?;
let mut num_flashes = 0;
let mut two = None;
for i in 0.. {
let n = octopi.step();
if i < 100 {
num_flashes += n;
}
if n == 100 {
two = Some(i);
break;
}
}
Ok((num_flashes, two.unwrap() + 1))
}

174
2021/src/day12.rs Normal file
View File

@ -0,0 +1,174 @@
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
use color_eyre::eyre;
use eyre::eyre;
use crate::lib::IterExt;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum Size {
Big,
Small,
}
// this is a terrible way to do it, because it's completely tied to
// the dataset and needs to be redone in order to e.g. use a sample
// dataset. A better way would be to use [char; 2] to represent each
// cave and supply a method that computes size from letter case.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[allow(non_camel_case_types)]
enum Tag {
start,
de,
DN,
jv,
MU,
oa,
OC,
rj,
vd,
VP,
WK,
yb,
end,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct Cave {
tag: Tag,
size: Size,
}
impl FromStr for Cave {
type Err = eyre::Report;
fn from_str(s: &str) -> eyre::Result<Self> {
let cave = match s {
"start" => Cave {tag: Tag::start, size: Size::Small},
"de" => Cave {tag: Tag::de, size: Size::Small},
"DN" => Cave {tag: Tag::DN, size: Size::Big},
"jv" => Cave {tag: Tag::jv, size: Size::Small},
"MU" => Cave {tag: Tag::MU, size: Size::Big},
"oa" => Cave {tag: Tag::oa, size: Size::Small},
"OC" => Cave {tag: Tag::OC, size: Size::Big},
"rj" => Cave {tag: Tag::rj, size: Size::Small},
"vd" => Cave {tag: Tag::vd, size: Size::Small},
"VP" => Cave {tag: Tag::VP, size: Size::Big},
"WK" => Cave {tag: Tag::WK, size: Size::Big},
"yb" => Cave {tag: Tag::yb, size: Size::Small},
"end" => Cave {tag: Tag::end, size: Size::Small},
_ => return Err(eyre!("Invalid cave tag: {}", s)),
};
Ok(cave)
}
}
type CaveMap = HashMap<Tag, Vec<Cave>>;
struct Path {
order: Vec<Cave>,
visited: HashSet<Cave>,
doubled: Option<Cave>
}
impl Path {
fn new() -> Self {
Path {
order: vec![Cave {tag: Tag::start, size: Size::Small}],
visited: HashSet::new(),
doubled: None,
}
}
fn push(&mut self, cave: Cave) {
self.order.push(cave);
if cave.size == Size::Small {
let inserted = self.visited.insert(cave);
if !inserted { // value was already present in set
self.doubled = Some(cave);
}
}
}
fn pop(&mut self) {
let cave = self.order.pop().unwrap();
if cave.size == Size::Small {
match self.doubled {
Some(d) if d == cave => {self.doubled = None},
_ => {self.visited.remove(&cave);}
}
}
}
fn last(&self) -> Option<&Cave> {
self.order.last()
}
}
fn load(data: &str) -> eyre::Result<CaveMap> {
let mut map = HashMap::new();
for line in data.lines() {
let (a, b) = line.split('-').take_pair()?;
let cave_a = a.parse::<Cave>()?;
let cave_b = b.parse::<Cave>()?;
let neighbors_a = map.entry(cave_a.tag).or_insert_with(|| vec![]);
neighbors_a.push(cave_b);
let neighbors_b = map.entry(cave_b.tag).or_insert_with(|| vec![]);
neighbors_b.push(cave_a);
}
Ok(map)
}
fn can_visit_part1(path: &Path, cave: &Cave) -> bool {
if cave.size == Size::Big {true}
else if cave.tag == Tag::start {false}
else if path.visited.contains(cave) {false}
else {true}
}
fn can_visit_part2(path: &Path, cave: &Cave) -> bool {
if cave.size == Size::Big {true}
else if cave.tag == Tag::start {false}
else if path.visited.contains(cave) && path.doubled.is_some() {false}
else {true}
}
fn search<F>(map: &CaveMap, path: &mut Path, can_visit: F) -> Vec<Vec<Cave>>
where F: Fn(&Path, &Cave) -> bool + Copy
{
let mut result = Vec::new();
for neighbor in &map[&path.last().unwrap().tag] {
if neighbor.tag == Tag::end {
path.push(*neighbor);
result.push(path.order.clone());
path.pop();
}
else if can_visit(path, neighbor) {
path.push(*neighbor);
let child_paths = search(map, path, can_visit);
result.extend(child_paths);
path.pop();
}
}
result
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let map = load(data)?;
let one = search(&map, &mut Path::new(), can_visit_part1);
let two = search(&map, &mut Path::new(), can_visit_part2);
Ok((one.len(), two.len()))
}

108
2021/src/day13.rs Normal file
View File

@ -0,0 +1,108 @@
use color_eyre::eyre;
use eyre::eyre;
use crate::lib::{Vec2, IterExt};
enum Direction {
Horizontal,
Vertical,
}
struct Line {
value: usize,
direction: Direction,
}
fn load(data: &str) -> eyre::Result<(Vec2<bool>, Vec<Line>)> {
// populate a 1500x1500 grid
let mut grid: Vec2<bool> = Vec2::with_capacity(1500, 1500);
grid.fill_to_cap(false);
let mut lines = data.lines();
for line in &mut lines {
if line == "" {break}
let (col, row) = line.split(',').map(|s| s.parse::<usize>()).take_pair()?;
grid.set(row?, col?, true);
}
// having consumed the coordinates, we can move on to the fold lines
let mut folds = Vec::new();
for line in lines {
let (axis, intercept) = line
.split(' ')
.nth(2)
.ok_or_else(|| eyre!("Invalid fold line statement: {}", line))?
.split('=')
.take_pair()?;
let direction = match axis {
"x" => Direction::Vertical,
"y" => Direction::Horizontal,
_ => return Err(eyre!("Invalid axis: {}", axis)),
};
let value = intercept.parse::<usize>()?;
folds.push(Line {value, direction});
}
Ok((grid, folds))
}
fn fold(grid: &Vec2<bool>, line: &Line) -> Vec2<bool> {
let mut new_rows = grid.row_count();
let mut new_cols = grid.col_count();
match line.direction {
Direction::Horizontal => new_rows = line.value,
Direction::Vertical => new_cols = line.value,
}
let mut new_grid = Vec2::with_capacity(new_rows, new_cols);
new_grid.fill_to_cap(false);
for row in 0..new_rows {
for col in 0..new_cols {
if *grid.get(row, col) {
new_grid.set(row, col, true);
}
let mut mirror_row = row;
let mut mirror_col = col;
match line.direction {
Direction::Horizontal => mirror_row = 2 * new_rows - row,
Direction::Vertical => mirror_col = 2 * new_cols - col,
}
if mirror_row < grid.row_count() && mirror_col < grid.col_count()
&& *grid.get(mirror_row, mirror_col)
{
new_grid.set(row, col, true);
}
}
}
new_grid
}
fn show(grid: &Vec2<bool>) {
for row in grid.rows() {
let line = row.iter().map(|&v| if v {'#'} else {' '}).collect::<String>();
println!("[{}]", line);
}
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let (grid, folds) = load(data)?;
let mut folded = fold(&grid, &folds[0]);
let one = folded.values().filter(|v| **v).count();
for line in &folds[1..] {
folded = fold(&folded, line);
}
show(&folded);
Ok((one, 0))
}

83
2021/src/day14.rs Normal file
View File

@ -0,0 +1,83 @@
use std::collections::HashMap;
use color_eyre::eyre;
use crate::lib::{IterExt, Counter};
type Pairs = Counter<(char, char)>;
type Rules = HashMap<(char, char), char>;
fn load(data: &str) -> eyre::Result<(Pairs, Rules)> {
let mut lines = data.lines();
let template = lines.next().unwrap().to_string();
let mut pairs = Counter::new();
for i in 1..template.len() {
let a = template.as_bytes()[i - 1] as char;
let b = template.as_bytes()[i] as char;
pairs.inc((a, b));
}
lines.next();
let mut rules = HashMap::new();
for line in &mut lines {
let (pair_s, tgt_s) = line.split(" -> ").take_pair()?;
let pair = pair_s.chars().take_pair()?;
let tgt = tgt_s.chars().next().unwrap();
rules.insert(pair, tgt);
}
Ok((pairs, rules))
}
fn expand(src: &Pairs, rules: &Rules) -> Pairs {
let mut dst = Counter::new();
for (&(a, b), count) in src.iter() {
if let Some(&tween) = rules.get(&(a, b)) {
dst.inc_by((a, tween), count);
dst.inc_by((tween, b), count);
}
}
dst
}
fn score(pairs: &Pairs) -> usize {
// "score" is defined as the number of occurences of the most common
// character minus the number of occurrences of the least common
let mut firsts = Counter::new();
let mut lasts = Counter::new();
for (pair, count) in pairs.iter() {
let (a, b) = *pair;
firsts.inc_by(a, count);
lasts.inc_by(b, count);
}
// the count of a given character is the number of times it appears
// in either first or last place of a pair, whichever is greater
let mut counts = Counter::new();
for (c, count) in firsts.iter().chain(lasts.iter()) {
if count > counts.get(c) {
counts.set(*c, count);
}
}
let max = counts.iter().map(|(_c, n)| n).max().unwrap();
let min = counts.iter().map(|(_c, n)| n).min().unwrap();
max - min
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let (mut pairs, rules) = load(data)?;
for _i in 0..10 {pairs = expand(&pairs, &rules);}
let one = score(&pairs);
for _i in 0..30 {pairs = expand(&pairs, &rules);}
let two = score(&pairs);
Ok((one, two))
}

105
2021/src/day15.rs Normal file
View File

@ -0,0 +1,105 @@
use color_eyre::eyre;
use std::collections::{HashMap, BinaryHeap};
use std::cmp::{Ord, PartialOrd, Ordering};
use crate::lib::Vec2;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct Node {
row: usize,
col: usize,
cost: usize,
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
other.cost.cmp(&self.cost)
.then_with(|| self.row.cmp(&other.row))
.then_with(|| self.col.cmp(&other.col))
}
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn search(graph: &Vec2<usize>) -> usize {
// lifted shamelessly from https://doc.rust-lang.org/std/collections/binary_heap/index.html
let mut q = BinaryHeap::new();
let start = Node {row: 0, col: 0, cost: 0};
q.push(start);
let mut costs = HashMap::new();
while let Some(node) = q.pop() {
if node.row == graph.row_count() - 1 && node.col == graph.col_count() - 1 {
return node.cost;
}
if &node.cost > costs.get(&(node.row, node.col)).unwrap_or(&usize::MAX) {
continue;
}
for (row, col) in graph.rect_neighbors(node.row, node.col) {
let hop_cost = graph.get(row, col);
let neighbor = Node {row, col, cost: node.cost + hop_cost};
if &neighbor.cost < costs.get(&(row, col)).unwrap_or(&usize::MAX) {
q.push(neighbor);
costs.insert((row, col), neighbor.cost);
}
}
}
panic!("Could not find a path");
}
fn load(data: &str) -> eyre::Result<Vec2<usize>> {
let width = data.lines().next().unwrap().len();
let mut grid = Vec2::new(width);
for line in data.lines() {
let chars = line.chars().map(|c| c.to_digit(10).unwrap() as usize);
grid.push(chars);
}
Ok(grid)
}
fn tile(grid: &Vec2<usize>) -> Vec2<usize> {
let old_cols = grid.col_count();
let new_cols = old_cols * 5;
let mut result = Vec2::new(new_cols);
for row in grid.rows() {
let values = (0..new_cols).map(|i| {
let v = row[i % old_cols] + i / old_cols;
(v - 1) % 9 + 1
});
result.push(values);
}
let old_rows = grid.row_count();
let new_rows = old_rows * 5;
for row_n in old_rows..new_rows {
let values = (0..new_cols).map(|c| {
let r = row_n % old_rows;
let v = result.get(r, c) + row_n / old_rows;
(v - 1) % 9 + 1
})
.collect::<Vec<usize>>();
result.push(values.into_iter());
}
result
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let grid = load(data)?;
let grid2 = tile(&grid);
let one = search(&grid);
let two = search(&grid2);
Ok((one, two))
}

127
2021/src/day16.rs Normal file
View File

@ -0,0 +1,127 @@
use color_eyre::eyre;
use crate::lib::{BitVec, BitSlice};
#[derive(Debug)]
struct Packet {
version: u8,
type_id: u8,
content: PacketContent,
}
#[derive(Debug)]
enum PacketContent {
Literal(u64),
Operator(Vec<Packet>),
}
struct Parser {
data: BitVec,
idx: usize,
}
impl Parser {
fn from_hex(hex: &str) -> eyre::Result<Self> {
let mut data = BitVec::with_capacity(hex.len() * 4);
for i in 0 .. (hex.len() / 2) { // we are assuming valid hex
let chunk = &hex[i * 2 .. (i + 1) * 2];
let byte = u8::from_str_radix(chunk, 16)?;
data.push_byte(byte);
}
Ok(Parser {data, idx: 0})
}
fn next(&mut self, n: usize) -> BitSlice {
let range = self.idx .. self.idx + n;
self.idx += n;
self.data.slice(range)
}
fn parse_value(&mut self) -> u64 {
let mut value = 0;
loop {
let group = self.next(5);
value = (value << 4) | group.slice(1..5).as_u64();
if !group[0] {break;}
}
value
}
fn parse_children(&mut self) -> Vec<Packet> {
let mut children = Vec::new();
if self.next(1).as_bool() {
// count refers to number of sub-packets
let count = self.next(11).as_u16();
children = Vec::with_capacity(count as usize);
for _i in 0..count {
children.push(self.parse());
}
}
else {
// len refers to content length in bits
let len = self.next(15).as_u16();
let end = self.idx + (len as usize);
while self.idx < end {
children.push(self.parse());
}
}
children
}
fn parse(&mut self) -> Packet {
let version = self.next(3).as_u8();
let type_id = self.next(3).as_u8();
let content = match type_id {
4 => PacketContent::Literal(self.parse_value()),
_ => PacketContent::Operator(self.parse_children()),
};
Packet {version, type_id, content}
}
}
fn sum_versions(root: &Packet) -> usize {
let mut total = root.version as usize;
if let PacketContent::Operator(children) = &root.content {
for child in children {
total += sum_versions(child);
}
}
total
}
fn evaluate(packet: &Packet) -> usize {
let children = match &packet.content {
PacketContent::Literal(value) => {return *value as usize;}, // early exit here
PacketContent::Operator(children) => children,
};
let mut values = children.iter().map(|p| evaluate(p));
match packet.type_id {
0 => values.sum(),
1 => values.product(),
2 => values.min().unwrap(),
3 => values.max().unwrap(),
// it's guaranteed that comparisons will have exactly two children
5 => if values.next().unwrap() > values.next().unwrap() {1} else {0},
6 => if values.next().unwrap() < values.next().unwrap() {1} else {0},
7 => if values.next().unwrap() == values.next().unwrap() {1} else {0},
// the version number was a 3-bit value, so:
_ => unreachable!(),
}
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let mut parser = Parser::from_hex(data)?;
let root = parser.parse();
let one = sum_versions(&root);
let two = evaluate(&root);
Ok((one, two))
}

81
2021/src/day17.rs Normal file
View File

@ -0,0 +1,81 @@
use std::ops::Range;
use color_eyre::eyre;
#[derive(Default, Debug)]
struct Pair {
x: i64,
y: i64
}
#[derive(Debug)]
struct ProbeState {
pos: Pair,
vel: Pair,
}
struct Zone {
x: Range<i64>,
y: Range<i64>,
}
impl ProbeState {
fn new(vx: i64, vy: i64) -> Self {
let vel = Pair {x: vx, y: vy};
ProbeState {pos: Pair::default(), vel}
}
fn step(&mut self) {
self.pos.x += self.vel.x;
self.pos.y += self.vel.y;
self.vel.y -= 1;
if self.vel.x > 0 {
self.vel.x -= 1;
}
}
fn is_in(&self, zone: Zone) -> bool {
zone.x.contains(&self.pos.x) && zone.y.contains(&self.pos.y)
}
fn is_past(&self, zone: Zone) -> bool {
self.pos.x >= zone.x.end || self.pos.y <= zone.y.start
}
}
const VX_MIN: i64 = 23;
const VX_MAX: i64 = 293;
const VY_MIN: i64 = -69;
const VY_MAX: i64 = 68;
const TARGET: Zone = Zone {x: 269..293, y: -68..-43};
//sample data
// const VX_MIN: i64 = 6;
// const VX_MAX: i64 = 31;
// const VY_MIN: i64 = -11;
// const VY_MAX: i64 = 10;
// const TARGET: Zone = Zone {x: 20..31, y: -10..-4};
pub fn run(_data: &str) -> eyre::Result<(usize, usize)> {
let mut total = 0;
// just brute force it, there are only ~35k possibilities
for vx in VX_MIN..VX_MAX {
for vy in VY_MIN..VY_MAX {
let mut state = ProbeState::new(vx, vy);
while !state.is_past(TARGET) {
state.step();
if state.is_in(TARGET) {
total += 1;
break;
}
}
}
}
Ok((2278, total))
}

188
2021/src/day18.rs Normal file
View File

@ -0,0 +1,188 @@
use color_eyre::eyre;
#[derive(Debug, Clone)]
enum Node {
Leaf(u8),
Branch {
left: Box<Node>,
right: Box<Node>,
}
}
use Node::*;
impl Node {
fn unwrap_branch(&self) -> (&Node, &Node) {
match self {
Node::Leaf(_) => panic!("Attempted to unwrap branches from a leaf node"),
Node::Branch {left, right} => (left, right),
}
}
fn leftmost_value_mut(&mut self) -> &mut u8 {
match self {
Node::Leaf(val) => val,
Node::Branch {left, ..} => left.leftmost_value_mut(),
}
}
fn rightmost_value_mut(&mut self) -> &mut u8 {
match self {
Node::Leaf(val) => val,
Node::Branch {right, ..} => right.rightmost_value_mut(),
}
}
fn magnitude(&self) -> usize {
match self {
Node::Leaf(v) => *v as usize,
Node::Branch {left, right} => 3 * left.magnitude() + 2 * right.magnitude(),
}
}
}
struct Parser<'a> {
chars: std::str::Chars<'a>,
}
impl<'a> Parser<'a> {
fn from(src: &'a str) -> Parser<'a> {
let mut chars = src.chars();
chars.next(); // throw away the opening bracket to avoid nesting too deeply
Parser {chars}
}
fn parse(&mut self) -> Node {
let mut left = None;
loop {
let node = match self.chars.next() {
Some('[') => self.parse(),
Some(','|']') => {continue;},
Some(c) => Node::Leaf(c.to_digit(10).unwrap() as u8),
None => {dbg!(left); panic!("Ran out of characters while parsing");},
};
if left.is_none() {
left = Some(node);
}
else {
return Node::Branch {
left: Box::new(left.unwrap()),
right: Box::new(node),
};
}
}
}
}
fn load(data: &str) -> Vec<Node> {
let mut nodes = Vec::new();
for line in data.lines() {
let mut parser = Parser::from(line);
nodes.push(parser.parse());
}
nodes
}
fn explode(node: &mut Node, depth: usize) -> Option<(u8, u8)> {
// only branch nodes can explode
if let Leaf(_) = node {return None;}
if depth >= 4 {
if let (Leaf(v1), Leaf(v2)) = node.unwrap_branch() {
let result = (*v1, *v2);
*node = Leaf(0);
return Some(result);
}
}
if let Branch {left, right} = node {
if let Some((v1, v2)) = explode(left, depth + 1) {
if v2 > 0 {
*right.leftmost_value_mut() += v2;
return Some((v1, 0));
}
return Some((v1, v2));
}
if let Some((v1, v2)) = explode(right, depth + 1) {
if v1 > 0 {
*left.rightmost_value_mut() += v1;
return Some((0, v2));
}
return Some((v1, v2));
}
}
return None;
}
fn split(node: &mut Node) -> bool {
match node {
Leaf(v) if *v > 9 => {
*node = Branch {
left: Box::new(Leaf(*v / 2)),
right: Box::new(Leaf((*v + 1) / 2)),
};
return true;
},
Leaf(_) => return false,
Branch {left, right} => {
if split(left) {
return true;
}
else {
return split(right);
}
}
}
}
fn reduce(node: &mut Node) {
loop {
if let Some(..) = explode(node, 0) {
continue;
}
if split(node) {
continue;
}
break;
}
}
fn add(left: Node, right: Node) -> Node {
let mut sum = Node::Branch {
left: Box::new(left),
right: Box::new(right),
};
reduce(&mut sum);
sum
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let nodes = load(data);
let one = nodes.into_iter().reduce(|sum, next| add(sum, next)).unwrap();
// I'm sure there's a more efficient way to do this, but
let mut max = 0;
let nodes2 = load(data);
for i in 0..nodes2.len() {
for j in 0..nodes2.len() {
if i == j {continue;}
let a = nodes2[i].clone();
let b = nodes2[j].clone();
let magnitude = add(a, b).magnitude();
if magnitude > max {
max = magnitude;
}
}
}
Ok((one.magnitude(), max))
}

84
2021/src/day2.rs Normal file
View File

@ -0,0 +1,84 @@
use std::str::FromStr;
use color_eyre::eyre;
use eyre::{eyre, ensure};
use crate::lib::ParseLines;
enum Direction {
Forward,
Up,
Down,
}
struct MoveCmd {
dir: Direction,
value: i32,
}
impl FromStr for MoveCmd {
type Err = eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split_whitespace().collect();
ensure!(parts.len() == 2, "Could not parse: \"{}\" (Wrong number of words)", s);
let dir = match parts[0] {
"forward" => Direction::Forward,
"up" => Direction::Up,
"down" => Direction::Down,
_ => return Err(eyre!("Could not parse: \"{}\" (invalid axis)", s)),
};
let value = parts[1].parse::<i32>()?;
Ok(MoveCmd{dir, value})
}
}
fn part1(commands: &Vec<MoveCmd>) -> i32 {
let mut horiz = 0; // how many units forward
let mut vert = 0; // how many units DOWN (so flipped)
for cmd in commands {
match cmd.dir {
Direction::Forward => horiz += cmd.value,
Direction::Up => vert -= cmd.value,
Direction::Down => vert += cmd.value,
}
}
horiz * vert
}
fn part2(commands: &Vec<MoveCmd>) -> i32 {
let mut horiz = 0;
let mut vert = 0;
let mut aim = 0;
for cmd in commands {
match cmd.dir {
Direction::Down => aim += cmd.value, // flipped again
Direction::Up => aim -= cmd.value,
Direction::Forward => {
horiz += cmd.value;
vert += cmd.value * aim;
}
}
}
horiz * vert
}
pub fn run(data: &str) -> eyre::Result<(i32, i32)> {
let mut commands = Vec::new();
for res in data.parse_lines::<MoveCmd>() {
commands.push(res?);
}
Ok((part1(&commands), part2(&commands)))
}

67
2021/src/day3.rs Normal file
View File

@ -0,0 +1,67 @@
use color_eyre::eyre;
fn most_common_bit(numbers: &Vec<u16>, pos: u16) -> u16 {
// number of times the bit in question is 1,
// minus the number of times it's 0
let mask: u16 = 1 << pos;
let total: isize = numbers
.iter()
.map(|n| if n & mask > 0 {1} else {-1})
.sum();
// if the total is positive, then 1 is more frequent than 0
if total >= 0 {1} else {0}
}
fn part1(numbers: &Vec<u16>) -> u64 {
let gamma = (0..12).reduce(|g, i|
g | most_common_bit(numbers, i) << i
).unwrap(); // unwrap is safe here since this iterator will always have elements
// epsilon is just the bit inverse of gamma, but since these are really 12-bit numbers
// we have to clear the 4 spurious 1's that come from inverting the original
let epsilon = !gamma & (65535 >> 4);
epsilon as u64 * gamma as u64
}
fn filter_bit_criteria(numbers: Vec<u16>, bit_pos: u16, bit_value: u16) -> Vec<u16> {
numbers
.into_iter()
.filter(|n| (n >> bit_pos) & 1 == bit_value)
.collect()
}
fn part2(numbers: &Vec<u16>) -> u64 {
let mut oxy = numbers.clone();
let mut co2 = numbers.clone();
for pos in (0..12).rev() {
if oxy.len() > 1 {
let oxy_bit = most_common_bit(&oxy, pos);
oxy = filter_bit_criteria(oxy, pos, oxy_bit);
}
if co2.len() > 1 {
let co2_bit = most_common_bit(&co2, pos) ^ 1; // flip the rightmost bit, giving us the least common instead
co2 = filter_bit_criteria(co2, pos, co2_bit);
}
}
// assume both vecs have len == 1 now, since otherwise it means
// AoC gave us an invalid problem input
oxy[0] as u64 * co2[0] as u64
}
pub fn run(data: &str) -> eyre::Result<(u64, u64)> {
let mut nums = Vec::new();
for line in data.lines() {
nums.push(u16::from_str_radix(line, 2)?);
}
Ok((part1(&nums), part2(&nums)))
}

211
2021/src/day4.rs Normal file
View File

@ -0,0 +1,211 @@
use std::fmt;
use color_eyre::eyre;
use eyre::eyre;
struct Ratchet<T, F>
where T: Copy,
F: Fn(T) -> u8,
{
value: Option<T>,
getter: F,
}
impl<T, F> Ratchet<T, F>
where T: Copy,
F: Fn(T) -> u8
{
fn new(getter: F) -> Self {
Ratchet {
value: None,
getter,
}
}
fn inc(&mut self, other: T) {
match self.value {
Some(v) if (self.getter)(other) <= (self.getter)(v) => (),
_ => self.value = Some(other),
};
}
fn dec(&mut self, other: T) {
match self.value {
Some(v) if (self.getter)(other) >= (self.getter)(v) => (),
_ => self.value = Some(other),
};
}
}
#[derive(Copy, Clone, Default, Debug)]
struct Square {
value: u8,
draw_time: Option<u8>,
}
#[derive(Copy, Clone, Default, Debug)]
struct Board {
squares: [[Square; 5]; 5],
bingo_time: Option<u8>,
bingo_value: Option<u8>
}
impl Board {
fn from(squares: &[Square]) -> eyre::Result<Self> {
let mut it = squares.iter();
let mut b = Self::default();
for row in 0..5 {
for col in 0..5 {
match it.next() {
Some(sq) => b.squares[row][col] = *sq,
None => return Err(eyre!("Not enough values to populate board")),
}
}
}
Ok(b)
}
fn set_bingo(&mut self) {
let mut board_bingo = Ratchet::new(|sq: Square| sq.draw_time.unwrap()); // these unwraps are safe because we will only call inc() or dec() after ensuring there is a value there
let mut cols_bingo: [bool; 5] = [true; 5];
let mut cols_last_drawn: Vec<Ratchet<Square, _>> = (0..5)
.map(|_i| Ratchet::new(|sq: Square| sq.draw_time.unwrap()))
.collect();
// let mut cols_last_drawn: [Ratchet<Square, _>; 5] = [Ratchet::new(|sq: Square| sq.draw_time.unwrap()); 5];
for row in self.squares {
let mut row_bingo = true;
let mut row_last_drawn = Ratchet::new(|sq: Square| sq.draw_time.unwrap());
for (col, sq) in row.iter().enumerate() {
match sq.draw_time {
Some(_) => {
row_last_drawn.inc(*sq);
cols_last_drawn[col].inc(*sq);
},
None => {
row_bingo = false;
cols_bingo[col] = false;
break;
}
}
}
if row_bingo {
let bingo_sq = row_last_drawn.value.unwrap(); // we know there will be a value here if row_bingo is true
board_bingo.dec(bingo_sq); //
}
}
for col in 0..5 {
if cols_bingo[col] {
let bingo_sq = cols_last_drawn[col].value.unwrap(); // similar to above
board_bingo.dec(bingo_sq);
}
}
if let Some(sq) = board_bingo.value {
self.bingo_time = sq.draw_time;
self.bingo_value = Some(sq.value);
}
}
fn score(&self) -> eyre::Result<isize> {
let bingo_time = match self.bingo_time {
Some(t) => t,
None => return Err(eyre!("Cannot find the score of a non-winning board")),
};
let mut total = 0;
for row in self.squares {
for sq in row {
if sq.draw_time.is_some() && sq.draw_time.unwrap() > bingo_time {
total += sq.value as isize;
}
}
}
let multiplier = self.bingo_value.unwrap() as isize;
Ok(total * multiplier)
}
}
impl fmt::Display for Board {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for row in self.squares {
write!(f, "|")?;
for (i, sq) in row.iter().enumerate() {
let dt = sq.draw_time.map(|dt| format!("{}", dt)).unwrap_or(String::from("__"));
write!(f, "({}: {})", sq.value, dt)?;
if i < 4 {
write!(f, ", ")?;
}
}
write!(f, "|\n")?;
}
if self.bingo_time.is_some() {
write!(f, "Bingo: ({}: {})", self.bingo_value.unwrap(), self.bingo_time.unwrap())
}
else {
write!(f, "Bingo: (__: __)")
}
}
}
fn load(data: &str) -> eyre::Result<Vec<Board>> {
let mut lines = data.lines().peekable();
let mut draws: [Option<u8>; 100] = [None; 100]; // indices are the number drawn, values are the position in which it was drawn
for (i, draw) in lines.next().unwrap().split(',').enumerate() {
let num = draw.parse::<usize>()?;
draws[num] = Some(i.try_into().unwrap()); // will panic if there are more than 256 items in this iterator, but there aren't
}
let mut boards = Vec::new();
let mut squares = Vec::new();
for line in lines {
for s in line.split_whitespace() {
let value = s.parse::<u8>()?; // shadows the loop variable I guess?
let sq = Square {value, draw_time: draws[value as usize]};
squares.push(sq);
}
if squares.len() == 25 {
boards.push(Board::from(&squares)?);
boards.last_mut().unwrap().set_bingo();
squares.clear();
}
}
Ok(boards)
}
fn part1(boards: &[Board]) -> eyre::Result<Board> {
let winner = boards.iter()
.filter(|b| b.bingo_time.is_some())
.min_by_key(|b| b.bingo_time.unwrap());
match winner {
Some(w) => Ok(*w),
None => Err(eyre!("Could not find a winning board")),
}
}
fn part2(boards: &[Board]) -> eyre::Result<Board> {
let winner = boards.iter()
.filter(|b| b.bingo_time.is_some())
.max_by_key(|b| b.bingo_time.unwrap());
match winner {
Some(w) => Ok(*w),
None => Err(eyre!("Could not find a winning board")),
}
}
pub fn run(data: &str) -> eyre::Result<(isize, isize)> {
let boards = load(data)?;
let winner = part1(&boards)?;
let loser = part2(&boards)?;
Ok((winner.score()?, loser.score()?))
}

150
2021/src/day5.rs Normal file
View File

@ -0,0 +1,150 @@
use std::str::FromStr;
use color_eyre::eyre;
use num::integer::gcd;
use crate::lib::{ParseLines, IterExt};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Point {
x: isize,
y: isize,
}
#[derive(Copy, Clone, Debug)]
struct Slope {
rise: isize,
run: isize,
}
impl Slope {
fn apply_to(&self, pt: Point) -> Point {
Point {
x: pt.x + self.run,
y: pt.y + self.rise,
}
}
}
#[derive(Copy, Clone, Debug)]
struct Line {
start: Point,
end: Point,
}
impl Line {
fn slope(&self) -> Slope {
let dx = self.end.y - self.start.y;
let dy = self.end.x - self.start.x;
let divisor;
if dx == 0 || dy == 0 {
divisor = (dx + dy).abs();
}
else {
divisor = gcd(dx, dy);
}
Slope {
rise: dx / divisor,
run: dy / divisor,
}
}
fn points(&self) -> PointsIter {
PointsIter {
line: self,
pos: self.start,
slope: self.slope(),
}
}
}
#[derive(Debug)]
struct PointsIter<'a> {
line: &'a Line,
pos: Point,
slope: Slope,
}
impl Iterator for PointsIter<'_> {
type Item = Point;
fn next(&mut self) -> Option<Self::Item> {
// note, this approach will break if total dX and dY are not multiples of rise/run
if self.pos == self.slope.apply_to(self.line.end) {
return None;
}
let orig = self.pos;
self.pos = self.slope.apply_to(self.pos);
Some(orig)
}
}
impl FromStr for Line {
type Err = eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (p1, p2) = s.split(" -> ").take_pair()?;
let (x1, y1) = p1.split(',').take_pair()?;
let (x2, y2) = p2.split(',').take_pair()?;
let start = Point {
x: x1.parse::<isize>()?,
y: y1.parse::<isize>()?,
};
let end = Point {
x: x2.parse::<isize>()?,
y: y2.parse::<isize>()?,
};
Ok(Line {start, end})
}
}
fn part1(lines: &[Line]) -> usize {
let mut grid = vec![[0; 1000]; 1000];
let points = lines.iter()
.filter(|l| l.start.x == l.end.x || l.start.y == l.end.y)
.flat_map(|l| l.points());
for pt in points {
grid[pt.y as usize][pt.x as usize] += 1;
}
grid.into_iter()
.flat_map(|row| row.into_iter())
.filter(|n| *n > 1)
.count()
}
fn part2(lines: &[Line]) -> usize {
let mut grid = vec![[0; 1000]; 1000];
for pt in lines.iter().flat_map(|l| l.points()) {
grid[pt.y as usize][pt.x as usize] += 1;
}
grid.into_iter()
.flat_map(|row| row.into_iter())
.filter(|n| *n > 1)
.count()
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let mut lines = Vec::new();
for line in data.parse_lines::<Line>() {
lines.push(line?);
}
Ok((part1(&lines), part2(&lines)))
}

56
2021/src/day6.rs Normal file
View File

@ -0,0 +1,56 @@
use color_eyre::eyre;
struct Ocean {
fish: [usize; 9],
}
impl Ocean {
fn from(ages: &[usize]) -> Self {
let mut ocean = Ocean {fish: [0; 9]};
for n in ages {
ocean.fish[*n] += 1;
}
ocean
}
fn step(&mut self) {
let mut new = [0; 9];
// 0 is a special case
new[8] += self.fish[0]; // newly-created fish
new[6] += self.fish[0]; // existing fish with timers reset
for i in 1..9 {
new[i - 1] += self.fish[i];
}
self.fish = new;
}
fn sum(&self) -> usize {
self.fish.iter().sum()
}
fn step_by(&mut self, n: usize) {
for _ in 0..n {
self.step();
}
}
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let mut ages = Vec::new();
for a in data.trim().split(',') {
ages.push(a.parse::<usize>()?);
}
let mut ocean = Ocean::from(&ages);
ocean.step_by(80);
let one = ocean.sum();
ocean.step_by(256 - 80);
let two = ocean.sum();
Ok((one, two))
}

56
2021/src/day7.rs Normal file
View File

@ -0,0 +1,56 @@
use color_eyre::eyre;
fn distance_between(a: usize, b: usize) -> usize {
// can't do the fancypants (a - b).abs() thing here because these are unsigned
if a > b {
a - b
}
else {
b - a
}
}
fn cost_simple(counts: &[usize], pos: usize) -> usize {
counts.iter()
.enumerate()
.fold(0, |total, (i, count)| {
total + (distance_between(pos, i) * count) // yes, these parens are unnecessary
})
}
fn cost_complex(counts: &[usize], pos: usize) -> usize {
counts.iter()
.enumerate()
.fold(0, |total, (i, count)| {
let dx = distance_between(pos, i);
let cost_single = dx * (dx + 1) / 2; // Gauss' method for summing numbers from 1 to n
total + (cost_single * count)
})
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let mut counts = Vec::new();
for s in data.trim().split(',') {
let n = s.parse::<usize>()?;
if n >= counts.len() {
counts.resize(n + 1, 0);
}
counts[n] += 1;
}
let mut min_simple = usize::MAX;
let mut min_complex = usize::MAX;
for i in 0..counts.len() {
let simple = cost_simple(&counts, i);
if simple < min_simple {min_simple = simple;}
let complex = cost_complex(&counts, i);
if complex < min_complex {min_complex = complex;}
}
Ok((min_simple, min_complex))
}

280
2021/src/day8.rs Normal file
View File

@ -0,0 +1,280 @@
use std::str::FromStr;
use color_eyre::eyre;
use eyre::eyre;
use crate::lib::{ParseLines, IterExt};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Segment {
A = 1,
B = 2,
C = 4,
D = 8,
E = 16,
F = 32,
G = 64, // wheee!
}
use Segment::*;
impl Segment {
fn from_char(c: char) -> eyre::Result<Self> {
match c {
'a' => Ok(A),
'b' => Ok(B),
'c' => Ok(C),
'd' => Ok(D),
'e' => Ok(E),
'f' => Ok(F),
'g' => Ok(G),
_ => Err(eyre!("Invalid character for segment: {}", c)),
}
}
fn to_ord(&self) -> usize {
// kinda gross but whatever
match self {
A => 0, B => 1, C => 2, D => 3,
E => 4, F => 5, G => 6
}
}
fn from_ord(n: u8) -> eyre::Result<Self> {
match n {
0 => Ok(A),
1 => Ok(B),
2 => Ok(C),
3 => Ok(D),
4 => Ok(E),
5 => Ok(F),
6 => Ok(G),
_ => Err(eyre!("Invalid ordinal for segment: {}", n)),
}
}
fn from_value(n: u8) -> eyre::Result<Self> {
match n {
1 => Ok(A),
2 => Ok(B),
4 => Ok(C),
8 => Ok(D),
16 => Ok(E),
32 => Ok(F),
64 => Ok(G),
_ => Err(eyre!("Invalid value for segment: {}", n)),
}
}
}
#[derive(Copy, Clone, Default, Debug)]
struct SegmentSet {
data: u8,
}
impl SegmentSet {
fn contains(&self, segment: Segment) -> bool {
self.data & (segment as u8) > 0
}
fn insert(&mut self, segment: Segment) {
self.data |= segment as u8;
}
fn len(&self) -> u8 {
(0..7).fold(0, |total, i| total + ((self.data >> i) & 1))
}
fn exclusion(&self, other: &Self) -> Self {
SegmentSet {data: self.data ^ other.data}
}
fn iter(&self) -> SegmentSetIter {
SegmentSetIter {ss: self, pos: 0}
}
}
impl FromStr for SegmentSet {
type Err = eyre::Report;
fn from_str(s: &str) -> eyre::Result<Self> {
let mut set = Self::default();
for c in s.chars() {
set.insert(Segment::from_char(c)?);
}
Ok(set)
}
}
struct SegmentSetIter<'a> {
ss: &'a SegmentSet,
pos: u8,
}
impl Iterator for SegmentSetIter<'_> {
type Item = Segment;
fn next(&mut self) -> Option<Segment> {
while self.pos < 7 {
let seg_value = self.ss.data & (1 << self.pos);
self.pos += 1;
if seg_value > 0 {
return Some(Segment::from_value(seg_value).unwrap());
}
}
return None
}
}
#[derive(Default, Debug)]
struct Screen {
patterns: [SegmentSet; 10],
output: [SegmentSet; 4],
}
impl FromStr for Screen {
type Err = eyre::Report;
fn from_str(s: &str) -> eyre::Result<Self> {
let mut screen = Screen::default();
let (patterns, output) = s.split(" | ").take_pair()?;
for (i, patt) in patterns.split(' ').enumerate() {
screen.patterns[i] = patt.parse::<SegmentSet>()?;
}
for (i, out) in output.split(' ').enumerate() {
screen.output[i] = out.parse::<SegmentSet>()?;
}
Ok(screen)
}
}
fn segment_counts(screen: &Screen) -> [usize; 7] {
let mut counts = [0; 7];
for i in 0..7 {
let segment = Segment::from_ord(i).unwrap();
counts[i as usize] = screen.patterns.iter()
.filter(|p| p.contains(segment))
.count()
}
counts
}
fn find_a(screen: &Screen) -> Segment {
// figure out which segment is currently attached to the "A" wire
let mut seven = None;
let mut one = None;
for p in screen.patterns {
if p.len() == 2 {
one = Some(p); // the only digit that uses 2 segments is 1
}
else if p.len() == 3 {
seven = Some(p); // similarly for 7
}
}
let result = seven.unwrap().exclusion(&one.unwrap());
if result.len() != 1 {
panic!("This should never happen");
}
result.iter().next().unwrap()
}
fn solve(screen: &Screen) -> [Segment; 7] {
let mut result = [A; 7]; // A is just a placeholder value
let four_pattern = screen.patterns.iter().find(|p| p.len() == 4).unwrap();
for (i, count) in segment_counts(screen).iter().enumerate() {
let possible = match count {
4 => Some(E),
6 => Some(B),
7|8 => None,
9 => Some(F),
_ => unreachable!(),
};
if let Some(segment) = possible {
result[i] = segment;
}
else if *count == 7 { // 7 appearances means either D or G
let segment = Segment::from_ord(i as u8).unwrap();
if four_pattern.contains(segment) {
result[i] = D;
}
else {
result[i] = G;
}
}
else if *count == 8 { // similarly 8 should be either A or C
let segment = Segment::from_ord(i as u8).unwrap();
if segment == find_a(screen) {
result[i] = A;
}
else {
result[i] = C;
}
}
}
result
}
fn decode(output: [SegmentSet; 4], map: [Segment; 7]) -> usize {
let mut result = 0;
for (i, segments_enc) in output.iter().enumerate() {
let mut segments_dec = SegmentSet::default();
for segment_enc in segments_enc.iter() {
let segment_dec = map[segment_enc.to_ord()];
segments_dec.insert(segment_dec)
}
let digit = match segments_dec.data {
0b01110111 => 0,
0b00100100 => 1,
0b01011101 => 2,
0b01101101 => 3,
0b00101110 => 4,
0b01101011 => 5,
0b01111011 => 6,
0b00100101 => 7,
0b01111111 => 8,
0b01101111 => 9,
_ => {
dbg!(segments_dec);
panic!("Invalid segment sequence");
},
};
result += digit * 10usize.pow(3 - (i as u32));
}
result
}
fn part2(screens: &[Screen]) -> usize {
let mut result = 0;
for screen in screens {
let map = solve(screen);
result += decode(screen.output, map);
}
result
}
fn part1(screens: &[Screen]) -> usize {
screens.iter()
.flat_map(|d| d.output.iter())
.filter(|s| matches!(s.len(), 2|3|4|7))
.count()
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let screens = data
.parse_lines::<Screen>()
.collect::<Result<Vec<_>, _>>()?;
Ok((part1(&screens), part2(&screens)))
}

143
2021/src/day9.rs Normal file
View File

@ -0,0 +1,143 @@
use std::collections::HashSet;
use color_eyre::eyre;
use eyre::eyre;
use crate::lib::{IterExt, Vec2};
#[derive(Debug)]
struct Cell {
height: u32,
min: bool,
}
struct NeighborsIter {
x: usize,
y: usize,
rows: usize,
cols: usize,
state: usize,
}
impl Iterator for NeighborsIter {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
let neighbor = match self.state {
0 if self.x > 0 => Some((self.y, self.x - 1)),
1 if self.y > 0 => Some((self.y - 1, self.x)),
2 if self.x < self.cols - 1 => Some((self.y, self.x + 1)),
3 if self.y < self.rows - 1 => Some((self.y + 1, self.x)),
_ => None,
};
self.state += 1;
if self.state > 4 {
None
}
else {
neighbor.or_else(|| self.next())
}
}
}
fn load(data: &str) -> eyre::Result<Vec2<Cell>> {
let num_columns = data.lines().next().unwrap().len();
let mut heights = Vec2::new(num_columns);
for line in data.lines() {
let mut row = Vec::new();
for c in line.chars() {
match c.to_digit(10) {
Some(d) => row.push(Cell {height: d, min: false}),
None => return Err(eyre!("Invalid character (not a digit): {}", c)),
}
}
heights.push(row);
}
Ok(heights)
}
fn scan(map: &mut Vec2<Cell>) {
for row in 0..map.row_count() {
for col in 0..map.col_count() {
let mut lt_left = true;
if col > 0 {
if map[row][col].height < map[row][col - 1].height {
map[row][col - 1].min = false;
}
else {
lt_left = false;
}
}
let mut lt_top = true;
if row > 0 {
if map[row][col].height < map[row - 1][col].height {
map[row - 1][col].min = false;
}
else {
lt_top = false;
}
}
if lt_left && lt_top {
map[row][col].min = true;
}
}
}
}
fn low_points(map: &Vec2<Cell>) -> impl Iterator<Item=(usize, &Cell)> {
map.values().enumerate().filter(|(_, c)| c.min)
}
fn neighbors(map: &Vec2<Cell>, coords: (usize, usize)) -> NeighborsIter {
NeighborsIter {
x: coords.1,
y: coords.0,
rows: map.row_count(),
cols: map.col_count(),
state: 0,
}
}
fn basin_size (map: &Vec2<Cell>, start: (usize, usize), visited: &mut HashSet<(usize, usize)>) -> u32 {
let mut total = 1;
for coords in neighbors(map, start) {
if map[coords.0][coords.1].height < 9 && !visited.contains(&coords) {
visited.insert(coords);
total += basin_size(map, coords, visited);
}
}
total
}
fn basins(map: &Vec2<Cell>) -> u32 {
low_points(map)
.map(|(i, _)| {
let start = map.coords(i);
basin_size(map, start, &mut HashSet::from([start]))
})
.max_n(3)
.unwrap()
.iter()
.product()
}
pub fn run(data: &str) -> eyre::Result<(u32, u32)> {
// let sample = "2199943210\n3987894921\n9856789892\n8767896789\n9899965678";
let mut map = load(data)?;
scan(&mut map);
let one = low_points(&map).fold(0, |acc, (_, c)| acc + c.height + 1);
let two = basins(&map);
Ok((one, two))
}

468
2021/src/lib.rs Normal file
View File

@ -0,0 +1,468 @@
use std::io::Read;
use std::collections::VecDeque;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::hash::Hash;
use std::ops::{Index, IndexMut, Range};
use std::str::FromStr;
use color_eyre::{eyre};
use eyre::eyre;
pub fn load(filename: &str) -> eyre::Result<String> {
let mut file = File::open(filename)?;
let mut data = String::new();
file.read_to_string(&mut data)?;
Ok(data)
}
pub struct LineParser<'a, T: FromStr> {
it: std::str::Lines<'a>,
convert: fn(&str) -> Result<T, <T as FromStr>::Err>
}
impl<'a, T: FromStr> Iterator for LineParser<'a, T> {
type Item = Result<T, <T as FromStr>::Err>;
fn next(&mut self) -> Option<Self::Item> {
self.it.next().map(|s|
(self.convert)(s)
)
}
}
// define a trait so that we can attach it to String
pub trait ParseLines {
fn parse_lines<T: FromStr>(&self) -> LineParser<'_, T>;
}
impl ParseLines for str {
fn parse_lines<T: FromStr>(&self) -> LineParser<'_, T> {
LineParser {
it: self.lines(),
convert: str::parse::<T>
}
}
}
impl ParseLines for String {
fn parse_lines<T: FromStr>(&self) -> LineParser<'_, T> {
self[..].parse_lines::<T>()
}
}
pub trait IterExt: Iterator {
fn take_pair(&mut self) -> eyre::Result<(Self::Item, Self::Item)> {
let a = match self.next() {
Some(v) => v,
None => return Err(eyre!("Not enough values to unpack")),
};
let b = match self.next() {
Some(v) => v,
None => return Err(eyre!("Not enough values to unpack")),
};
Ok((a, b))
}
fn max_n(&mut self, n: usize) -> eyre::Result<VecDeque<Self::Item>>
where Self: Sized, Self::Item: Ord,
{
let mut items = VecDeque::with_capacity(n);
while let Some(item) = self.next() {
for i in 0..=items.len() {
if i < items.len() && item > items[i] {
if items.len() == n {
items.pop_back();
}
items.insert(i, item);
break;
}
else if i == items.len() && i < n {
items.push_back(item);
break;
}
}
}
if items.len() < n {
Err(eyre!("Not enough values to take the maximum {}", n))
}
else {
Ok(items)
}
}
}
impl<I: Iterator> IterExt for I {}
// 2d container, functions kind of like a "vec of vecs" but all data is stored in a single vec for memory contiguity
pub struct Vec2<T> {
data: Vec<T>,
columns: usize,
}
pub struct NeighborCoords {
num_rows: usize,
num_cols: usize,
start_row: usize,
start_col: usize,
i: usize,
}
impl Iterator for NeighborCoords {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
if self.i > 8 {
return None;
}
let (rel_row, rel_col) = (self.i / 3, self.i % 3);
self.i += 1;
if rel_row == 1 && rel_col == 1 {
return self.next();
}
let row_offset = self.start_row + rel_row;
let col_offset = self.start_col + rel_col;
// the "neighbor window" is off by 1, so we make the comparisons off by 1 as well
if row_offset > 0 && row_offset <= self.num_rows
&& col_offset > 0 && col_offset <= self.num_cols
{
Some((row_offset - 1, col_offset - 1))
}
else {
self.next()
}
}
}
pub struct RectNeighbors {
nc: NeighborCoords,
}
impl Iterator for RectNeighbors {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
if let Some((row, col)) = self.nc.next() {
if row == self.nc.start_row || col == self.nc.start_col {
return Some((row, col));
}
else {
return self.next();
}
}
return None
}
}
#[allow(dead_code)]
impl<T> Vec2<T> {
pub fn new(columns: usize) -> Vec2<T> {
Vec2 {data: Vec::<T>::new(), columns}
}
pub fn with_capacity(rows: usize, columns: usize) -> Vec2<T> {
Vec2 {data: Vec::<T>::with_capacity(rows * columns), columns}
}
pub fn push<S: IntoIterator<Item=T>>(&mut self, row: S) {
self.data.extend(row.into_iter());
}
pub fn row_count(&self) -> usize {
self.data.len() / self.columns
}
pub fn col_count(&self) -> usize {
self.columns
}
pub fn coords(&self, i: usize) -> (usize, usize) {
// convert underlying index into 2d coordinates
(i / self.columns, i % self.columns)
}
pub fn offset(&self, row: usize, col: usize) -> usize {
// and the reverse
self.columns * row + col
}
pub fn rows(&self) -> impl Iterator<Item=&[T]> {
self.data.chunks_exact(self.columns)
}
pub fn rows_mut(&mut self) -> impl Iterator<Item=&mut [T]> {
self.data.chunks_exact_mut(self.columns)
}
pub fn values(&self) -> impl Iterator<Item=&T> {
self.data.iter()
}
pub fn values_mut(&mut self) -> impl Iterator<Item=&mut T> {
self.data.iter_mut()
}
pub fn neighbor_coords(&self, row: usize, col: usize) -> NeighborCoords {
NeighborCoords {
num_rows: self.row_count(),
num_cols: self.col_count(),
start_row: row,
start_col: col,
i: 0,
}
}
pub fn rect_neighbors(&self, row: usize, col: usize) -> RectNeighbors {
RectNeighbors {nc: self.neighbor_coords(row, col)}
}
pub fn get(&self, row: usize, col: usize) -> &T {
&self.data[self.offset(row, col)]
}
pub fn get_mut(&mut self, row: usize, col: usize) -> &mut T {
self.data.index_mut(self.offset(row, col))
}
pub fn set(&mut self, row: usize, col: usize, val: T) {
let existing = self.data.index_mut(self.offset(row, col));
*existing = val;
}
}
#[allow(dead_code)]
impl<T: Copy> Vec2<T> {
pub fn fill(&mut self, value: T) {
for i in 0..self.data.len() {
self.data[i] = value;
}
}
pub fn fill_to_cap(&mut self, value: T) {
self.fill(value);
for _i in self.data.len()..self.data.capacity() {
self.data.push(value);
}
}
}
impl<T> Index<usize> for Vec2<T> {
type Output = [T];
fn index(&self, index: usize) -> &Self::Output {
let start = self.columns * index;
&self.data[start..(start + self.columns)]
}
}
impl<T> IndexMut<usize> for Vec2<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
let start = self.columns * index;
&mut self.data[start..(start + self.columns)]
}
}
impl<T: Display> Display for Vec2<T> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
for row in self.rows() {
write!(f, "[")?;
for (i, v) in row.iter().enumerate() {
if i > 0 {write!(f, "")?;}
write!(f, "{}", v)?;
}
write!(f, "]\n")?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Counter<K> {
data: HashMap<K, usize>
}
#[allow(dead_code)]
impl<K> Counter<K>
where K: Eq + Hash,
{
pub fn new() -> Self {
Counter {data: HashMap::<K, usize>::new()}
}
pub fn inc(&mut self, key: K) {
let v = self.data.entry(key).or_insert(0);
*v += 1;
}
pub fn inc_by(&mut self, key: K, increment: usize) {
let v = self.data.entry(key).or_insert(0);
*v += increment;
}
pub fn dec(&mut self, key: K) {
let v = self.data.entry(key).or_insert(0);
if *v > 0 {
*v -= 1;
}
}
pub fn dec_by(&mut self, key: K, decrement: usize) {
let v = self.data.entry(key).or_insert(0);
if *v >= decrement {
*v -= decrement;
}
}
pub fn get(&mut self, key: &K) -> usize {
*self.data.get(key).unwrap_or(&0)
}
pub fn set(&mut self, key: K, value: usize) {
self.data.insert(key, value);
}
pub fn iter(&self) -> impl Iterator<Item =(&'_ K, usize)> {
self.data.iter().map(|(k, v)| (k, *v))
}
}
pub struct BitVec {
data: Vec<u8>,
len: usize,
}
#[allow(dead_code)]
impl BitVec {
fn new() -> Self {
BitVec {data: vec![], len: 0}
}
pub fn with_capacity(cap: usize) -> Self {
let true_cap = (cap + 8 - 1) / 8; // ceiling division
BitVec {
data: Vec::with_capacity(true_cap),
len: 0
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn push(&mut self, b: bool) {
let offset = self.len % 8;
let v = (b as u8) << (7 - offset);
match offset {
0 => self.data.push(v),
_ => *self.data.last_mut().unwrap() |= v,
}
self.len += 1;
}
pub fn push_byte(&mut self, byte: u8) {
if self.len() % 8 != 0 {
panic!("Attempted to push full byte across byte boundary");
}
self.data.push(byte);
self.len += 8;
}
pub fn slice(&self, range: Range<usize>) -> BitSlice {
if range.end > self.len {
panic!("Slice out of bounds: {}..{}", range.start, range.end);
}
BitSlice {
src: self,
start: range.start,
end: range.end,
}
}
}
impl Index<usize> for BitVec {
type Output = bool;
fn index(&self, index: usize) -> &bool {
let (idx, offset) = (index / 8, index % 8);
let bit = self.data[idx] & (128 >> offset);
if bit == 0 {&false} else {&true}
}
}
#[derive(Copy, Clone)]
pub struct BitSlice<'a> {
src: &'a BitVec,
start: usize,
end: usize,
}
#[allow(dead_code)]
impl BitSlice<'_> {
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn as_u64(&self) -> u64 {
if self.len() > 64 {panic!("Slice too large for u64");}
let mut value = 0;
for i in self.start..self.end {
let offset = self.end - 1 - i;
value |= (self.src[i] as u64) << offset;
}
value
}
pub fn as_u16(&self) -> u16 {
if self.len() > 16 {panic!("Slice too large for u16");}
self.as_u64() as u16
}
pub fn as_u8(&self) -> u8 {
if self.len() > 8 {panic!("Slice too large for u8");}
self.as_u64() as u8
}
pub fn as_bool(&self) -> bool {
if self.len() != 1 {
panic!("Only slices of length 1 can be converted to bool");
}
self.as_u64() == 1
}
pub fn slice(&self, range: Range<usize>) -> BitSlice {
if range.end > self.len() {
panic!("Slice out of bounds: {}..{}", range.start, range.end);
}
BitSlice {
src: &self.src,
start: self.start + range.start,
end: self.start + range.end,
}
}
}
impl Index<usize> for BitSlice<'_> {
type Output = bool;
fn index(&self, index: usize) -> &bool {
self.src.index(self.start + index)
}
}

36
2021/src/main.rs Normal file
View File

@ -0,0 +1,36 @@
use std::time::{Instant};
use color_eyre::eyre;
mod lib;
use lib::load;
mod day18;
fn main() -> eyre::Result<()> {
let data = load("data/18.txt")?;
let start = Instant::now();
let (one, two) = day18::run(&data)?;
let (dur, unit) = format_ns(start.elapsed().as_nanos());
let precision = 2.0 - dur.log10().floor();
println!(
"One: {0}\nTwo: {1}\nCompleted in {2:.4$}{3}",
one, two, dur, unit, precision as usize
);
Ok(())
}
fn format_ns(ns: u128) -> (f64, &'static str) {
const UNITS: [&str; 4] = ["ns", "us", "ms", "s"];
let mut display_n = ns as f64; //
let mut unit_idx = 0;
while display_n > 1000.0 && unit_idx < 4 {
display_n /= 1000.0;
unit_idx += 1
}
(display_n, UNITS[unit_idx])
}

49
2022/Cargo.lock generated Normal file
View File

@ -0,0 +1,49 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "advent-2022"
version = "0.1.0"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "regex"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"

50
2022/Cargo.toml Normal file
View File

@ -0,0 +1,50 @@
[package]
name = "advent-2022"
version = "0.1.0"
edition = "2021"
[lib]
name = "advent_2022"
path = "src/lib.rs"
[[bin]]
name = "day1"
path = "src/day1.rs"
[[bin]]
name = "day2"
path = "src/day2.rs"
[[bin]]
name = "day3"
path = "src/day3.rs"
[[bin]]
name = "day4"
path = "src/day4.rs"
[[bin]]
name = "day5"
path = "src/day5.rs"
[[bin]]
name = "day6"
path = "src/day6.rs"
[[bin]]
name = "day7"
path = "src/day7.rs"
[[bin]]
name = "day8"
path = "src/day8.rs"
[[bin]]
name = "day9"
path = "src/day9.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.7.0"
lazy_static = "1.4.0"

28
2022/src/day1.rs Normal file
View File

@ -0,0 +1,28 @@
const DATA: &'static str = include_str!("../data/day1.txt");
fn main() {
let mut counts = DATA.trim()
.split("\n\n")
.map(|lines| {
lines.split("\n")
.map(|c| c.parse::<u64>().unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
counts.sort_by_key(|values| values.iter().sum::<u64>());
let max_total_cals: u64 = counts.last()
.unwrap()
.iter()
.sum();
let top3_total_cals: &u64 = &counts[(counts.len() - 3)..]
.iter()
.flatten()
.sum();
println!("Part 1: {max_total_cals}");
println!("Part 2: {top3_total_cals}");
}

108
2022/src/day2.rs Normal file
View File

@ -0,0 +1,108 @@
use std::str::FromStr;
const DATA: &'static str = include_str!("../data/day2.txt");
// const DATA: &'static str = "
// A Y
// B X
// C Z
// ";
enum RPS {
Rock = 1,
Paper = 2,
Scissors = 3,
}
impl FromStr for RPS {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"A" | "X" => Ok(Rock),
"B" | "Y" => Ok(Paper),
"C" | "Z" => Ok(Scissors),
_ => Err(())
}
}
}
enum Outcome {
Win = 6,
Loss = 0,
Draw = 3,
}
impl FromStr for Outcome {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
match s {
"X" => Ok(Loss),
"Y" => Ok(Draw),
"Z" => Ok(Win),
_ => Err(())
}
}
}
use RPS::*;
use Outcome::*;
fn compare(me: &RPS, them: &RPS) -> Outcome {
match (me, them) {
(Rock, Rock) => Draw,
(Rock, Paper) => Loss,
(Rock, Scissors) => Win,
(Paper, Rock) => Win,
(Paper, Paper) => Draw,
(Paper, Scissors) => Loss,
(Scissors, Rock) => Loss,
(Scissors, Paper) => Win,
(Scissors, Scissors) => Draw,
}
}
fn get_response(opponent: &RPS, outcome: &Outcome) -> RPS {
match (opponent, outcome) {
(Rock, Win) => Paper,
(Rock, Loss) => Scissors,
(Rock, Draw) => Rock,
(Paper, Win) => Scissors,
(Paper, Loss) => Rock,
(Paper, Draw) => Paper,
(Scissors, Win) => Rock,
(Scissors, Loss) => Paper,
(Scissors, Draw) => Scissors,
}
}
fn score(me: RPS, them: RPS) -> u64 {
let outcome = compare(&me, &them);
(me as u64) + (outcome as u64)
}
fn main() {
let mut total_1 = 0;
for pair in DATA.trim().split("\n") {
let mut it = pair.split(" ")
.map(|v| v.parse::<RPS>().unwrap());
let (them, me) = (it.next().unwrap(), it.next().unwrap());
total_1 += score(me, them);
}
println!("Part 1: {total_1}");
let mut total_2 = 0;
for pair in DATA.trim().split("\n") {
let mut it = pair.split(" ");
let them = it.next().unwrap().parse::<RPS>().unwrap();
let outcome = it.next().unwrap().parse::<Outcome>().unwrap();
let me = get_response(&them, &outcome);
total_2 += me as u64;
total_2 += outcome as u64
}
println!("Part 2: {total_2}");
}

103
2022/src/day3.rs Normal file
View File

@ -0,0 +1,103 @@
use std::str::FromStr;
const DATA: &'static str = include_str!("../data/day3.txt");
// const DATA: &'static str = "
// vJrwpWtwJgWrhcsFMMfFFhFp
// jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
// PmmdzqPrVvPwwTWBwg
// wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
// ttgJtRGJQctTZtZT
// CrZsJsPPZsGzwwsLwLmpwMDw
// ";
#[derive(Debug)]
enum DataError {
Imbalanced,
UnknownValue,
NoDuplicate,
NoTriplicate,
ShortGroup,
}
struct Rucksack {
left: Vec<u8>,
right: Vec<u8>,
}
impl Rucksack {
fn find_duplicate(&self) -> Option<u8> {
// pretty sure this is faster than hash set, given the sizes involved
self.left.iter()
.find(|c| self.right.contains(c))
.map(|c| *c)
}
fn iter_all(&self) -> impl Iterator<Item = &u8> {
self.left.iter().chain(self.right.iter())
}
fn contains(&self, v: u8) -> bool {
self.left.contains(&v) || self.right.contains(&v)
}
}
impl FromStr for Rucksack {
type Err = DataError;
fn from_str(line: &str) -> Result<Rucksack, DataError> {
let line = line.trim();
if line.len() % 2 != 0 {
return Err(DataError::Imbalanced);
}
let (a, b) = line.as_bytes().split_at(line.len() / 2);
Ok(Rucksack {
left: a.into(),
right: b.into(),
})
}
}
fn priority(c: u8) -> Result<u64, DataError> {
match c {
// lowercase
97..=122 => Ok(c as u64 - 96),
// uppercase
65..=90 => Ok(c as u64 - 38),
_ => Err(DataError::UnknownValue),
}
}
fn main() -> Result<(), DataError> {
let mut dup_total = 0;
let rucks = DATA.trim()
.split("\n")
.map(|line| Rucksack::from_str(line))
.collect::<Result<Vec<Rucksack>, DataError>>()?;
for ruck in rucks.iter() {
let dup = ruck.find_duplicate()
.ok_or(DataError::NoDuplicate)?;
dup_total += priority(dup)?;
}
println!("Part 1: {dup_total}");
let mut groups_total = 0;
for (idx, ruck) in rucks.iter().enumerate().step_by(3) {
if idx > rucks.len() - 3 {
return Err(DataError::ShortGroup);
}
let badge = *ruck.iter_all()
.find(|&v| rucks[idx + 1].contains(*v) && rucks[idx + 2].contains(*v))
.ok_or(DataError::NoTriplicate)?;
groups_total += priority(badge)?;
}
println!("Part 2: {groups_total}");
Ok(())
}

89
2022/src/day4.rs Normal file
View File

@ -0,0 +1,89 @@
const DATA: &'static str = include_str!("../data/day4.txt");
// const DATA: &'static str = "
// 2-4,6-8
// 2-3,4-5
// 5-7,7-9
// 2-8,3-7
// 6-6,4-6
// 2-6,4-8
// ";
#[derive(Debug)]
enum DataError {
NotANumber,
NotAPair,
NotARange,
}
struct Range {
start: u32,
end: u32,
}
impl Range {
fn contains(&self, other: &Range) -> bool {
self.start <= other.start && self.end >= other.end
}
fn overlaps(&self, other: &Range) -> bool {
// what if self.start == other.start or self.end == other.end?
if self.start <= other.start && self.end >= other.start {
return true;
}
if self.start <= other.end && self.end >= other.end {
return true;
}
false
}
}
impl std::str::FromStr for Range {
type Err = DataError;
fn from_str(s: &str) -> Result<Range, DataError> {
let mut bounds = s.split('-');
let start = bounds.next()
.ok_or(DataError::NotARange)?
.parse::<u32>()
.map_err(|_e| DataError::NotANumber)?;
let end = bounds.next()
.ok_or(DataError::NotARange)?
.parse::<u32>()
.map_err(|_e| DataError::NotANumber)?;
Ok(Range {start, end})
}
}
fn main() -> Result<(), DataError> {
let ranges = DATA.trim()
.lines()
.map(|line| {
let mut pairs = line.split(',');
let left: Range = pairs.next()
.ok_or(DataError::NotAPair)?
.parse()?;
let right: Range = pairs.next()
.ok_or(DataError::NotAPair)?
.parse()?;
Ok((left, right))
})
.collect::<Result<Vec<(Range, Range)>, DataError>>()?;
let total_supersets = ranges.iter()
.filter(|(a, b)| a.contains(b) || b.contains(a))
.count();
println!("Part 1: {total_supersets}");
let total_overlaps = ranges.iter()
.filter(|(a, b)| a.overlaps(b) || b.contains(a) || a.contains(b))
.count();
println!("Part 2: {total_overlaps}");
Ok(())
}

113
2022/src/day5.rs Normal file
View File

@ -0,0 +1,113 @@
use core::num::ParseIntError;
const DATA: &'static str = include_str!("../data/day5.txt");
// const DATA: &'static str = "
// [D]
// [N] [C]
// [Z] [M] [P]
// 1 2 3
// move 1 from 2 to 1
// move 3 from 1 to 3
// move 2 from 2 to 1
// move 1 from 1 to 2
// ";
enum BadLine {
NotEnoughWords,
ParseError(ParseIntError),
}
#[derive(Debug)]
struct State {
stacks: [Vec<char>; 9],
}
impl State {
fn process(&mut self, op: &Instruction) {
for _ in 0..(op.count) {
let item = self.stacks[op.src as usize - 1].pop().unwrap();
self.stacks[op.dst as usize - 1].push(item);
}
}
fn process_inorder(&mut self, op: &Instruction) {
let src_stack = &mut self.stacks[op.src as usize - 1];
let split_idx = src_stack.len() - op.count as usize;
let mut substack = src_stack.split_off(split_idx);
self.stacks[op.dst as usize - 1].append(&mut substack);
}
fn get_tops(&self) -> String {
let mut res = String::with_capacity(9);
for stack in &self.stacks {
res.push(*stack.last().unwrap());
}
res
}
}
#[derive(Debug)]
struct Instruction {
count: u32,
src: u32,
dst: u32,
}
fn parse_map(map: &str) -> State {
let rows: Vec<&str> = map.lines().collect();
let mut stacks: [Vec<char>; 9] = Default::default();
// skip the first row, because it's just the stack numbers
for row in rows.iter().rev().skip(1) {
for (col_num, c) in row.chars().enumerate() {
if c.is_ascii_uppercase() {
let stack_idx = (col_num - 1) / 4;
stacks[stack_idx].push(c);
}
}
}
State { stacks }
}
fn parse_instruction(data: &str) -> Result<Instruction, BadLine> {
let mut words = data.split(' ');
Ok(Instruction {
count: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
src: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
dst: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
})
}
fn main() -> Result<(), String> {
let mut splat = DATA.trim_end().split("\n\n");
let map = splat.next().unwrap();
let instruction_lines = splat.next().unwrap();
let mut state = parse_map(map);
let instructions: Vec<Instruction> = instruction_lines.lines()
.map(|line| parse_instruction(line).map_err(|_e| format!("Bad line: {line}")))
.collect::<Result<Vec<Instruction>, String>>()?;
for i in &instructions {
state.process(i);
}
println!("Part 1: {}", state.get_tops());
let mut state2 = parse_map(map);
for i in &instructions {
state2.process_inorder(i);
}
println!("Part 2: {}", state2.get_tops());
Ok(())
}

58
2022/src/day6.rs Normal file
View File

@ -0,0 +1,58 @@
// const DATA: &'static str = "zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw";
const DATA: &'static str = include_str!("../data/day6.txt");
struct CharCounts {
chars: [u8; 26],
}
impl CharCounts {
fn new() -> Self {
CharCounts { chars: [0; 26]}
}
fn idx(c: u8) -> usize {
(c as usize) - (b'a' as usize)
}
fn inc(&mut self, c: u8) {
self.chars[Self::idx(c)] += 1;
}
fn dec(&mut self, c: u8) {
self.chars[Self::idx(c)] -= 1;
}
fn max(&self) -> u8 {
// max only returns None if the iterator is empty
*self.chars.iter().max().unwrap()
}
}
fn find_marker(data: &[u8], window_size: usize) -> Option<usize> {
let mut window = CharCounts::new();
for (i, v) in data.iter().enumerate() {
window.inc(*v);
if i > (window_size - 1) {
let dropout = data[i - window_size];
window.dec(dropout);
}
if i > (window_size - 1) && window.max() == 1 {
return Some(i + 1);
}
}
None
}
fn main() {
let bytes = DATA.trim().as_bytes();
let m1 = find_marker(&bytes, 4).unwrap();
println!("Part 1: {m1}");
let m2 = find_marker(&bytes, 14).unwrap();
println!("Part 2: {m2}");
}

229
2022/src/day7.rs Normal file
View File

@ -0,0 +1,229 @@
use std::collections::HashMap;
use lazy_static::lazy_static;
use regex::Regex;
struct Directory {
name: String,
children: Vec<Node>,
}
struct File {
name: String,
size: usize,
}
enum Node {
Branch(Directory),
Leaf(File),
}
lazy_static! {
static ref CMD_CD: Regex = Regex::new(r"^\$ cd (\S+)$").unwrap();
static ref CMD_LS: Regex = Regex::new(r"^\$ ls$").unwrap();
static ref DIR_LISTING: Regex = Regex::new(r"^dir (\S+)$").unwrap();
static ref FILE_LISTING: Regex = Regex::new(r"^(\d+) (\S+)$").unwrap();
}
struct DirListing {
name: String,
children: Vec<usize>,
parent: Option<usize>,
}
enum NodeListing {
Branch(DirListing),
Leaf(File),
}
fn build_map(data: &str) -> Result<HashMap<usize, NodeListing>, String> {
let root = DirListing {
name: "".to_string(),
children: vec![],
parent: None,
};
let mut map = HashMap::new();
map.insert(0, NodeListing::Branch(root));
let mut stack: Vec<usize> = vec![];
stack.push(0);
'lines: for line in data.trim().lines() {
if CMD_LS.is_match(line) {continue;}
if let Some(c) = CMD_CD.captures(line) {
let name = c.get(1).unwrap().as_str();
if name == "/" {
stack.truncate(1);
continue;
}
if name == ".." {
stack.pop();
continue;
}
let current_dir = match map.get(&stack.last().unwrap()).unwrap() {
NodeListing::Branch(dir) => dir,
_ => unreachable!(),
};
for id in &current_dir.children {
let child_name = match map.get(&id).unwrap() {
NodeListing::Branch(dir) => &dir.name,
NodeListing::Leaf(file) => &file.name,
};
if child_name == name {
stack.push(*id);
continue 'lines;
}
}
return Err(format!("Attempted to cd into directory before ls-ing it: {}", line.to_string()));
}
if let Some(c) = DIR_LISTING.captures(line) {
let parent_id = stack.last().unwrap();
let child_id = map.len();
let parent = match map.get_mut(parent_id).unwrap() {
NodeListing::Branch(dir) => dir,
_ => unreachable!(),
};
parent.children.push(child_id);
let dir = DirListing {
name: c.get(1).unwrap().as_str().to_string(),
children: vec![],
parent: Some(*parent_id),
};
map.insert(child_id, NodeListing::Branch(dir));
continue;
}
if let Some(c) = FILE_LISTING.captures(line) {
let name = c.get(2).unwrap().as_str().to_string();
let size = c.get(1).unwrap().as_str().parse::<usize>()
.map_err(|_e| format!("Invalid file size: {}", line.to_string()))?;
let file_id = map.len();
let file = File {name, size};
map.insert(file_id, NodeListing::Leaf(file));
let parent = match map.get_mut(stack.last().unwrap()).unwrap() {
NodeListing::Branch(dir) => dir,
_ => unreachable!(),
};
parent.children.push(file_id);
continue;
}
return Err(format!("Could not interpret line: {line}"));
}
Ok(map)
}
fn build_tree(map: &mut HashMap<usize, NodeListing>, root_listing: &DirListing) -> Directory {
let mut root_children = Vec::new();
for child_id in &root_listing.children {
match map.remove(&child_id).unwrap() {
NodeListing::Leaf(file) => {
root_children.push(Node::Leaf(file));
},
NodeListing::Branch(dir_listing) => {
let dir = build_tree(map, &dir_listing);
root_children.push(Node::Branch(dir))
},
}
}
Directory {
name: root_listing.name.clone(),
children: root_children,
}
}
fn get_sizes(root: &Directory, mut sizes: Vec<usize>) -> (usize, Vec<usize>) {
let mut dir_size = 0;
for child in &root.children {
match child {
Node::Leaf(file) => dir_size += file.size,
Node::Branch(dir) => {
let (child_size, rs) = get_sizes(dir, sizes);
sizes = rs;
dir_size += child_size;
},
}
}
sizes.push(dir_size);
(dir_size, sizes)
}
fn main() -> Result<(), String> {
let mut map = build_map(DATA)?;
let root = match map.remove(&0).unwrap() {
NodeListing::Branch(dir) => dir,
_ => unreachable!(),
};
let tree = build_tree(&mut map, &root);
let (total_size, dir_sizes) = get_sizes(&tree, vec![]);
let part1_sizes: usize = dir_sizes.iter()
.filter(|&&s| s <= 100000)
.sum();
println!("Part 1: {part1_sizes}");
let available = 70_000_000 - total_size;
if available >= 30_000_000 {
return Err("Weird, there's already enough space available".to_string());
}
let target_size = 30_000_000 - available;
let delete_size = dir_sizes.iter()
.filter(|&&s| s >= target_size)
.min()
.unwrap();
println!("Part 2: {delete_size}");
Ok(())
}
const DATA: &'static str = include_str!("../data/day7.txt");
// const DATA: &'static str = "
// $ cd /
// $ ls
// dir a
// 14848514 b.txt
// 8504156 c.dat
// dir d
// $ cd a
// $ ls
// dir e
// 29116 f
// 2557 g
// 62596 h.lst
// $ cd e
// $ ls
// 584 i
// $ cd ..
// $ cd ..
// $ cd d
// $ ls
// 4060174 j
// 8033020 d.log
// 5626152 d.ext
// 7214296 k
// ";

93
2022/src/day8.rs Normal file
View File

@ -0,0 +1,93 @@
const DATA: &'static str = include_str!("../data/day8.txt");
// const DATA: &'static str = "
// 30373
// 25512
// 65332
// 33549
// 35390
// ";
// O(N * (2 * (sqrt(N) - 1))), so roughly N^1.5? Where N is number of elements in the grid
fn is_visible(grid: &Vec<Vec<usize>>, x: usize, y: usize) -> bool {
let height = grid[y][x];
let rows = grid.len();
let cols = grid[x].len();
// search horizontal
let vis_left = (0..x).all(|cmp_x| grid[y][cmp_x] < height);
let vis_right = ((x + 1)..cols).all(|cmp_x| grid[y][cmp_x] < height);
let vis_top = (0..y).all(|cmp_y| grid[cmp_y][x] < height);
let vis_bottom = ((y + 1)..rows).all(|cmp_y| grid[cmp_y][x] < height);
vis_left || vis_right || vis_top || vis_bottom
}
fn scenic_score(grid: &Vec<Vec<usize>>, x: usize, y: usize) -> usize {
let height = grid[y][x];
let rows = grid.len();
let cols = grid[x].len();
let sc_left = (0..x).rev()
.position(|cmp_x| grid[y][cmp_x] >= height) // 0-indexed position of the first blocking tree
.map(|p| p + 1) // +1 since we include the blocking tree in the count
.unwrap_or(x); // if no blocking trees, score is how many are to the left of current
let sc_right = ((x + 1)..cols)
.position(|cmp_x| grid[y][cmp_x] >= height)
.map(|p| p + 1)
.unwrap_or(cols - x - 1); // e.g. if x is 4 and there are 5 cols, this is rightmost, so 5 - 4 - 1 = 0
let sc_top = (0..y).rev()
.position(|cmp_y| grid[cmp_y][x] >= height)
.map(|p| p + 1)
.unwrap_or(y);
let sc_bottom = ((y + 1)..rows)
.position(|cmp_y| grid[cmp_y][x] >= height)
.map(|p| p + 1)
.unwrap_or(rows - y - 1);
sc_left * sc_right * sc_top * sc_bottom
}
fn build_grid(data: &str) -> Result<Vec<Vec<usize>>, String> {
data.trim()
.lines()
.map(|line| {
line.chars()
.map(|c| c.to_digit(10).map(|c| c as usize).ok_or_else(|| format!("Failed to convert to integer: {c}") ))
.collect::<Result<Vec<usize>, String>>()
})
.collect::<Result<Vec<Vec<usize>>, String>>()
}
fn main() -> Result<(), String> {
let grid = build_grid(DATA)?;
let mut visible_trees = 0;
for y in 0..grid.len() {
for x in 0..grid[y].len() {
if is_visible(&grid, x, y) {
visible_trees += 1;
}
}
}
println!("Part 1: {visible_trees}");
let mut max_score = 0;
for y in 0..grid.len() {
for x in 0..grid[y].len() {
let score = scenic_score(&grid, x, y);
if score > max_score {
max_score = score;
}
}
}
println!("Part 2: {max_score}");
Ok(())
}

184
2022/src/day9.rs Normal file
View File

@ -0,0 +1,184 @@
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(Copy, Clone, Debug)]
enum Direction {
Left,
Right,
Up,
Down,
}
impl FromStr for Direction {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
match s {
"L" => Ok(Direction::Left),
"R" => Ok(Direction::Right),
"U" => Ok(Direction::Up),
"D" => Ok(Direction::Down),
_ => Err(format!("Not a valid direction: {s}")),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct Pos {
x: isize,
y: isize,
}
#[derive(Debug)]
struct Rope<const L: usize> {
knots: [Pos; L],
tail_visited: HashSet<Pos>,
}
impl<const L: usize> Rope<L> {
fn new() -> Self {
Rope {
knots: [Pos {x: 0, y: 0}; L],
tail_visited: HashSet::new()
}
}
fn do_move(&mut self, dir: Direction, count: isize) {
for _ in 0..count {
let head = &mut self.knots[0];
match dir {
Direction::Left => head.x -= 1,
Direction::Right => head.x += 1,
Direction::Up => head.y += 1,
Direction::Down => head.y -= 1,
}
for i in 1..(self.knots.len()) {
self.move_follower(i);
}
self.tail_visited.insert(*self.knots.last().unwrap());
}
}
fn move_follower(&mut self, i: usize) {
let leader = self.knots[i - 1];
let follower = &mut self.knots[i];
let dx = leader.x - follower.x;
let dy = leader.y - follower.y;
// check to make sure that we didn't do anything wrong previously
if dx.abs() + dy.abs() > 4
|| dx.abs() == 0 && dy.abs() > 2
|| dx.abs() > 2 && dy.abs() == 0
{
panic!("Invalid head and tail positions: {dx}, {dy}");
}
// head and tail are in line vertically
if dx == 0 && dy.abs() == 2 {
follower.y += dy / 2; // use integer division to keep the sign of dy
}
// in line horizontally
if dy == 0 && dx.abs() == 2 {
follower.x += dx / 2; // similarly
}
// head and tail are offset by 1 horizontaly and 2 vertically
if dx.abs() == 1 && dy.abs() == 2 {
follower.x += dx;
follower.y += dy / 2;
}
// offset by 1 vertically and 2 horizontally
if dy.abs() == 1 && dx.abs() == 2 {
follower.y += dy;
follower.x += dx / 2;
}
// offset by 2 in both dimensions
if dx.abs() == 2 && dy.abs() == 2 {
follower.x += dx / 2;
follower.y += dy / 2;
}
}
}
use std::convert::TryFrom;
impl<const L: usize> Display for Rope<L> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let min_x = self.knots.iter().map(|pos| pos.x).min().unwrap();
let max_x = self.knots.iter().map(|pos| pos.x).max().unwrap();
let min_y = self.knots.iter().map(|pos| pos.y).min().unwrap();
let max_y = self.knots.iter().map(|pos| pos.y).max().unwrap();
let n_rows = usize::try_from(max_y - min_y + 1).unwrap();
let n_cols = usize::try_from(max_x - min_x + 1).unwrap();
let mut grid = vec![vec!['.'; n_cols]; n_rows];
for (i, kt) in self.knots.iter().enumerate() {
let rel_x = usize::try_from(kt.x - min_x).unwrap();
let rel_y = usize::try_from(max_y - kt.y).unwrap();
let tgt = &mut grid[rel_y][rel_x];
if *tgt == '.' {
*tgt = char::from_digit(i as u32, 10).unwrap();
}
}
for row in grid {
write!(f, "{}\n", row.iter().collect::<String>())?;
}
Ok(())
}
}
fn main() -> Result<(), String> {
let instructions = DATA.trim()
.lines()
.map(|line| {
let mut splat = line.split(' ');
match (splat.next(), splat.next()) {
(Some(dir), Some(dist)) => {
let dist = dist.parse().map_err(|_e| format!("Not an integer: {dist}"));
Ok((dir.parse()?, dist?))
},
_ => Err(format!("Could not parse move instruction from line: {line}"))
}
})
.collect::<Result<Vec<(Direction, isize)>, String>>()?;
let mut rope1 = Rope::<2>::new();
for &(dir, dist) in &instructions {
rope1.do_move(dir, dist);
}
println!("Part 1: {}", rope1.tail_visited.len());
let mut rope2 = Rope::<10>::new();
for &(dir, dist) in &instructions {
rope2.do_move(dir, dist);
}
println!("Part 2: {}", rope2.tail_visited.len());
Ok(())
}
const DATA: &'static str = include_str!("../data/day9.txt");
// const DATA: &'static str = "
// R 5
// U 8
// L 8
// D 3
// R 17
// D 10
// L 25
// U 20
// ";

67
2022/src/lib.rs Normal file
View File

@ -0,0 +1,67 @@
#![allow(dead_code)]
use std::ops::{Index, IndexMut};
struct Grid<T> {
rows: Vec<Vec<T>>
}
impl<T> Grid<T> {
fn new() -> Self {
Grid { rows: vec![] }
}
fn with_default_values<D>(width: usize, height: usize) -> Self
where T: Default + Clone
{
let rows = (0..height)
.map(|_i| vec![Default::default(); width])
.collect();
Grid { rows }
}
fn from_rows<I>(rows: I) -> Self
where I: Iterator<Item = Vec<T>>
{
Grid { rows: rows.collect() }
}
fn from_chars<F, E>(src: &str, conv: F) -> Result<Self, E>
where F: Fn(char) -> Result<T, E>
{
let rows: Result<Vec<Vec<T>>, E> = src.lines()
.map(|line| {
line.chars()
.map(|c| conv(c))
.collect()
})
.collect();
Ok(Grid { rows: rows? })
}
fn iter_rows(&self) -> impl Iterator<Item = &Vec<T>> {
self.rows.iter()
}
}
impl<T> Index<[usize; 2]> for Grid<T> {
type Output = T;
fn index(&self, idx: [usize; 2]) -> &T {
let [x, y] = idx;
&self.rows[y][x]
}
}
impl<T> IndexMut<[usize; 2]> for Grid<T> {
fn index_mut(&mut self, idx: [usize; 2]) -> &mut T {
let [x, y] = idx;
&mut self.rows[y][x]
}
}

79
2024/01.c Normal file
View File

@ -0,0 +1,79 @@
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int cmp(const void *a, const void *b) {
int *_a = (int *)a;
int *_b = (int *)b;
return *_a - *_b;
}
int count_occurrences(int num, int arr[], int len) {
int total = 0;
for (int i = 0; i < len; i++) {
if (arr[i] == num) {
total++;
}
}
return total;
}
int part1(int nums_l[], int nums_r[], int len) {
int sum = 0;
for (int i = 0; i < len; i++) {
int diff = nums_l[i] - nums_r[i];
if (diff < 0) {
diff = diff * -1;
}
sum += diff;
}
return sum;
}
int part2(int nums_l[], int nums_r[], int len) {
int score = 0;
for (int i = 0; i < len; i++) {
score += nums_l[i] * count_occurrences(nums_l[i], nums_r, len);
}
return score;
}
int main() {
FILE *file = fopen("data/01.txt", "r");
char *data = (char *)malloc(16384);
size_t data_len = fread(data, 1, 16384, file);
int nums_l[1000];
int nums_r[1000];
int nlines = 0;
while (1) {
char* line = strsep(&data, "\n");
int left = strtol(line, &line, 10);
int right = strtol(line, NULL, 10);
// if `strtol` fails, it apparently just returns 0, how helpful
if (left == 0 && right == 0) {
break;
}
nums_l[nlines] = left;
nums_r[nlines] = right;
nlines++;
}
qsort(nums_l, nlines, 4, cmp);
qsort(nums_r, nlines, 4, cmp);
int solution_1 = part1(nums_l, nums_r, nlines);
printf("Part 1: %i\n", solution_1);
int solution_2 = part2(nums_l, nums_r, nlines);
printf("Part 2: %i\n", solution_2);
}

77
2024/02.cpp Normal file
View File

@ -0,0 +1,77 @@
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
vector<int> parse_line(string line) {
int start = 0;
vector<int> result;
while (start < line.length()) {
int end = line.find(" ", start);
string word = line.substr(start, end - start);
int n = stoi(word);
result.push_back(n);
start = end + 1;
if (end == -1) {
break;
}
}
return result;
}
bool is_valid(vector<int> report) {
int prev_diff = 0;
for (int i = 1; i < report.size(); i++) {
int diff = report[i] - report[i - 1];
if (diff < -3 || diff == 0 || diff > 3) {
return false;
}
// on the first iteration, we can't compare to the previous difference
if (i == 1) {
prev_diff = diff;
continue;
}
if ((diff > 0 && prev_diff < 0) || (diff < 0 && prev_diff > 0)) {
return false;
}
prev_diff = diff;
}
return true;
}
int main() {
ifstream file("data/02.txt");
string line;
int count_part1 = 0;
int count_part2 = 0;
while (getline(file, line)) {
auto report = parse_line(line);
if (is_valid(report)) {
count_part1++;
count_part2++;
}
else {
for (int i = 0; i < report.size(); i++) {
int n = report[i];
report.erase(report.begin() + i);
if (is_valid(report)) {
count_part2++;
break;
}
report.insert(report.begin() + i, n);
}
}
}
cout << "Part 1: " << count_part1 << "\n";
cout << "Part 2: " << count_part2 << "\n";
}

87
2024/03.fs Normal file
View File

@ -0,0 +1,87 @@
: load-data ( addr1 u1 -- addr2 u2 )
20000 allocate drop \ allocate a 20k buffer, drop the status code
dup 2swap \ duplicate the buffer addr and stick both copies under the addr/length of filename
r/o open-file drop \ open file, drop the status code
20000 swap \ stick the number of bytes to be read in between the buffer addr and file id
read-file drop \ read the file, drop the status code
;
: chop-prefix ( addr u u2 -- addr2 u2 )
\ chop the first `u2` bytes off the beginning of the string at `addr u`
tuck \ duplicate `u2` and store it "under" the length of the string
- \ subtract `u2` from the length of the string
-rot \ stick the new string length underneath the start pointer
+ \ increment the start pointer by `u2`
swap \ put them back in the right order
;
require regexp.fs
: mul-instr ( addr u -- flag )
\ match a string of the form `mul(x,y)` where x and y are integers and capture those integers
(( =" mul(" \( {++ \d ++} \) ` , \( {++ \d ++} \) ` ) )) ;
: get-product ( addr u -- u2 )
mul-instr \ match the string from `addr u` against the above regex
if \ if the regex matches, then:
\1 s>number drop \ convert the first capture from string to number, drop the status code (we already know it will succeed)
\2 s>number drop \ convert the second capture from string to number, drop the status code
* \ multiply, and leave the answer on the stack
else
0 \ otherwise, leave 0 on the stack
then
;
variable result \ initialize `result` with 0
0 result !
variable enabled
-1 enabled ! \ idiomatically -1 is "true" (really anything other than 0 is true)
: handle-mul ( addr u -- )
get-product \ pass those to get-product above
result @ + \ load `result` and add to product
result ! \ store this new value back in `result`
;
: sum-mul-instrs ( addr u -- u2 )
\ we want to loop from addr to (addr + u - 8), because 8 is the min length of a valid mul(x,y) instruction
\ we also want to have addr + u on the top of the stack when we enter the loop,
\ so that we can use that to compute the remaining length of the string from our current address
over + \ copy addr to top of stack and add to length
dup 8 - \ duplicate, then subtract 8 from the top value
rot \ move original addr to top of stack
( stack at this point: [ addr + u, addr + u - 8, addr ] )
( i.e. [ end-of-string, loop-limit, loop-start ] )
do \ start looping
I 4 s" do()" str= \ compare the length-4 substring starting at I to the string "do()"
if \ if valid do() instruction,
-1 enabled ! \ set enabled=true
then
I 7 s" don't()" str= \ compare length-7 substring to "don't()"
if \ if valid don't() instruction,
0 enabled ! \ set enabled=false
then
I 4 s" mul(" str= \ compare length-4 substring to "mul("
enabled @ and \ combine with current value of `enabled`
if \ if a candidate for `mul(x,y)` instruction, and enabled=true, then
dup I - \ subtract current string pointer from end-of-string pointer to get length of remaining string
I swap handle-mul \ put current pointer onto stack again, swap so stack is ( addr len), and handle
then
loop
drop \ get rid of end-of-string pointer
result @ \ return value of result
;
s" data/03.txt" load-data
sum-mul-instrs .
bye

171
2024/04.f90 Normal file
View File

@ -0,0 +1,171 @@
program advent04
implicit none
character, dimension(146, 146) :: grid
integer :: row, col, part1, part2
real :: start, end
call cpu_time(start)
grid = load("data/04.txt", 140, 140)
part1 = 0
part2 = 0
do col = 4, 143
do row = 4, 143
if (grid(row, col) == 'X') then
part1 = part1 + count_xmas(row, col)
end if
if (grid(row, col) == 'A') then
part2 = part2 + count_mas(row, col)
end if
end do
end do
call cpu_time(end)
print *, "Part 1:", part1
print *, "Part 2:", part2
print *, "Execution time:", end - start
contains
function load(path, n_rows, n_cols) result(grid)
implicit none
character(*), intent(in) :: path
integer, intent(in) :: n_rows, n_cols
integer :: handle, row, col
character, dimension(:, :), allocatable :: grid
character(n_cols) :: line
allocate(grid(n_rows + 6, n_cols + 6))
grid = '.'
open(newunit=handle, file=path, status="old", action="read")
do row = 4, n_rows + 3
read(handle, *) line
do col = 1, n_cols
grid(row, col + 3) = line(col:col)
end do
end do
close(handle)
end function load
integer function count_xmas(row, col) result(count)
implicit none
integer, intent(in) :: row, col
integer :: i
integer(8) :: prod
integer(8), dimension(8) :: primes
character, dimension(7, 7) :: test_grid, window
integer(8), dimension(7, 7) :: prime_mask, matches, matches_prime
test_grid = reshape( &
[&
'S', '.', '.', 'S', '.', '.', 'S', &
'.', 'A', '.', 'A', '.', 'A', '.', &
'.', '.', 'M', 'M', 'M', '.', '.', &
'S', 'A', 'M', 'X', 'M', 'A', 'S', &
'.', '.', 'M', 'M', 'M', '.', '.', &
'.', 'A', '.', 'A', '.', 'A', '.', &
'S', '.', '.', 'S', '.', '.', 'S' &
], &
shape(test_grid) &
)
primes = [2, 3, 5, 7, 11, 13, 17, 19]
prime_mask = reshape( &
[ &
2, 1, 1, 3, 1, 1, 5, &
1, 2, 1, 3, 1, 5, 1, &
1, 1, 2, 3, 5, 1, 1, &
19, 19, 19, 1, 7, 7, 7, &
1, 1, 17, 13, 11, 1, 1, &
1, 17, 1, 13, 1, 11, 1, &
17, 1, 1, 13, 1, 1, 11 &
], &
shape(prime_mask) &
)
window = grid(row - 3:row + 3, col - 3:col + 3)
matches = logical_to_int64(window == test_grid)
matches_prime = matches * prime_mask
prod = product(zero_to_one(matches_prime))
count = 0
do i = 1, 8
if (mod(prod, primes(i) ** 3) == 0) then
count = count + 1
end if
end do
end function count_xmas
integer function count_mas(row, col) result(count)
implicit none
integer, intent(in) :: row, col
integer :: i
character, dimension(3, 3) :: window, t1, t2, t3, t4
t1 = reshape( &
[ &
'M', '.', 'S', &
'.', 'A', '.', &
'M', '.', 'S' &
], &
shape(t1) &
)
t2 = t1(3:1:-1, :) ! flip t1 top-to-bottom
t3 = transpose(t1) ! swap t1 rows for columns
t4 = t3(:, 3:1:-1) ! flip t3 lef-to-right
window = grid(row - 1:row + 1, col - 1:col + 1)
if ( &
count_matches(window, t1) == 5 &
.or. count_matches(window, t2) == 5 &
.or. count_matches(window, t3) == 5 &
.or. count_matches(window, t4) == 5 &
) then
count = 1
else
count = 0
end if
end function count_mas
integer function count_matches(a1, a2) result(matches)
implicit none
character, dimension(:, :) :: a1, a2
matches = count(a1 == a2)
end function count_matches
elemental integer(8) function logical_to_int64(b) result(i)
implicit none
logical, intent(in) :: b
if (b) then
i = 1
else
i = 0
end if
end function logical_to_int64
elemental integer(8) function zero_to_one(x) result(y)
implicit none
integer(8), intent(in) :: x
if (x == 0) then
y = 1
else
y = x
end if
end function zero_to_one
end program advent04