├── 2015 ├── deps.edn ├── Makefile ├── src │ ├── 25.clj │ ├── 17.clj │ ├── 10.clj │ ├── 04.clj │ ├── 24.clj │ ├── 20.clj │ ├── 12.clj │ ├── 08.clj │ ├── 15.clj │ ├── 14.clj │ ├── 11.clj │ ├── 23.clj │ ├── 21.clj │ └── 09.clj ├── LICENSE └── README.md ├── 2016 ├── dune-project ├── aoc.opam ├── lib │ ├── dune │ └── aoc.ml ├── Makefile ├── src │ ├── day19.ml │ ├── day16.ml │ ├── day18.ml │ ├── day13.ml │ ├── day05.ml │ ├── day17.ml │ ├── day12.ml │ └── day14.ml └── LICENSE ├── 2017 ├── Makefile ├── Aoc.hs ├── src │ ├── 15.hs │ ├── 06.hs │ ├── 17.hs │ ├── 13.hs │ ├── 10.hs │ ├── 24.hs │ ├── 02.hs │ └── 03.hs ├── LICENSE └── README.md ├── 2018 ├── requirements.txt ├── Makefile ├── src │ ├── 01.py │ ├── 05.py │ ├── 14.py │ ├── 03.py │ ├── 02.py │ ├── 25.py │ ├── aoc.py │ ├── 09.py │ ├── 23.py │ ├── 10.py │ ├── 08.py │ ├── 04.py │ ├── 07.py │ ├── 06.py │ ├── 12.py │ ├── 11.py │ ├── 17.py │ ├── 18.py │ ├── 13.py │ └── 19.py ├── LICENSE └── README.md ├── 2019 ├── Cargo.toml ├── Makefile ├── README.md ├── src │ └── bin │ │ ├── 04.rs │ │ ├── 02.rs │ │ └── 01.rs └── LICENSE ├── 2020 ├── Cargo.toml ├── src │ ├── bin │ │ ├── 25.rs │ │ ├── 15.rs │ │ ├── 23.rs │ │ ├── 01.rs │ │ ├── 23.c │ │ └── 13.rs │ ├── lib.rs │ └── main.rs ├── Makefile ├── README.md └── LICENSE ├── 2021 ├── src │ ├── bin │ │ ├── 01.rs │ │ ├── 02.rs │ │ ├── 06.rs │ │ ├── 07.rs │ │ ├── 03.rs │ │ ├── 05.rs │ │ ├── 12.rs │ │ ├── 25.rs │ │ ├── 17.rs │ │ ├── 11.rs │ │ ├── 10.rs │ │ ├── 20.rs │ │ ├── 09.rs │ │ ├── 14.rs │ │ ├── 13.rs │ │ ├── 21.rs │ │ ├── 15.rs │ │ └── 08.rs │ ├── main.rs │ └── lib.rs ├── Makefile ├── Cargo.toml ├── LICENSE └── README.md ├── 2022 ├── src │ ├── bin │ │ ├── 01.rs │ │ ├── 25.rs │ │ ├── 06.rs │ │ ├── 10.rs │ │ ├── 04.rs │ │ ├── 03.rs │ │ ├── 20.rs │ │ ├── 18.rs │ │ ├── 02.rs │ │ ├── 08.rs │ │ ├── 09.rs │ │ ├── 14.rs │ │ ├── 21.rs │ │ ├── 07.rs │ │ ├── 12.rs │ │ ├── 15.rs │ │ ├── 05.rs │ │ ├── 13.rs │ │ ├── 16.rs │ │ └── 11.rs │ ├── main.rs │ └── lib.rs ├── Makefile ├── Cargo.toml ├── README.md ├── timings.md └── LICENSE ├── 2023 ├── src │ ├── template.rs │ ├── bin │ │ ├── 09.rs │ │ ├── 04.rs │ │ ├── 01.rs │ │ ├── 06.rs │ │ ├── 02.rs │ │ ├── 18.rs │ │ ├── 15.rs │ │ ├── 11.rs │ │ ├── 13.rs │ │ ├── 08.rs │ │ ├── 03.rs │ │ ├── 17.rs │ │ ├── 21.rs │ │ ├── 16.rs │ │ ├── 14.rs │ │ └── 22.rs │ ├── lib.rs │ └── main.rs ├── README.md ├── Makefile └── Cargo.toml ├── 2024 ├── README.md ├── src │ ├── template.rs │ ├── bin │ │ ├── 01.rs │ │ ├── 03.rs │ │ ├── 13.rs │ │ ├── 02.rs │ │ ├── 25.rs │ │ ├── 05.rs │ │ ├── 22.rs │ │ ├── 07.rs │ │ ├── 19.rs │ │ ├── 11.rs │ │ ├── 10.rs │ │ ├── 09.rs │ │ ├── 14.rs │ │ ├── 04.rs │ │ ├── 08.rs │ │ ├── 18.rs │ │ ├── 24.rs │ │ ├── 20.rs │ │ ├── 06.rs │ │ └── 17.rs │ ├── lib.rs │ └── main.rs ├── Makefile └── Cargo.toml ├── 2025 ├── src │ ├── template.rs │ ├── bin │ │ ├── 01.rs │ │ ├── 03.rs │ │ ├── 12.rs │ │ ├── 02.rs │ │ ├── 05.rs │ │ ├── 07.rs │ │ ├── 11.rs │ │ ├── 04.rs │ │ └── 06.rs │ ├── lib.rs │ └── main.rs ├── Makefile ├── Cargo.toml └── README.md ├── .gitattributes ├── imgs ├── all-stars.png ├── all-stars-2019.png ├── end-screen-2015.png ├── end-screen-2016.png ├── end-screen-2017.png ├── end-screen-2018.png ├── leaderboard-2019.png ├── leaderboard-2020.png ├── leaderboard-2021.png ├── leaderboard-2022.png ├── leaderboard-2023.png ├── leaderboard-2024.png ├── leaderboard-2025.png └── leaderboard-2020-01.png ├── .gitignore ├── fetch.sh ├── Makefile ├── LICENSE └── README.md /2016/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.8) 2 | -------------------------------------------------------------------------------- /2018/requirements.txt: -------------------------------------------------------------------------------- 1 | z3-solver 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ml linguist-language=OCaml 2 | -------------------------------------------------------------------------------- /2016/aoc.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "me" 3 | -------------------------------------------------------------------------------- /2016/lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name aoc) 3 | (public_name aoc)) 4 | -------------------------------------------------------------------------------- /imgs/all-stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/all-stars.png -------------------------------------------------------------------------------- /imgs/all-stars-2019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/all-stars-2019.png -------------------------------------------------------------------------------- /imgs/end-screen-2015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/end-screen-2015.png -------------------------------------------------------------------------------- /imgs/end-screen-2016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/end-screen-2016.png -------------------------------------------------------------------------------- /imgs/end-screen-2017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/end-screen-2017.png -------------------------------------------------------------------------------- /imgs/end-screen-2018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/end-screen-2018.png -------------------------------------------------------------------------------- /imgs/leaderboard-2019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/leaderboard-2019.png -------------------------------------------------------------------------------- /imgs/leaderboard-2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/leaderboard-2020.png -------------------------------------------------------------------------------- /imgs/leaderboard-2021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/leaderboard-2021.png -------------------------------------------------------------------------------- /imgs/leaderboard-2022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/leaderboard-2022.png -------------------------------------------------------------------------------- /imgs/leaderboard-2023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/leaderboard-2023.png -------------------------------------------------------------------------------- /imgs/leaderboard-2024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/leaderboard-2024.png -------------------------------------------------------------------------------- /imgs/leaderboard-2025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/leaderboard-2025.png -------------------------------------------------------------------------------- /imgs/leaderboard-2020-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode/HEAD/imgs/leaderboard-2020-01.png -------------------------------------------------------------------------------- /2024/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2024 :christmas_tree: 2 | Solutions to AoC 2024 in Rust :crab: 3 | 4 | ## Leaderboard 5 | ![leaderboard](../imgs/leaderboard-2024.png) 6 | -------------------------------------------------------------------------------- /2015/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps { 2 | cheshire/cheshire {:mvn/version "5.10.0"} 3 | org.clojure/data.priority-map {:mvn/version "1.0.0"} 4 | org.clojure/math.combinatorics {:mvn/version "0.1.6"}}} 5 | -------------------------------------------------------------------------------- /2020/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc2020" 3 | version = "0.1.0" 4 | authors = ["Axel Lindeberg "] 5 | edition = "2018" 6 | default-run = "aoc2020" 7 | 8 | [dependencies] 9 | itertools = "0.9" 10 | regex = "1.4" 11 | once_cell = "1.5" 12 | -------------------------------------------------------------------------------- /2023/src/template.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #[aoc::main($DAY)] 4 | fn main(input: &str) -> (usize, usize) { 5 | let (mut p1, mut p2) = (0, 0); 6 | let xs = input.split('\n').map(|l| { 7 | l.split(',').collect::>() 8 | }).collect::>(); 9 | (p1, p2) 10 | } 11 | -------------------------------------------------------------------------------- /2015/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2015 $* 7 | 8 | $(DAYS): %: inputs/%.in 9 | clj -M src/$*.clj 10 | 11 | all: $(DAYS) 12 | -------------------------------------------------------------------------------- /2018/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2018 $* 7 | 8 | $(DAYS): %: inputs/%.in 9 | python3 src/$*.py 10 | 11 | all: $(DAYS) 12 | -------------------------------------------------------------------------------- /2024/src/template.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #[aoc::main($DAY)] 4 | fn main(input: &str) -> (usize, usize) { 5 | let (mut p1, mut p2) = (0, 0); 6 | let xs = input.split('\n').map(|l| { 7 | l.split(',').collect::>() 8 | }).collect::>(); 9 | (p1, p2) 10 | } 11 | -------------------------------------------------------------------------------- /2025/src/template.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #[aoc::main($DAY)] 4 | fn main(input: &str) -> (usize, usize) { 5 | let (mut p1, mut p2) = (0, 0); 6 | let xs = input.split('\n').map(|l| { 7 | l.split(',').collect::>() 8 | }).collect::>(); 9 | (p1, p2) 10 | } 11 | -------------------------------------------------------------------------------- /2016/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2016 $* 7 | 8 | $(DAYS): %: inputs/%.in 9 | dune exec --release src/$*.exe 10 | 11 | all: $(DAYS) 12 | -------------------------------------------------------------------------------- /2017/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2017 $* 7 | 8 | src/%: src/%.hs 9 | ghc -O $^ 10 | 11 | $(DAYS): %: inputs/%.in src/% 12 | ./src/$* 13 | 14 | all: $(DAYS) 15 | -------------------------------------------------------------------------------- /2015/src/25.clj: -------------------------------------------------------------------------------- 1 | (def input '(3010 3019)) 2 | 3 | (defn walk-diagonal [r c v] 4 | (if (= [r c] input) 5 | v 6 | (let [[r c] (if (= r 1) [(inc c) 1] [(dec r) (inc c)])] 7 | (recur r c (mod (* v 252533) 33554393))))) 8 | 9 | (println "Part one:" (walk-diagonal 1 1 20151125)) 10 | (println "Part two:" "🎄") 11 | -------------------------------------------------------------------------------- /2023/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2023 :christmas_tree: 2 | Solutions to AoC 2023 in Rust :crab: 3 | 4 | ## Leaderboard 5 | Getting up at 5:50 every day is tough. Life got in the way a few times. Best placement this year was 111. Went really well this year, especially towards the end. 6 | 7 | ![leaderboard](../imgs/leaderboard-2023.png) 8 | -------------------------------------------------------------------------------- /2019/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "AdventOfCode2019" 3 | version = "0.1.0" 4 | authors = ["Axel Lindeberg "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "intcoder" 9 | path = "src/intcoder.rs" 10 | 11 | [dependencies] 12 | itertools = "0.8.2" 13 | num-integer = "0.1.41" 14 | mod_exp = "1.0.1" 15 | easy_io = "0.3.0" 16 | -------------------------------------------------------------------------------- /2020/src/bin/25.rs: -------------------------------------------------------------------------------- 1 | const A: usize = 13316116; 2 | const B: usize = 13651422; 3 | 4 | aoc2020::main! { 5 | let (mut x, mut loop_size) = (1,0); 6 | while x != A && x != B { 7 | x = x * 7 % 20201227; 8 | loop_size += 1; 9 | } 10 | let y = if x == A {B} else {A}; 11 | let key = (0..loop_size).fold(1, |x,_| x * y % 20201227); 12 | (key, '🎄') 13 | } 14 | -------------------------------------------------------------------------------- /2018/src/01.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from itertools import cycle 3 | 4 | @aoc.main('01') 5 | def main(indata: str) -> tuple[int,int]: 6 | nums, seen, s = [int(l) for l in indata.split('\n')], set(), 0 7 | for x in cycle(nums): 8 | s += x 9 | if s in seen: 10 | break 11 | seen.add(s) 12 | return sum(nums), s 13 | 14 | if __name__ == "__main__": 15 | main() 16 | -------------------------------------------------------------------------------- /2022/src/bin/01.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | #[aoc::main(01)] 4 | fn main(input: &str) -> (usize, usize) { 5 | let xs = input.split("\n\n") 6 | .map(|s| s.lines().map(|l| l.parse::().unwrap()).sum::()) 7 | .sorted() 8 | .rev() 9 | .collect::>(); 10 | let p1 = xs[0]; 11 | let p2 = xs[0..3].iter().sum(); 12 | (p1,p2) 13 | } 14 | -------------------------------------------------------------------------------- /2016/lib/aoc.ml: -------------------------------------------------------------------------------- 1 | open Format 2 | 3 | let parse_lines parse_line input = input |> String.split_on_char '\n' |> List.map parse_line 4 | 5 | let timer f = 6 | let t0 = Sys.time() in 7 | let (part1, part2) = f () in 8 | let elapsed_ms = (Sys.time() -. t0) *. 1000.0 in 9 | printf "Part 1: %s\n" part1; 10 | printf "Part 2: %s\n" part2; 11 | printf "Time: %.3fms\n" elapsed_ms 12 | -------------------------------------------------------------------------------- /2020/src/bin/15.rs: -------------------------------------------------------------------------------- 1 | use std::collections::*; 2 | 3 | fn solve(target: u32) -> u32 { 4 | let mut seen = [9,19,1,6,0,5].iter() 5 | .enumerate() 6 | .map(|(i,&e)| (e, i as u32 + 1)) 7 | .collect::>(); 8 | (7..target).fold(4, |last, i| i - seen.insert(last, i).unwrap_or(i)) 9 | } 10 | 11 | aoc2020::main! { 12 | (solve(2020), solve(30_000_000)) 13 | } 14 | -------------------------------------------------------------------------------- /2021/src/bin/01.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | #[aoc::main(01)] 4 | fn main(input: &str) -> (usize,usize) { 5 | let nums = input.lines() 6 | .map(|s| s.parse::().unwrap()) 7 | .collect::>(); 8 | let p1 = nums.iter().tuple_windows().filter(|(a,b)| a < b).count(); 9 | let p2 = nums.iter().tuple_windows().filter(|(a,_,_,d)| a < d).count(); 10 | (p1,p2) 11 | } 12 | -------------------------------------------------------------------------------- /2025/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2025 $* 7 | 8 | src/bin/%.rs: 9 | DAY=$* envsubst < src/template.rs > $@ 10 | 11 | $(DAYS): %: src/bin/%.rs inputs/%.in 12 | cargo run --quiet --release --bin $* 13 | 14 | all: $(patsubst %,inputs/%.in,$(DAYS)) 15 | cargo run --quiet --release 16 | -------------------------------------------------------------------------------- /2022/src/bin/25.rs: -------------------------------------------------------------------------------- 1 | fn todec(s: &str) -> usize { 2 | s.chars().fold(0, |n, d| n * 5 + "=-012".chars().position(|x| x == d).unwrap() - 2) 3 | } 4 | 5 | fn tosnafu(n: usize) -> String { 6 | if n == 0 {String::new()} else {tosnafu((n+2)/5) + ["0","1","2","=","-"][n % 5]} 7 | } 8 | 9 | #[aoc::main(25)] 10 | fn main(input: &str) -> (String, char) { 11 | (tosnafu(input.lines().map(todec).sum()), '🎄') 12 | } 13 | -------------------------------------------------------------------------------- /2022/src/bin/06.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn find_unique_chunk(input: &str, size: usize) -> usize { 4 | size + input.as_bytes() 5 | .windows(size) 6 | .position(|window| window.iter().tuple_combinations().all(|(a,b)| a != b)) 7 | .unwrap() 8 | } 9 | 10 | #[aoc::main(06)] 11 | fn main(input: &str) -> (usize, usize) { 12 | (find_unique_chunk(input, 4), find_unique_chunk(input, 14)) 13 | } 14 | -------------------------------------------------------------------------------- /2016/src/day19.ml: -------------------------------------------------------------------------------- 1 | let input = 3012210 2 | 3 | let rec josephus i = 4 | if i < 3 then 1 else josephus (i/2) * 2 + ((i mod 2) * 2 - 1) 5 | 6 | let rec find_pow_three i = 7 | if i * 3 > input then i else find_pow_three (i*3) 8 | 9 | let main () = 10 | let part1 = josephus input in 11 | let part2 = input - (find_pow_three 1) in 12 | (string_of_int part1, string_of_int part2) 13 | 14 | let () = Aoc.timer main 15 | -------------------------------------------------------------------------------- /2015/src/17.clj: -------------------------------------------------------------------------------- 1 | (def input '(43 3 4 10 21 44 4 6 47 41 34 17 17 44 36 31 46 9 27 38)) 2 | 3 | (defn powerset [base-set] 4 | (defn sets [xs x] (concat xs (map #(cons x %) xs))) 5 | (reduce sets [()] base-set)) 6 | 7 | (let [valid (->> input powerset (filter #(= 150 (reduce + %))))] 8 | (->> valid count (println "Part one:")) 9 | (->> valid (map count) frequencies (apply min-key first) second (println "Part two:"))) 10 | -------------------------------------------------------------------------------- /2019/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2019 $* 7 | 8 | src/bin/%.rs: 9 | DAY=$* envsubst < src/template.rs > $@ 10 | 11 | $(DAYS): %: src/bin/%.rs inputs/%.in 12 | cargo run --quiet --release --bin $* 13 | 14 | all: $(patsubst %,inputs/%.in,$(DAYS)) 15 | cargo run --quiet --release 16 | -------------------------------------------------------------------------------- /2020/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2020 $* 7 | 8 | src/bin/%.rs: 9 | DAY=$* envsubst < src/template.rs > $@ 10 | 11 | $(DAYS): %: src/bin/%.rs inputs/%.in 12 | cargo run --quiet --release --bin $* 13 | 14 | all: $(patsubst %,inputs/%.in,$(DAYS)) 15 | cargo run --quiet --release 16 | -------------------------------------------------------------------------------- /2020/src/lib.rs: -------------------------------------------------------------------------------- 1 | // a macro to automatically time the solution 2 | #[macro_export] 3 | macro_rules! main { 4 | ($($body:tt)+) => { 5 | fn main() { 6 | let now = std::time::Instant::now(); 7 | let (p1,p2) = { $($body)+ }; 8 | let time = now.elapsed().as_millis(); 9 | println!("Part one: {}", p1); 10 | println!("Part two: {}", p2); 11 | println!("Time: {}ms", time); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /2021/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2021 $* 7 | 8 | src/bin/%.rs: 9 | DAY=$* envsubst < src/template.rs > $@ 10 | 11 | $(DAYS): %: src/bin/%.rs inputs/%.in 12 | cargo run --quiet --release --bin $* 13 | 14 | all: $(patsubst %,inputs/%.in,$(DAYS)) 15 | cargo run --quiet --release 16 | -------------------------------------------------------------------------------- /2022/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2022 $* 7 | 8 | src/bin/%.rs: 9 | DAY=$* envsubst < src/template.rs > $@ 10 | 11 | $(DAYS): %: src/bin/%.rs inputs/%.in 12 | cargo run --quiet --release --bin $* 13 | 14 | all: $(patsubst %,inputs/%.in,$(DAYS)) 15 | cargo run --quiet --release 16 | -------------------------------------------------------------------------------- /2023/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2023 $* 7 | 8 | src/bin/%.rs: 9 | DAY=$* envsubst < src/template.rs > $@ 10 | 11 | $(DAYS): %: src/bin/%.rs inputs/%.in 12 | cargo run --quiet --release --bin $* 13 | 14 | all: $(patsubst %,inputs/%.in,$(DAYS)) 15 | cargo run --quiet --release 16 | -------------------------------------------------------------------------------- /2024/Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | .DEFAULT_GOAL := all 3 | .PHONY: $(DAYS) all 4 | 5 | inputs/%.in: 6 | ../fetch.sh 2024 $* 7 | 8 | src/bin/%.rs: 9 | DAY=$* envsubst < src/template.rs > $@ 10 | 11 | $(DAYS): %: src/bin/%.rs inputs/%.in 12 | cargo run --quiet --release --bin $* 13 | 14 | all: $(patsubst %,inputs/%.in,$(DAYS)) 15 | cargo run --quiet --release 16 | -------------------------------------------------------------------------------- /2023/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc" 3 | version = "0.1.0" 4 | authors = ["Axel Lindeberg"] 5 | edition = "2021" 6 | default-run = "aoc" 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [profile.dev] 12 | overflow-checks = false 13 | 14 | [dependencies] 15 | itertools = "0.12" 16 | hashbrown = "0.14" 17 | z3 = "0.12" 18 | 19 | # lib proc-macro dependencies 20 | syn = { version = "2.0", features = ["full"] } 21 | quote = "1.0" 22 | -------------------------------------------------------------------------------- /2021/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc" 3 | version = "0.1.0" 4 | authors = ["Axel Lindeberg"] 5 | edition = "2021" 6 | default-run = "aoc" 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [dependencies] 12 | itertools = "0.10" 13 | hashbrown = "0.11" 14 | 15 | # main.rs dependencies 16 | regex = "1.5" 17 | once_cell = "1.9" 18 | 19 | # lib proc-macro dependencies 20 | syn = { version = "1.0", features = ["full"] } 21 | quote = "1.0" 22 | -------------------------------------------------------------------------------- /2024/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc" 3 | version = "0.1.0" 4 | authors = ["Axel Lindeberg"] 5 | edition = "2021" 6 | default-run = "aoc" 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [profile.dev] 12 | overflow-checks = false 13 | 14 | [dependencies] 15 | itertools = "0.13" 16 | hashbrown = "0.15" 17 | regex = "1.11" 18 | z3 = "0.12" 19 | 20 | # lib proc-macro dependencies 21 | syn = { version = "2.0", features = ["full"] } 22 | quote = "1.0" 23 | -------------------------------------------------------------------------------- /2022/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc" 3 | version = "0.1.0" 4 | authors = ["Axel Lindeberg"] 5 | edition = "2021" 6 | default-run = "aoc" 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [profile.dev] 12 | overflow-checks = false 13 | 14 | [dependencies] 15 | itertools = "0.10" 16 | hashbrown = "0.13" 17 | serde_json = "1.0" 18 | rayon = "1.6" 19 | 20 | # lib proc-macro dependencies 21 | syn = { version = "1.0", features = ["full"] } 22 | quote = "1.0" 23 | -------------------------------------------------------------------------------- /2021/src/bin/02.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | #[aoc::main(02)] 4 | fn main(input: &str) -> (usize,usize) { 5 | let ops = input.split_whitespace() 6 | .tuples() 7 | .map(|(op,i)| (op.as_bytes()[0], i.parse().unwrap())) 8 | .collect::>(); 9 | let (x,y,z) = ops.iter() 10 | .fold((0,0,0), |(x,y,aim),(op,i)| match op { 11 | b'f' => (x+i, y+aim*i, aim), 12 | b'd' => (x, y, aim+i), 13 | b'u' => (x, y, aim-i), 14 | _ => unreachable!() 15 | }); 16 | (x*z, x*y) 17 | } 18 | -------------------------------------------------------------------------------- /2025/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc" 3 | version = "0.1.0" 4 | authors = ["Axel Lindeberg"] 5 | edition = "2024" 6 | default-run = "aoc" 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [profile.dev] 12 | overflow-checks = false 13 | 14 | [dependencies] 15 | itertools = "0.13" 16 | hashbrown = "0.15" 17 | regex = "1.11" 18 | 19 | # lib proc-macro dependencies 20 | syn = { version = "2.0", features = ["full"] } 21 | quote = "1.0" 22 | good_lp = { version = "1.14.2", features = ["highs"], default-features = false } 23 | -------------------------------------------------------------------------------- /2017/Aoc.hs: -------------------------------------------------------------------------------- 1 | module Aoc where 2 | import Data.Time 3 | 4 | elapsedMs :: UTCTime -> IO Int 5 | elapsedMs t0 = do 6 | t1 <- getCurrentTime 7 | let dt = nominalDiffTimeToSeconds $ diffUTCTime t1 t0 8 | return $ round (dt * 1000000) `div` 1000 9 | 10 | timer :: (Show a, Show b) => (Int -> (a,b)) -> IO() 11 | timer fn = do 12 | t0 <- getCurrentTime 13 | let (a,b) = fn 0 14 | putStrLn $ "Part one: " ++ show a 15 | putStrLn $ "Part one: " ++ show b 16 | ms <- elapsedMs t0 17 | putStrLn $ "Time: " ++ show ms ++ "ms" 18 | -------------------------------------------------------------------------------- /2018/src/05.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | 3 | def react(init: str, c: str = '') -> int: 4 | s = [x for x in init if x.lower() != c] 5 | for i in range(len(s)-1, -1, -1): 6 | if i+1 < len(s) and s[i] != s[i+1] and s[i].upper() == s[i+1].upper(): 7 | s.pop(i) 8 | s.pop(i) 9 | return len(s) 10 | 11 | @aoc.main('05') 12 | def main(indata: str) -> tuple[int,int]: 13 | part2 = min(react(indata, c) for c in "abcdefghijklmnopqrstuvxyz") 14 | return react(indata), part2 15 | 16 | if __name__ == "__main__": 17 | main() 18 | -------------------------------------------------------------------------------- /2025/src/bin/01.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(01)] 2 | fn main(input: &str) -> (usize, usize) { 3 | let (mut r, mut p1, mut p2) = (50, 0, 0); 4 | for l in input.split('\n') { 5 | let x = l[1..].parse::().unwrap(); 6 | let d = if l.as_bytes()[0] == b'R' {1} else {-1}; 7 | for _ in 0..x { 8 | r += d; 9 | if r % 100 == 0 { 10 | p2 += 1; 11 | } 12 | } 13 | if r % 100 == 0 { 14 | p1 += 1; 15 | } 16 | } 17 | (p1, p2) 18 | } 19 | -------------------------------------------------------------------------------- /2021/src/bin/06.rs: -------------------------------------------------------------------------------- 1 | fn simulate_fishes(mut fishes: [usize;9], size: usize) -> usize { 2 | for _ in 0..size { 3 | fishes[7] += fishes[0]; 4 | fishes.rotate_left(1); 5 | } 6 | fishes.iter().sum() 7 | } 8 | 9 | #[aoc::main(06)] 10 | fn main(input: &str) -> (usize,usize) { 11 | let mut fishes = [0;9]; 12 | for s in input.split(',') { 13 | let i = s.parse::().unwrap(); 14 | fishes[i] += 1; 15 | } 16 | let p1 = simulate_fishes(fishes, 80); 17 | let p2 = simulate_fishes(fishes, 256); 18 | (p1,p2) 19 | } 20 | -------------------------------------------------------------------------------- /2025/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2025 :christmas_tree: 2 | Solutions to AoC 2025 in Rust :crab: 3 | 4 | I found this year one of the easiest so far. 5 | Not sure if it's just that I've done this 10 times 6 | now, if its because it was only 12 days, or if it 7 | was genuinely easier. 8 | 9 | Missing the global leaderboard was a bit sad I think. 10 | I get why he removed it but I feel like we lost 11 | a bit of the community around it. 12 | 13 | Total runtime is around `500ms`. 14 | 15 | ## Leaderboard 16 | ![leaderboard](../imgs/leaderboard-2025.png) 17 | -------------------------------------------------------------------------------- /2015/src/10.clj: -------------------------------------------------------------------------------- 1 | (def input "3113322113") 2 | 3 | (defn expand 4 | ([res [c & s]] 5 | (if (not c) 6 | (->> res reverse (apply str)) 7 | (if (not s) 8 | (recur (conj res "1" c) s) 9 | (let [[same rest] (split-with #(= c %) s)] 10 | (recur (conj res (inc (count same)) c) rest))))) 11 | ([s] (expand nil s))) 12 | 13 | (defn repeated-expand [s n] 14 | (nth (iterate expand s) n)) 15 | 16 | (->> 40 (repeated-expand input) count (println "Part one:")) 17 | (->> 50 (repeated-expand input) count (println "Part two:")) 18 | -------------------------------------------------------------------------------- /2022/src/bin/10.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(10)] 2 | fn main(input: &str) -> (i32, String) { 3 | let (mut x, mut cycle, mut p1, mut p2) = (1i32, 0i32, 0, String::with_capacity(256)); 4 | macro_rules! tick { 5 | () => { 6 | if cycle % 40 == 0 { p2.push('\n'); } 7 | p2.push(if (x - cycle % 40).abs() < 2 {'█'} else {' '}); 8 | cycle += 1; 9 | if (cycle - 20) % 40 == 0 { p1 += cycle * x } 10 | }; 11 | } 12 | for l in input.lines() { 13 | tick!(); 14 | if l.len() > 4 { 15 | tick!(); 16 | x += l[5..].parse::().unwrap(); 17 | } 18 | } 19 | (p1, p2) 20 | } 21 | -------------------------------------------------------------------------------- /2018/src/14.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | 3 | @aoc.main('14') 4 | def main(indata: str): 5 | r = int(indata) 6 | res = [int(c) for c in str(r)] 7 | e1,e2,recipes = 0,1,[3,7] 8 | while recipes[-len(res):] != res: 9 | x = recipes[e1] + recipes[e2] 10 | if x > 9: 11 | recipes.append(1) 12 | if recipes[-len(res):] == res: 13 | break 14 | recipes.append(x % 10) 15 | e1 = (e1 + recipes[e1] + 1) % len(recipes) 16 | e2 = (e2 + recipes[e2] + 1) % len(recipes) 17 | return ''.join(str(i) for i in recipes[r:r+10]),len(recipes) - len(res) 18 | 19 | if __name__ == "__main__": 20 | main() 21 | -------------------------------------------------------------------------------- /2022/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2022 :christmas_tree: 2 | Solutions to all 25 AoC 2022 in Rust :crab: Using less than 100 lines per day, and with a total runtime of `0.82` seconds. 3 | 4 | I really liked 2022, it was a good AoC year. It was the year I finally reached my goal of hitting the top 100, 25th place on [day 19](./src/bin/19.rs)! 5 | 6 | For execution time of each solution, see [timings.md](./timings.md). 7 | 8 | ## Leaderboard 9 | This year I finally reached my goal of ending up on the leaderboard. On [day 19, part 1](./src/bin/19.rs) I got 25th in the world. 10 | 11 | ![leaderboard](../imgs/leaderboard-2022.png) 12 | -------------------------------------------------------------------------------- /2022/src/bin/04.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(04)] 2 | fn main(input: &str) -> (usize, usize) { 3 | let pairs = input.lines().filter_map(|l| { 4 | let (x,y) = l.split_once(',')?; 5 | let (a,b) = x.split_once('-')?; 6 | let (c,d) = y.split_once('-')?; 7 | Some((a.parse().ok()?, b.parse().ok()?, c.parse().ok()?, d.parse().ok()?)) 8 | }).collect::>(); 9 | let p1 = pairs.iter() 10 | .filter(|(x1,y1,x2,y2)| (x1 <= x2 && y1 >= y2) || (x2 <= x1 && y2 >= y1)) 11 | .count(); 12 | let p2 = pairs.iter() 13 | .filter(|(x1,y1,x2,y2)| x2 <= y1 && x1 <= y2) 14 | .count(); 15 | (p1, p2) 16 | } 17 | -------------------------------------------------------------------------------- /2025/src/bin/03.rs: -------------------------------------------------------------------------------- 1 | fn max_batteries(xs: &[u8], l: usize) -> usize { 2 | let mut r = String::new(); 3 | let mut j = 0; 4 | for i in 0..l { 5 | j = (j..xs.len() - l + i + 1).max_by_key(|&x| (xs[x], usize::MAX - x)).unwrap(); 6 | r.push(xs[j] as char); 7 | j += 1; 8 | } 9 | r.parse().unwrap() 10 | } 11 | 12 | #[aoc::main(03)] 13 | fn main(input: &str) -> (usize, usize) { 14 | let (mut p1, mut p2) = (0, 0); 15 | for l in input.split('\n') { 16 | p1 += max_batteries(l.as_bytes(), 2); 17 | p2 += max_batteries(l.as_bytes(), 12); 18 | } 19 | (p1, p2) 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | inputs/ 2 | 3 | # rust things 4 | target/ 5 | Cargo.lock 6 | **/*.rs.bk 7 | 8 | # python things 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # haskell things 14 | dist 15 | dist-* 16 | cabal-dev 17 | *.o 18 | *.hi 19 | *.hie 20 | *.chi 21 | *.chs.h 22 | *.dyn_o 23 | *.dyn_hi 24 | .hpc 25 | .hsenv 26 | .cabal-sandbox/ 27 | cabal.sandbox.config 28 | *.prof 29 | *.aux 30 | *.hp 31 | *.eventlog 32 | .stack-work/ 33 | cabal.project.local 34 | cabal.project.local~ 35 | .HTF/ 36 | .ghc.environment.* 37 | 0? 38 | 1? 39 | 2? 40 | 41 | # ocaml things 42 | _build/ 43 | *.byte 44 | *.native 45 | 46 | # clj things 47 | .cpcache 48 | -------------------------------------------------------------------------------- /2021/src/bin/07.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn gauss(n: i32) -> i32 { n * (n + 1) / 2 } 4 | 5 | #[aoc::main(07)] 6 | fn main(input: &str) -> (i32,i32) { 7 | let ints = input.split(',') 8 | .map(|s| s.parse::().unwrap()) 9 | .collect::>(); 10 | let (&min,&max) = ints.iter() 11 | .minmax() 12 | .into_option() 13 | .unwrap(); 14 | let p1 = (min..=max) 15 | .map(|x| ints.iter().map(|i| (x - i).abs()).sum::()) 16 | .min() 17 | .unwrap(); 18 | let p2 = (min..=max) 19 | .map(|x| ints.iter().map(|i| gauss((x - i).abs())).sum::()) 20 | .min() 21 | .unwrap(); 22 | (p1,p2) 23 | } 24 | -------------------------------------------------------------------------------- /2018/src/03.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from itertools import product 3 | from collections import defaultdict 4 | import re 5 | 6 | @aoc.main('03') 7 | def main(indata: str) -> tuple[int,int]: 8 | squares = [[int(i) for i in re.findall("\d+",l)] for l in indata.split('\n')] 9 | points = defaultdict[tuple[int,int],int](int) 10 | for _,x,y,h,w in squares: 11 | for p in product(range(x,x+h), range(y,y+w)): 12 | points[p] += 1 13 | for p2,x,y,h,w in squares: 14 | if all(points[p] == 1 for p in product(range(x,x+h), range(y,y+w))): 15 | break 16 | return sum(c > 1 for c in points.values()), p2 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /2018/src/02.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from collections import Counter 3 | from itertools import combinations 4 | 5 | def part1(ids: list[str]) -> int: 6 | c = Counter[int]() 7 | for l in ids: 8 | c.update(set(Counter(l).values())) 9 | return c[2]*c[3] 10 | 11 | def part2(ids: list[str]) -> str: 12 | for a,b in combinations(ids,r=2): 13 | ans = [c1 for c1,c2 in zip(a,b) if c1 == c2] 14 | if len(ans)+1 == len(a): 15 | return ''.join(ans) 16 | assert False 17 | 18 | @aoc.main('02') 19 | def main(indata: str) -> tuple[int,str]: 20 | ids = indata.split('\n') 21 | return part1(ids), part2(ids) 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /2024/src/bin/01.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | #[aoc::main(01)] 4 | fn main(input: &str) -> (i64, i64) { 5 | let (mut l1, mut l2): (Vec<_>, Vec<_>) = input.lines().map(|l| { 6 | let (a, b) = l.split_once(" ").unwrap(); 7 | (a.parse::().unwrap(), b.parse::().unwrap()) 8 | }).unzip(); 9 | l1.sort(); 10 | l2.sort(); 11 | 12 | let mut count = HashMap::new(); 13 | for &b in &l2 { 14 | *count.entry(b).or_insert(0) += 1; 15 | } 16 | 17 | let p1 = l1.iter().zip(&l2).map(|(a, b)| (a - b).abs()).sum(); 18 | let p2 = l1.iter().map(|&a| a * count.get(&a).unwrap_or(&0)).sum(); 19 | (p1, p2) 20 | } 21 | -------------------------------------------------------------------------------- /2015/src/04.clj: -------------------------------------------------------------------------------- 1 | (import 'java.security.MessageDigest 2 | 'java.math.BigInteger) 3 | 4 | (def leading-5-zeros (BigInteger. "000001000000000000000000000000000" 16)) 5 | (def leading-6-zeros (BigInteger. "000000100000000000000000000000000" 16)) 6 | 7 | ; from https://gist.github.com/jizhang/4325757 8 | (defn md5 [s] 9 | (->> s 10 | .getBytes 11 | (.digest (MessageDigest/getInstance "MD5")) 12 | (BigInteger. 1))) 13 | 14 | (defn find-hash [n] 15 | (->> (iterate inc 0) 16 | (filter #(->> % (str "ckczppom") md5 (> n))) 17 | first)) 18 | 19 | (->> leading-5-zeros find-hash (println "Part two:")) 20 | (->> leading-6-zeros find-hash (println "Part one:")) 21 | -------------------------------------------------------------------------------- /2015/src/24.clj: -------------------------------------------------------------------------------- 1 | (require '[clojure.math.combinatorics :as combo]) 2 | 3 | (def input '(1 3 5 11 13 17 19 23 29 31 37 41 43 47 53 59 67 71 73 79 83 89 97 101 103 107 109 113)) 4 | (def total-sum (reduce + input)) 5 | 6 | (defn valid-lists [target numbers len] 7 | (->> len 8 | (combo/combinations numbers) 9 | (filter #(= (reduce + %) target)))) 10 | 11 | (defn find-min [numbers parts] 12 | (->> (iterate inc 1) 13 | (map #(valid-lists (/ total-sum parts) numbers %)) 14 | (filter not-empty) 15 | first 16 | (map #(reduce * %)) 17 | (apply min))) 18 | 19 | (->> 3 (find-min input) (println "Part one:")) 20 | (->> 4 (find-min input) (println "Part two:")) 21 | -------------------------------------------------------------------------------- /2015/src/20.clj: -------------------------------------------------------------------------------- 1 | (def input 34000000) 2 | 3 | (defn divisors 4 | ([ds i n] 5 | (if (> (* i i) n) 6 | ds 7 | (if (not= 0 (rem n i)) 8 | (recur ds (inc i) n) 9 | (recur (conj ds i (quot n i)) (inc i) n)))) 10 | ([n] (divisors #{} 1 n))) 11 | 12 | (defn presents-p1 [n] 13 | (->> n divisors (reduce +) (* 10))) 14 | 15 | (defn presents-p2 [n] 16 | (->> n divisors (filter #(<= (quot n %) 50)) (reduce +) (* 11))) 17 | 18 | (defn find-first-house [target f] 19 | (->> (iterate inc 1) (filter #(> (f %) target)) first)) 20 | 21 | (->> presents-p1 (find-first-house input) (println "Part one:")) 22 | (->> presents-p2 (find-first-house input) (println "Part two:")) 23 | -------------------------------------------------------------------------------- /2024/src/bin/03.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | #[aoc::main(03)] 4 | fn main(input: &str) -> (usize, usize) { 5 | let r = Regex::new(r"(mul\(\d+,\d+\)|do(n't)?\(\))").unwrap(); 6 | let (mut p1, mut p2, mut ok) = (0, 0, true); 7 | for x in r.find_iter(input) { 8 | match x.as_str() { 9 | "do()" => ok = true, 10 | "don't()" => ok = false, 11 | x => { 12 | let (a, b) = x[4..x.len()-1].split_once(',').unwrap(); 13 | let i = a.parse::().unwrap() * b.parse::().unwrap(); 14 | p1 += i; 15 | if ok { p2 += i } 16 | } 17 | } 18 | } 19 | (p1, p2) 20 | } 21 | -------------------------------------------------------------------------------- /2015/src/12.clj: -------------------------------------------------------------------------------- 1 | (require '[cheshire.core :as cc]) 2 | 3 | (def json (->> "inputs/12.in" slurp cc/parse-string)) 4 | 5 | (defn sum-json-p1 [o] 6 | (cond 7 | (map? o) (->> o vals (map sum-json-p1) (reduce +)) 8 | (vector? o) (->> o (map sum-json-p1) (reduce +)) 9 | (integer? o) o 10 | :else 0)) 11 | 12 | (defn sum-json-p2 [o] 13 | (cond 14 | (map? o) 15 | (if (->> o vals (some #(= "red" %))) 16 | 0 17 | (->> o vals (map sum-json-p2) (reduce +))) 18 | (vector? o) (->> o (map sum-json-p2) (reduce +)) 19 | (integer? o) o 20 | :else 0)) 21 | 22 | (->> json sum-json-p1 (println "Part one:")) 23 | (->> json sum-json-p2 (println "Part two:")) 24 | -------------------------------------------------------------------------------- /2023/src/bin/09.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | #[aoc::main(09)] 4 | fn main(input: &str) -> (isize, isize) { 5 | let (mut p1, mut p2) = (0,0); 6 | for l in input.split('\n') { 7 | let xs = l.split_whitespace() 8 | .map(|w| w.parse::().unwrap()) 9 | .collect::>(); 10 | let mut v = vec![xs]; 11 | while v[v.len()-1].iter().any(|&x| x != 0) { 12 | let xs = v[v.len()-1].iter() 13 | .tuple_windows() 14 | .map(|(a,b)| b - a) 15 | .collect(); 16 | v.push(xs); 17 | } 18 | let mut b = 0; 19 | for xs in v.iter().rev() { 20 | p1 += xs[xs.len()-1]; 21 | b = xs[0] - b; 22 | } 23 | p2 += b; 24 | } 25 | (p1, p2) 26 | } 27 | -------------------------------------------------------------------------------- /2018/src/25.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | import re 3 | 4 | def dist(s1: list[int], s2: list[int]) -> int: 5 | return sum(abs(a-b) for a,b in zip(s1,s2)) 6 | 7 | def pop_constellation(stars: list[list[int]], s: list[int]): 8 | if s not in stars: 9 | return 10 | stars.remove(s) 11 | for s2 in [s2 for s2 in stars if dist(s,s2) < 4]: 12 | pop_constellation(stars, s2) 13 | 14 | @aoc.main('25') 15 | def main(indata: str) -> tuple[int,str]: 16 | stars = [[int(i) for i in re.findall("-?\d+", l)] for l in indata.split('\n')] 17 | constellations = 0 18 | while stars: 19 | pop_constellation(stars, stars[0]) 20 | constellations += 1 21 | return constellations, '🎄' 22 | 23 | if __name__ == '__main__': 24 | main() 25 | -------------------------------------------------------------------------------- /2018/src/aoc.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import Any, Callable, Iterable 3 | from pathlib import Path 4 | 5 | INPUT_DIR = (Path(__file__) / '..' / '..' / 'inputs').resolve() 6 | AocSolution = Callable[[str], Iterable[Any]] 7 | 8 | def main(day: str) -> Callable[[AocSolution], Callable[[],None]]: 9 | def decorator(solver: AocSolution) -> Callable[[],None]: 10 | def timer() -> None: 11 | indata = (INPUT_DIR / f'{day}.in').read_text() 12 | start = time.time() 13 | p1, p2 = solver(indata.rstrip('\n')) 14 | elapsed = int((time.time() - start) * 1000) 15 | print(f"Part 1: {p1}") 16 | print(f"Part 2: {p2}") 17 | print(f"Time: {elapsed}ms") 18 | return timer 19 | return decorator 20 | -------------------------------------------------------------------------------- /2018/src/09.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | import re 3 | from collections import defaultdict, deque 4 | 5 | def play(players: int, rounds: int) -> int: 6 | marbles, scores = deque([0]), defaultdict[int,int](int) 7 | for i in range(1, rounds+1): 8 | if i % 23 == 0: 9 | marbles.rotate(7) 10 | scores[i % players] += i + marbles.pop() 11 | marbles.rotate(-1) 12 | else: 13 | marbles.rotate(-1) 14 | marbles.append(i) 15 | return max(scores.values()) 16 | 17 | @aoc.main('09') 18 | def main(indata: str) -> tuple[int,int]: 19 | players, rounds = [int(i) for i in re.findall('\d+', indata)] 20 | p1 = play(players, rounds) 21 | p2 = play(players, rounds*100) 22 | return p1,p2 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /2023/src/bin/04.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(04)] 2 | fn main(input: &str) -> (usize, usize) { 3 | let mut p1 = 0; 4 | let mut copies = vec![1; input.split('\n').count()]; 5 | for (i, l) in input.split('\n').enumerate() { 6 | let (_, rest) = l.split_once(": ").unwrap(); 7 | let (wanted, got) = rest.split_once(" | ").unwrap(); 8 | let wanted = wanted.split_whitespace() 9 | .map(|w| w.parse::().unwrap()) 10 | .collect::>(); 11 | let won = got.split_whitespace() 12 | .map(|w| w.parse::().unwrap()) 13 | .filter(|c| wanted.contains(c)) 14 | .count(); 15 | p1 += if won != 0 {1 << (won - 1)} else {0}; 16 | for j in 0..won { 17 | copies[i+j+1] += copies[i]; 18 | } 19 | } 20 | (p1, copies.iter().sum()) 21 | } 22 | -------------------------------------------------------------------------------- /2015/src/08.clj: -------------------------------------------------------------------------------- 1 | (require '[clojure.string :as str]) 2 | 3 | (def lines (->> "inputs/08.in" slurp str/split-lines)) 4 | 5 | (defn chars-in-p1 6 | ([c [a & s]] 7 | (if (not a) 8 | c 9 | (let [n 10 | (cond 11 | (not= \\ a) 0 12 | (= (first s) \x) 3 13 | :else 1)] 14 | (recur (inc c) (drop n s))))) 15 | ([s] (chars-in-p1 -2 s))) 16 | 17 | (defn chars-in-p2 18 | ([c [a & s]] 19 | (case a 20 | nil (+ 2 c) 21 | \" (recur (inc c) s) 22 | \\ (recur (inc c) s) 23 | (recur c s))) 24 | ([s] (chars-in-p2 0 s))) 25 | 26 | (->> lines (map chars-in-p1) (reduce +) (- (->> lines (map count) (reduce +))) (println "Part one:")) 27 | (->> lines (map chars-in-p2) (reduce +) (println "Part two:")) 28 | -------------------------------------------------------------------------------- /2020/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use once_cell::sync::Lazy; 3 | use regex::Regex; 4 | 5 | static MS_REGEX: Lazy:: = Lazy::new(|| Regex::new(r"Time: (\d+)ms").unwrap()); 6 | 7 | fn extract_time(s: &str) -> u32 { 8 | let capture = MS_REGEX.captures_iter(&s).next().unwrap(); 9 | capture[1].parse().unwrap() 10 | } 11 | 12 | fn main() { 13 | let total_time = (1..=25).map(|day_num| { 14 | let day = format!("{:0>2}", day_num); 15 | let cmd = Command::new("cargo") 16 | .args(&["run", "--release", "--bin", &day]) 17 | .output() 18 | .unwrap(); 19 | let output = String::from_utf8(cmd.stdout).unwrap(); 20 | println!("Day {}:\n{}", day, output); 21 | extract_time(&output) 22 | }).sum::(); 23 | println!("Total time: {}ms", total_time); 24 | } 25 | -------------------------------------------------------------------------------- /2021/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use once_cell::sync::Lazy; 3 | use regex::Regex; 4 | 5 | static MS_REGEX: Lazy:: = Lazy::new(|| Regex::new(r"Time: (\d+)ms").unwrap()); 6 | 7 | fn extract_time(s: &str) -> u32 { 8 | let capture = MS_REGEX.captures_iter(s).next().unwrap(); 9 | capture[1].parse().unwrap() 10 | } 11 | 12 | fn main() { 13 | let total_time = (1..=25).map(|day_num| { 14 | let day = format!("{:0>2}", day_num); 15 | let cmd = Command::new("cargo") 16 | .args(&["run", "--release", "--bin", &day]) 17 | .output() 18 | .unwrap(); 19 | let output = String::from_utf8(cmd.stdout).unwrap(); 20 | println!("Day {}:\n{}", day, output); 21 | extract_time(&output) 22 | }).sum::(); 23 | println!("Total time: {}ms", total_time); 24 | } 25 | -------------------------------------------------------------------------------- /2022/src/bin/03.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn value(c: u8) -> usize { 4 | match c { 5 | b'a'..=b'z' => c as usize - b'a' as usize + 1, 6 | b'A'..=b'Z' => c as usize - b'A' as usize + 27, 7 | _ => unreachable!(), 8 | } 9 | } 10 | 11 | fn same_chars(a: &[u8], b: &[u8]) -> Vec { 12 | a.iter().copied().filter(|c| b.contains(c)).collect() 13 | } 14 | 15 | #[aoc::main(03)] 16 | fn main(input: &str) -> (usize, usize) { 17 | let lines = input.lines().map(str::as_bytes).collect::>(); 18 | let p1 = lines.iter() 19 | .map(|l| same_chars(&l[..l.len()/2], &l[l.len()/2..])) 20 | .map(|c| value(c[0])) 21 | .sum(); 22 | let p2 = lines.iter() 23 | .tuples() 24 | .map(|(a,b,c)| same_chars(a, &same_chars(b, c))) 25 | .map(|c| value(c[0])) 26 | .sum(); 27 | (p1, p2) 28 | } 29 | -------------------------------------------------------------------------------- /2024/src/bin/13.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn solve(x1: i64, x2: i64, y1: i64, y2: i64, z1: i64, z2: i64) -> i64 { 4 | let b = (z2 * x1 - z1 * x2) / (y2 * x1 - y1 * x2); 5 | let a = (z1 - b * y1) / x1; 6 | if (x1 * a + y1 * b, x2 * a + y2 * b) != (z1, z2) { 7 | return 0; 8 | } 9 | a * 3 + b 10 | } 11 | 12 | #[aoc::main(13)] 13 | fn main(input: &str) -> (i64, i64) { 14 | let xs = input 15 | .split(|c: char| !c.is_ascii_digit()) 16 | .filter(|w| !w.is_empty()) 17 | .map(|w| w.parse().unwrap()) 18 | .tuples(); 19 | let (mut p1, mut p2) = (0, 0); 20 | for (x1, x2, y1, y2, z1, z2) in xs { 21 | p1 += solve(x1, x2, y1, y2, z1, z2); 22 | p2 += solve(x1, x2, y1, y2, z1 + 10000000000000, z2 + 10000000000000); 23 | } 24 | (p1, p2) 25 | } 26 | -------------------------------------------------------------------------------- /2023/src/bin/01.rs: -------------------------------------------------------------------------------- 1 | const STR_DIGITS: &[&[u8]] = &[b"one", b"two", b"three", b"four", b"five", b"six", b"seven", b"eight", b"nine"]; 2 | 3 | fn digit_sum(w: &[u8], part_two: bool) -> usize { 4 | let mut digits = (0..w.len()).filter_map(|i| match w[i] { 5 | b'0'..=b'9' => Some((w[i] - b'0') as usize), 6 | _ if part_two => STR_DIGITS.iter() 7 | .enumerate() 8 | .find_map(|(di, d)| w[i..].starts_with(d).then_some(di + 1)), 9 | _ => None 10 | }); 11 | let a = digits.next().unwrap(); 12 | let b = digits.last().unwrap_or(a); 13 | a * 10 + b 14 | } 15 | 16 | #[aoc::main(01)] 17 | fn main(input: &str) -> (usize, usize) { 18 | let (mut p1, mut p2) = (0,0); 19 | for l in input.split('\n') { 20 | p1 += digit_sum(l.as_bytes(), false); 21 | p2 += digit_sum(l.as_bytes(), true); 22 | } 23 | (p1, p2) 24 | } 25 | -------------------------------------------------------------------------------- /2025/src/bin/12.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(12)] 2 | fn main(input: &str) -> (usize, char) { 3 | let parts = input.split("\n\n").collect::>(); 4 | let sizes = parts[0..parts.len()-1].iter() 5 | .map(|p| p.bytes().filter(|&b| b == b'#').count()) 6 | .collect::>(); 7 | let mut p1 = 0; 8 | for region in parts[parts.len()-1].split('\n') { 9 | let (x, rest) = region.split_once(": ").unwrap(); 10 | let (a, b) = x.split_once('x').unwrap(); 11 | let area = a.parse::().unwrap() * b.parse::().unwrap(); 12 | let min_area = rest.split(' ').zip(&sizes) 13 | .map(|(n, size)| n.parse::().unwrap() * size) 14 | .sum::(); 15 | if min_area as f64 * 1.3 < area { 16 | p1 += 1; 17 | } 18 | } 19 | (p1, '🎄') 20 | } 21 | -------------------------------------------------------------------------------- /2017/src/15.hs: -------------------------------------------------------------------------------- 1 | import Aoc 2 | import Data.Bits 3 | 4 | input = (591, 393) 5 | 6 | judgement :: Int -> Int -> Int 7 | judgement i j 8 | | (.&.) (xor i j) 0xffff == 0 = 1 9 | | otherwise = 0 10 | 11 | nextVal :: Int -> Int -> Int -> Int 12 | nextVal i d factor 13 | | next `rem` d == 0 = next 14 | | otherwise = nextVal next d factor 15 | where next = (i * factor) `rem` 2147483647 16 | 17 | numAccepted :: (Int,Int) -> (Int,Int) -> Int -> Int -> Int 18 | numAccepted _ _ 0 c = c 19 | numAccepted (d1,d2) (i,j) round c = numAccepted (d1,d2) nextValues (round-1) (c + judgement i j) 20 | where nextValues = (nextVal i d1 16807, nextVal j d2 48271) 21 | 22 | solveParts :: Int -> (Int,Int) 23 | solveParts _ = (numAccepted (1,1) input 40000000 0, numAccepted (4,8) input 5000000 0) 24 | 25 | main = Aoc.timer solveParts 26 | -------------------------------------------------------------------------------- /2024/src/bin/02.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn is_safe(x: &[i64]) -> bool { 4 | let mut ok = false; 5 | ok |= x.iter().tuple_windows().all(|(a, b)| a < b); 6 | ok |= x.iter().tuple_windows().all(|(a, b)| a > b); 7 | ok && x.iter().tuple_windows().all(|(a, b)| (1..=3).contains(&(a - b).abs())) 8 | } 9 | 10 | fn is_any_safe(x: &[i64]) -> bool { 11 | (0..x.len()).any(|i| { 12 | let mut y = x.to_vec(); 13 | y.remove(i); 14 | is_safe(&y) 15 | }) 16 | } 17 | 18 | #[aoc::main(02)] 19 | fn main(input: &str) -> (usize, usize) { 20 | let (mut p1, mut p2) = (0, 0); 21 | for l in input.lines() { 22 | let x = l.split(' ').map(|w| w.parse().unwrap()).collect::>(); 23 | if is_safe(&x) { p1 += 1 } 24 | if is_any_safe(&x) { p2 += 1 } 25 | } 26 | (p1, p2) 27 | } 28 | -------------------------------------------------------------------------------- /2024/src/bin/25.rs: -------------------------------------------------------------------------------- 1 | fn fits(l: &[&[u8]], k: &[&[u8]]) -> bool { 2 | for r in 0..l.len() { 3 | for c in 0..l[0].len() { 4 | if l[r][c] == b'#' && k[r][c] == b'#' { 5 | return false; 6 | } 7 | } 8 | } 9 | true 10 | } 11 | 12 | #[aoc::main(25)] 13 | fn main(input: &str) -> (usize, char) { 14 | let mut locks = Vec::new(); 15 | let mut keys = Vec::new(); 16 | for s in input.split("\n\n") { 17 | let x = s.lines().map(|s| s.as_bytes()).collect::>(); 18 | if x[0][0] == b'#' { 19 | locks.push(x); 20 | } else { 21 | keys.push(x); 22 | } 23 | } 24 | let mut p1 = 0; 25 | for l in &locks { 26 | for k in &keys { 27 | p1 += fits(&l, &k) as usize; 28 | } 29 | } 30 | (p1, '🎄') 31 | } 32 | -------------------------------------------------------------------------------- /2025/src/bin/02.rs: -------------------------------------------------------------------------------- 1 | fn invalid(s: &[u8], k: usize) -> bool { 2 | if s.len() % k != 0 { 3 | return false; 4 | } 5 | (k..s.len()).step_by(k).all(|x| s[x..x+k] == s[0..k]) 6 | } 7 | 8 | #[aoc::main(02)] 9 | fn main(input: &str) -> (usize, usize) { 10 | let (mut p1, mut p2) = (0, 0); 11 | for l in input.split(',') { 12 | let (a, b) = l.split_once('-') 13 | .map(|(a,b)| (a.parse::().unwrap(), b.parse::().unwrap())) 14 | .unwrap(); 15 | for i in a..=b { 16 | let s = i.to_string(); 17 | if s.len() % 2 == 0 && invalid(s.as_bytes(), s.len() / 2) { 18 | p1 += i; 19 | } 20 | if (1..=s.len() / 2).any(|k| invalid(s.as_bytes(), k)) { 21 | p2 += i; 22 | } 23 | } 24 | } 25 | (p1, p2) 26 | } 27 | -------------------------------------------------------------------------------- /2018/src/23.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | import re 3 | from z3 import If, Int, Optimize 4 | 5 | def part1(bots: list[list[int]]) -> int: 6 | x,y,z,r = max(bots, key=lambda b: b[3]) 7 | return sum(abs(x1-x) + abs(y1-y) + abs(z1-z) <= r for x1,y1,z1,_ in bots) 8 | 9 | def part2(bots: list[list[int]]) -> int: 10 | def abs(x): 11 | return If(x >= 0,x,-x) 12 | x,y,z,opt = Int("x"), Int("y"), Int("z"), Optimize() 13 | opt.maximize(sum(If(abs(x-x1) + abs(y-y1) + abs(z-z1) <= r, 1, 0) for x1,y1,z1,r in bots)) 14 | opt.minimize(abs(x) + abs(y) + abs(z)) 15 | opt.check() 16 | return opt.model().eval(abs(x) + abs(y) + abs(z)) 17 | 18 | @aoc.main('23') 19 | def main(indata: str) -> tuple[int,int]: 20 | bots = [[int(i) for i in re.findall("-?\d+", l)] for l in indata.split('\n')] 21 | return part1(bots), part2(bots) 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /2019/README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2019 2 | My solutions to all [Advent of Code 2019](https://adventofcode.com/2019) problems. I did them in Rust to get more familiar with the language. This was the first year I participated. The parts with the [IntCoder](./src/intcoder.rs), simulating a custom CPU-architecture, were so cool and opened up many interesting puzzle-possibilities. I learned a ton and got a lot better at Rust! 3 | 4 | Huge thanks to everyone who makes AoC possible! 5 | 6 | ![all stars](../imgs/all-stars-2019.png) 7 | 8 | ## Leaderboard 9 | I did not attempt to get high leaderboard scores since, in my timezone, I would have to get up very early. I still managed to get around top 1-2k on a lot of the later ones and even `774th` on [one star](./src/bin/22.rs). Finished most of them the day they came out. 10 | 11 | ![leaderboard](../imgs/leaderboard-2019.png) 12 | -------------------------------------------------------------------------------- /2023/src/bin/06.rs: -------------------------------------------------------------------------------- 1 | // d = (t - w) * w => w = (t +- sqrt(t^2 - 4d)) / 2 2 | fn f(t: usize, d: usize) -> usize { 3 | let diff = ((t*t - 4*d) as f64).sqrt(); 4 | let l = (t as f64 - diff) / 2.0; 5 | let h = (t as f64 + diff) / 2.0; 6 | (h.floor() - l.ceil()) as usize + 1 7 | } 8 | 9 | #[aoc::main(06)] 10 | fn main(input: &str) -> (usize, usize) { 11 | let (l1, l2) = input.split_once('\n').unwrap(); 12 | let times = l1.split_whitespace().skip(1).map(|w| w.parse().unwrap()).collect::>(); 13 | let dists = l2.split_whitespace().skip(1).map(|w| w.parse().unwrap()).collect::>(); 14 | let time2 = l1.split_whitespace().skip(1).collect::().parse().unwrap(); 15 | let dist2 = l2.split_whitespace().skip(1).collect::().parse().unwrap(); 16 | let p1 = times.iter().zip(dists).map(|(&t, d)| f(t, d)).product(); 17 | (p1, f(time2, dist2)) 18 | } 19 | -------------------------------------------------------------------------------- /fetch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | fail() { echo "$@" && exit 1; } 5 | 6 | if [[ $# != 2 ]]; then 7 | fail "usage: $0 YEAR DAY" 8 | fi 9 | year=$1 10 | day=$2 11 | 12 | if [[ ! "$year" =~ ^20(1[5-9]|2[0-9])$ ]]; then 13 | fail "Not a valid year: $year" 14 | fi 15 | if [[ ! "$day" =~ ^(0[1-9]|1[0-9]|2[0-5])$ ]]; then 16 | fail "Not a valid day: $day" 17 | fi 18 | if [[ -z "${AOC_SESSION-""}" ]]; then 19 | fail "\$AOC_SESSION not set" 20 | fi 21 | 22 | TMPFILE=$(mktemp) 23 | trap 'rm -f "$TMPFILE"' EXIT 24 | 25 | curl "https://adventofcode.com/$year/day/${day#0}/input" \ 26 | -s --fail-with-body --cookie "session=$AOC_SESSION" \ 27 | -A "Bash script at $(git remote -v | awk 'NR==1{print $2}')" \ 28 | | tee "$TMPFILE" 29 | 30 | outdir=$(realpath "$(dirname "$0")")/$year/inputs 31 | mkdir -p "$outdir" 32 | mv "$TMPFILE" "$outdir/$day.in" 33 | -------------------------------------------------------------------------------- /2021/src/bin/03.rs: -------------------------------------------------------------------------------- 1 | fn max_bit(nums: &[u32], bit: usize) -> u32 { 2 | let mut c = [0,0]; 3 | for &x in nums { 4 | c[(x as usize >> bit) & 1] += 1 5 | } 6 | (c[1] >= c[0]) as u32 7 | } 8 | 9 | fn part1(nums: &[u32]) -> u32 { 10 | let x = (0..12).map(|i| max_bit(nums, i) << i).sum::(); 11 | x * (!x & 0xfff) 12 | } 13 | 14 | fn part2(nums: &[u32], oxygen: u32) -> u32 { 15 | let mut nums = nums.to_vec(); 16 | for i in (0..12).rev() { 17 | let keep = max_bit(&nums, i) ^ oxygen; 18 | nums.retain(|x| (x>>i) & 1 == keep); 19 | if nums.len() == 1 { break } 20 | } 21 | nums[0] 22 | } 23 | 24 | #[aoc::main(03)] 25 | fn main(input: &str) -> (u32,u32) { 26 | let input = input.lines() 27 | .map(|l| u32::from_str_radix(l,2).unwrap()) 28 | .collect::>(); 29 | let p1 = part1(&input); 30 | let p2 = part2(&input, 1) * part2(&input, 0); 31 | (p1,p2) 32 | } 33 | -------------------------------------------------------------------------------- /2020/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2020 :crab: 2 | Solutions to all 25 AoC 2020 problems in Rust :crab: 3 | 4 | This year I went all out and went up at `5:50` every single day (except day 5 when I overslept :cry:) to do the puzzles. I tried to aim for relatively high scores on the leaderboard and got as high as [116 on day 20](./src/bin/20.rs), however competing against Python with Rust is difficult. Solved most problems in less than one hour though, and consistently hit top 1000 on a lot of the problems. This year was cool but _a lot_ easier than 2019. 5 | 6 | See [notes.md](./notes.md) for some thoughts I wrote down after completing each day. 7 | 8 | See [timings.md](./timings.md) for the execution time of each solution. 9 | 10 | Thanks for another amazing year of AoC [@ericwastl](https://twitter.com/ericwastl) :christmas_tree: 11 | 12 | ## Leaderboard 13 | ![leaderboard](../imgs/leaderboard-2020.png) 14 | -------------------------------------------------------------------------------- /2018/src/10.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | import re 3 | 4 | def print_grid(stars: list[list[int]]) -> str | None: 5 | ymin, ymax = min(s[1] for s in stars), max(s[1] for s in stars) 6 | if ymax-ymin > 10: 7 | return None 8 | xmin, xmax = min(s[0] for s in stars), max(s[0] for s in stars) 9 | grid = '\n' 10 | for y in range(ymin,ymax+1): 11 | grid += '\n' 12 | for x in range(xmin,xmax+1): 13 | grid += '█' if any(s[0]==x and s[1]==y for s in stars) else ' ' 14 | return grid 15 | 16 | @aoc.main('10') 17 | def main(indata: str) -> tuple[str,int]: 18 | stars = [[int(i) for i in re.findall("-?\d+", l)] for l in indata.split('\n')] 19 | for steps in range(1000000): 20 | for s in stars: 21 | s[0] += s[2] 22 | s[1] += s[3] 23 | steps += 1 24 | if grid := print_grid(stars): 25 | return grid, steps 26 | assert False 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /2023/src/bin/02.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(02)] 2 | fn main(input: &str) -> (usize, usize) { 3 | let games = input.split('\n').map(|l| { 4 | let (id, game) = l.trim_start_matches("Game ").split_once(':').unwrap(); 5 | let colors = game.split([';', ',']).map(|w| { 6 | let (n, color) = w.trim().split_once(' ').unwrap(); 7 | let c = "rgb".bytes().position(|c| c == color.as_bytes()[0]).unwrap(); 8 | (n.parse().unwrap(), c) 9 | }).collect::>(); 10 | (id.parse().unwrap(), colors) 11 | }).collect::>(); 12 | 13 | let p1 = games.iter() 14 | .filter(|(_, actions)| actions.iter().all(|&(n,c)| n <= [12, 13, 14][c])) 15 | .map(|(id,_)| id) 16 | .sum(); 17 | 18 | let p2 = games.iter().map(|(_,actions)| { 19 | let mut a = [0; 3]; 20 | for &(n, c) in actions { 21 | a[c] = a[c].max(n); 22 | } 23 | a[0] * a[1] * a[2] 24 | }).sum(); 25 | 26 | (p1, p2) 27 | } 28 | -------------------------------------------------------------------------------- /2024/src/bin/05.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::{HashMap, HashSet}; 2 | 3 | #[aoc::main(05)] 4 | fn main(input: &str) -> (usize, usize) { 5 | let (s1, s2) = input.split_once("\n\n").unwrap(); 6 | let mut orderings = HashMap::>::new(); 7 | for l in s1.lines() { 8 | let (x, y) = l.split_once('|').unwrap(); 9 | orderings.entry(y.parse().unwrap()).or_default().insert(x.parse().unwrap()); 10 | } 11 | let pages = s2.lines().map(|l| { 12 | l.split(',').map(|w| w.parse::().unwrap()).collect::>() 13 | }); 14 | 15 | let (mut p1, mut p2) = (0, 0); 16 | for mut p in pages { 17 | if p.is_sorted_by(|a, b| orderings[b].contains(a)) { 18 | p1 += p[p.len() / 2]; 19 | } else { 20 | p.sort_by(|a, b| orderings[b].contains(a).cmp(&true)); 21 | p2 += p[p.len() / 2]; 22 | } 23 | } 24 | (p1, p2) 25 | } 26 | -------------------------------------------------------------------------------- /2017/src/06.hs: -------------------------------------------------------------------------------- 1 | import Aoc 2 | import Data.Function 3 | import Data.Vector (Vector, (//), (!)) 4 | import qualified Data.Vector as Vec 5 | import Data.Map (Map) 6 | import qualified Data.Map as Map 7 | 8 | input = "4 1 15 12 0 9 9 5 5 8 7 3 14 5 12 3" 9 | 10 | moveMaximum :: Vector Int -> Vector Int 11 | moveMaximum v = v // updates 12 | where 13 | i = Vec.maxIndex v 14 | idx j = (i+j) `mod` Vec.length v 15 | updates = (i,0):[(idx j, v ! idx j + 1) | j <- [1..v ! i]] 16 | 17 | findRepeat :: Map (Vector Int) Int -> Int -> Vector Int -> (Int,Int) 18 | findRepeat seen c v = case Map.lookup v seen of 19 | Just x -> (x,c) 20 | Nothing -> findRepeat (Map.insert v c seen) (c+1) (moveMaximum v) 21 | 22 | solveParts :: Int -> (Int,Int) 23 | solveParts _ = (c, c - x) 24 | where 25 | inputs = words input & map read & Vec.fromList 26 | (x,c) = findRepeat Map.empty 0 inputs 27 | 28 | main = Aoc.timer solveParts 29 | -------------------------------------------------------------------------------- /2018/src/08.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from typing import Any 3 | 4 | Tree = tuple[list[Any],list[int]] # mypy cannot handle recursive types, use 'Any' for now 5 | 6 | def parse_tree(xs: list[int], i: int) -> tuple[int,Tree]: 7 | children, ndata = [], xs[i+1] 8 | i += 2 9 | for _ in range(xs[i-2]): 10 | i, child = parse_tree(xs,i) 11 | children.append(child) 12 | return i+ndata, (children, xs[i:i+ndata]) 13 | 14 | def metadata_sum(e: Tree) -> int: 15 | return sum(e[1]) + sum(metadata_sum(c) for c in e[0]) 16 | 17 | def node_value(e: Tree) -> int: 18 | cs, data = e 19 | if len(cs) == 0: 20 | return sum(data) 21 | return sum(node_value(cs[c-1]) for c in data if 0 < c <= len(cs)) 22 | 23 | @aoc.main('08') 24 | def main(indata: str) -> tuple[int,int]: 25 | _,e = parse_tree([int(i) for i in indata.split(' ')],0) 26 | return metadata_sum(e), node_value(e) 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /2022/src/bin/20.rs: -------------------------------------------------------------------------------- 1 | fn mix(nums: &[i64], iterations: usize, decryption_key: i64) -> i64 { 2 | let nums = nums.iter().map(|x| x * decryption_key).collect::>(); 3 | let mut ans = (0..nums.len()).collect::>(); 4 | for _ in 0..iterations { 5 | for (i, &x) in nums.iter().enumerate() { 6 | let pos = ans.iter().position(|&y| y == i).unwrap(); 7 | ans.remove(pos); 8 | let new_i = (pos as i64 + x).rem_euclid(ans.len() as i64) as usize; 9 | ans.insert(new_i, i); 10 | } 11 | } 12 | let orig_zero_i = nums.iter().position(|&i| i == 0).unwrap(); 13 | let zero_i = ans.iter().position(|&i| i == orig_zero_i).unwrap(); 14 | [1000, 2000, 3000].iter().map(|i| nums[ans[(zero_i + i) % ans.len()]]).sum() 15 | } 16 | 17 | #[aoc::main(20)] 18 | fn main(input: &str) -> (i64, i64) { 19 | let nums = input.lines().map(|l| l.parse().unwrap()).collect::>(); 20 | (mix(&nums, 1, 1), mix(&nums, 10, 811589153)) 21 | } 22 | -------------------------------------------------------------------------------- /2022/timings.md: -------------------------------------------------------------------------------- 1 | # Timings of each day 2 | Measured on an `Intel(R) Core(TM) i5-6600K CPU @ 3.50GHz`. 3 | 4 | | Day | Time (ms) | 5 | | --------- | --------: | 6 | | 01 | 0.052 | 7 | | 02 | 0.074 | 8 | | 03 | 0.152 | 9 | | 04 | 0.063 | 10 | | 05 | 0.070 | 11 | | 06 | 0.153 | 12 | | 07 | 0.268 | 13 | | 08 | 0.292 | 14 | | 09 | 0.601 | 15 | | 10 | 0.006 | 16 | | 11 | 5.595 | 17 | | 12 | 0.356 | 18 | | 13 | 0.678 | 19 | | 14 | 8.825 | 20 | | 15 | 121.177 | 21 | | 16 | 292.030 | 22 | | 17 | 17.897 | 23 | | 18 | 1.446 | 24 | | 19 | 7.933 | 25 | | 20 | 73.857 | 26 | | 21 | 0.570 | 27 | | 22 | 0.297 | 28 | | 23 | 218.210 | 29 | | 24 | 68.402 | 30 | | 25 | 0.017 | 31 | | **Total** | 819.021 | 32 | -------------------------------------------------------------------------------- /2023/src/bin/18.rs: -------------------------------------------------------------------------------- 1 | fn calc_area(instructions: impl Iterator) -> isize { 2 | let (mut r, mut c, mut a) = (0,0,0); 3 | for (d, n) in instructions { 4 | let (rr, cc) = (r,c); 5 | match d { 6 | b'U' => r -= n, 7 | b'R' => c += n, 8 | b'D' => r += n, 9 | b'L' => c -= n, 10 | _ => unreachable!(), 11 | } 12 | a += (c + cc) * (r - rr) + n; 13 | } 14 | a / 2 + 1 15 | } 16 | 17 | #[aoc::main(18)] 18 | fn main(input: &str) -> (isize, isize) { 19 | let p1 = input.split('\n').map(|l| { 20 | let (n, _) = l[2..].split_once(' ').unwrap(); 21 | (l.as_bytes()[0], n.parse().unwrap()) 22 | }); 23 | let p2 = input.split('\n').map(|l| { 24 | let (_, color) = l.split_once('#').unwrap(); 25 | let d = b"RDLU"[(color.as_bytes()[color.len()-2] - b'0') as usize]; 26 | (d, isize::from_str_radix(&color[0..color.len()-2], 16).unwrap()) 27 | }); 28 | (calc_area(p1), calc_area(p2)) 29 | } 30 | -------------------------------------------------------------------------------- /2024/src/bin/22.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::{HashMap, HashSet}; 2 | use itertools::Itertools; 3 | 4 | #[aoc::main(22)] 5 | fn main(input: &str) -> (i64, i64) { 6 | let (mut p1, mut p2) = (0, HashMap::new()); 7 | let mut seen = HashSet::new(); 8 | for l in input.lines() { 9 | let mut ps = [0; 2000]; 10 | let mut p = l.parse::().unwrap(); 11 | for i in 0..2000 { 12 | p = (p ^ (p * 64)) % 16777216; 13 | p = (p ^ (p / 32)) % 16777216; 14 | p = (p ^ (p * 2048)) % 16777216; 15 | ps[i] = p % 10; 16 | } 17 | p1 += p; 18 | 19 | seen.clear(); 20 | for (a, b, c, d, e) in ps.iter().tuple_windows() { 21 | let k = (b - a) + (c - b) * 20 + (d - c) * 400 + (e - d) * 8000; 22 | if seen.insert(k) { 23 | *p2.entry(k).or_default() += *e; 24 | } 25 | } 26 | } 27 | (p1, *p2.values().max().unwrap()) 28 | } 29 | -------------------------------------------------------------------------------- /2016/src/day16.ml: -------------------------------------------------------------------------------- 1 | let input = "01000100010010111" 2 | 3 | let flip_inverse s = 4 | let len = String.length s in 5 | String.init len (fun i -> if s.[len - i - 1] = '1' then '0' else '1') 6 | 7 | let round s = s ^ "0" ^ (flip_inverse s) 8 | 9 | let rec checksum s = 10 | let rec checksum_round res i s = 11 | if i = String.length s then res |> List.rev |> String.concat "" 12 | else 13 | let c = if s.[i] = s.[i+1] then "1" else "0" in 14 | checksum_round (c::res) (i+2) s 15 | in 16 | let sum = checksum_round [] 0 s in 17 | if String.length sum mod 2 = 0 then checksum sum else sum 18 | 19 | let rec compute_result s size = 20 | if String.length s > size then String.sub s 0 size 21 | else 22 | compute_result (round s) size 23 | 24 | let main () = 25 | let part1 = 272 |> compute_result input |> checksum in 26 | let part2 = 35651584 |> compute_result input |> checksum in 27 | (part1, part2) 28 | 29 | let () = Aoc.timer main 30 | -------------------------------------------------------------------------------- /2025/src/bin/05.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(05)] 2 | fn main(input: &str) -> (usize, usize) { 3 | let (s1, s2) = input.split_once("\n\n").unwrap(); 4 | let mut ranges = s1.split('\n') 5 | .map(|l| { 6 | let (a, b) = l.split_once('-').unwrap(); 7 | (a.parse().unwrap(), b.parse().unwrap()) 8 | }) 9 | .collect::>(); 10 | ranges.sort(); 11 | let mut merged = Vec::from([ranges[0]]); 12 | for &(a, b) in &ranges[1..] { 13 | let &(a2, b2) = merged.last().unwrap(); 14 | if a > b2 { 15 | merged.push((a, b)); 16 | } else { 17 | *merged.last_mut().unwrap() = (a2, b2.max(b)); 18 | } 19 | } 20 | 21 | let p1 = s2.split('\n').filter(|l| { 22 | let x = l.parse().unwrap(); 23 | ranges.iter().any(|&(a, b)| (a..=b).contains(&x)) 24 | }).count(); 25 | let p2 = merged.iter().map(|&(a, b)| b - a + 1).sum(); 26 | (p1, p2) 27 | } 28 | -------------------------------------------------------------------------------- /2024/src/bin/07.rs: -------------------------------------------------------------------------------- 1 | fn is_valid(target: i64, ns: &[i64], n: i64, p2: bool) -> bool { 2 | if ns.is_empty() || n > target { 3 | return n == target; 4 | } 5 | (p2 && is_valid(target, &ns[1..], n * 10i64.pow(ns[0].ilog10() + 1) + ns[0], p2)) 6 | || is_valid(target, &ns[1..], n + ns[0], p2) 7 | || is_valid(target, &ns[1..], n * ns[0], p2) 8 | } 9 | 10 | #[aoc::main(07)] 11 | fn main(input: &str) -> (i64, i64) { 12 | let ops = input.lines().map(|l| { 13 | let (n, rest) = l.split_once(": ").unwrap(); 14 | let ns = rest.split(' ').map(|w| w.parse().unwrap()).collect::>(); 15 | (n.parse::().unwrap(), ns) 16 | }); 17 | 18 | let (mut p1, mut p2) = (0, 0); 19 | for (n, ns) in ops { 20 | if is_valid(n, &ns[1..], ns[0], false) { 21 | p1 += n; 22 | } else if is_valid(n, &ns[1..], ns[0], true) { 23 | p2 += n; 24 | } 25 | } 26 | (p1, p1 + p2) 27 | } 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | get_days = $(shell head -n1 $(1)/Makefile | cut -f 3- -d ' ') 2 | 3 | YEARS := $(wildcard 20*) 4 | YEAR := $(lastword $(YEARS)) 5 | YEAR_DAYS := $(call get_days,$(YEAR)) 6 | TARGETS := $(foreach year,$(YEARS),$(addprefix $(year)-,$(call get_days,$(year))) $(year)-all) 7 | TODAY := $(shell TZ=America/New_York date +%Y%m%d) 8 | 9 | # if today is an AOC-day set it as the default goal 10 | .DEFAULT_GOAL := $(or $(filter $(TODAY:$(YEAR)12%=%),$(YEAR_DAYS)),help) 11 | .PHONY: $(YEAR_DAYS) $(TARGETS) help 12 | 13 | $(TARGETS): 14 | @$(MAKE) --no-print-directory -C $(subst -, ,$@) 15 | 16 | help: 17 | @echo 'usage: make [TARGET..]' 18 | @echo 'Automatically downloads input and runs solutions, e.g:' 19 | @echo ' make 2025-03' 20 | @echo 21 | @echo 'TARGET:' 22 | @echo ' YEAR-DAY run a specific day (e.g 2025-09)' 23 | @echo ' YEAR-all run all days (e.g 2022-all)' 24 | @echo "During the AoC month just 'make' will run the current day's" 25 | -------------------------------------------------------------------------------- /2024/src/bin/19.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | fn ways<'a>(s: &'a [u8], towels: &[&[u8]], cache: &mut HashMap<&'a [u8], usize>) -> usize { 4 | if s.is_empty() { 5 | return 1; 6 | } 7 | if let Some(&n) = cache.get(&s) { 8 | return n; 9 | } 10 | let n = towels.iter() 11 | .filter(|t| s.starts_with(t)) 12 | .map(|t| ways(&s[t.len()..], towels, cache)) 13 | .sum(); 14 | cache.insert(s, n); 15 | n 16 | } 17 | 18 | #[aoc::main(19)] 19 | fn main(input: &str) -> (usize, usize) { 20 | let (a, patterns) = input.split_once("\n\n").unwrap(); 21 | let towels = a.split(", ").map(str::as_bytes).collect::>(); 22 | let mut cache = HashMap::new(); 23 | let (mut p1, mut p2) = (0, 0); 24 | for p in patterns.lines() { 25 | let n = ways(p.as_bytes(), &towels, &mut cache); 26 | if n > 0 { 27 | p1 += 1; 28 | p2 += n; 29 | } 30 | } 31 | (p1, p2) 32 | } 33 | -------------------------------------------------------------------------------- /2018/src/04.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from collections import defaultdict, Counter 3 | import re 4 | 5 | @aoc.main('04') 6 | def main(indata: str) -> list[int]: 7 | actions = [] 8 | for l in indata.split('\n'): 9 | xs = re.findall("\[1518-(\d+)-(\d+) (\d+):(\d+)\] (.+)", l)[0] 10 | month, day, hour, minute = [int(i) for i in xs[:4]] 11 | actions.append([month, day, hour, minute, xs[4].split(' ')[1]]) 12 | asleep, guard, slept = 0, -1, defaultdict[int, Counter[int]](Counter) 13 | for _, _, _, minute, action in sorted(actions): 14 | match action: 15 | case 'up': 16 | slept[guard].update(range(asleep, minute)) 17 | case 'asleep': 18 | asleep = minute 19 | case _: 20 | guard = int(action[1:]) 21 | g1 = max(slept, key=lambda g: sum(slept[g].values())) 22 | g2 = max(slept, key=lambda g: slept[g].most_common(1)[0][1]) 23 | return [g * slept[g].most_common(1)[0][0] for g in [g1,g2]] 24 | 25 | if __name__ == "__main__": 26 | main() 27 | -------------------------------------------------------------------------------- /2015/src/15.clj: -------------------------------------------------------------------------------- 1 | (def input '((3 0 0 -3 2) (-3 3 0 0 9) (-1 0 4 0 1) (0 0 -2 2 8))) 2 | 3 | (defn zip [coll1 coll2] (map vector coll1 coll2)) 4 | 5 | (defn score [parts] 6 | (let [scores 7 | (->> parts 8 | (zip input) 9 | (map (fn [[ingredient spoons]] (map #(* spoons %) ingredient))))] 10 | (->> (range 4) 11 | (map (fn [i] 12 | (->> scores 13 | (map #(nth % i)) 14 | (reduce +)))) 15 | (map #(max 0 %)) 16 | (reduce *)))) 17 | 18 | (defn score-p2 [parts] 19 | (->> parts 20 | (zip input) 21 | (map (fn [[ingredient spoons]] (* (nth ingredient 4) spoons))) 22 | (reduce +) 23 | (#(if (= % 500) (score parts) -1)))) 24 | 25 | (defn find-optimal [score-fn] 26 | (apply max 27 | (for [ 28 | i (range 100) 29 | j (range (- 100 i)) 30 | k (range (- 100 i j))] 31 | (score-fn [i j k (- 100 i j k)])))) 32 | 33 | (->> score find-optimal (println "Part one:")) 34 | (->> score-p2 find-optimal (println "Part two:")) 35 | -------------------------------------------------------------------------------- /2017/src/17.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE BangPatterns #-} 2 | import Aoc 3 | import Data.Function 4 | 5 | insertAt :: Int -> a -> [a] -> [a] 6 | insertAt 0 v lst = v:lst 7 | insertAt i v (x:xs) = x:insertAt (i-1) v xs 8 | insertAt i _ [] = error "out of bounds" 9 | 10 | findAfter :: Int -> [Int] -> Int 11 | findAfter _ [] = error "out of bounds" 12 | findAfter n (a:xs) 13 | | a == n = head xs 14 | | otherwise = findAfter n xs 15 | 16 | part1 :: Int -> Int 17 | part1 times = foldl next ([0],0) [1..times] & fst & findAfter 2017 18 | where 19 | next (lst,i) n = 20 | let idx = 1 + (i + 335) `rem` n in 21 | (insertAt idx n lst, idx) 22 | 23 | part2 :: Int -> Int 24 | part2 times = foldl next (0,0) [1..times] & fst 25 | where 26 | next (res,i) n = 27 | let !idx = 1 + (i + 335) `rem` n in 28 | let !newRes = if idx == 1 then n else res in 29 | (newRes, idx) 30 | 31 | solveParts :: Int -> (Int,Int) 32 | solveParts _ = (part1 2017, part2 50000000) 33 | 34 | main = Aoc.timer solveParts 35 | -------------------------------------------------------------------------------- /2016/src/day18.ml: -------------------------------------------------------------------------------- 1 | let input = ".^.^..^......^^^^^...^^^...^...^....^^.^...^.^^^^....^...^^.^^^...^^^^.^^.^.^^..^.^^^..^^^^^^.^^^..^" 2 | 3 | let rec next_row res i s = 4 | if i = String.length s then 5 | res |> List.rev |> String.concat "" 6 | else 7 | let a = if i = 0 then '.' else s.[i-1] in 8 | let c = if i + 1 = String.length s then '.' else s.[i+1] in 9 | let tile = match (a,s.[i],c) with 10 | | '^','^','.' -> "^" 11 | | '.','^','^' -> "^" 12 | | '.','.','^' -> "^" 13 | | '^','.','.' -> "^" 14 | | _ -> "." in 15 | next_row (tile::res) (i+1) s 16 | 17 | let num_safe s = 18 | s |> String.to_seq |> Seq.filter ((=) '.') |> List.of_seq |> List.length 19 | 20 | let rec safe_tiles s c left = 21 | if left = 0 then c else safe_tiles (next_row [] 0 s) (num_safe s + c) (left-1) 22 | 23 | let main () = 24 | let part1 = safe_tiles input 0 40 in 25 | let part2 = safe_tiles input 0 400000 in 26 | (string_of_int part1, string_of_int part2) 27 | 28 | let () = Aoc.timer main 29 | -------------------------------------------------------------------------------- /2021/src/bin/05.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use hashbrown::HashMap; 3 | 4 | fn num_overlapping(lines: impl Iterator) -> usize { 5 | let mut points = HashMap::new(); 6 | for (x1,y1,x2,y2) in lines { 7 | let dx = (x2 - x1).signum(); 8 | let dy = (y2 - y1).signum(); 9 | let (mut x, mut y) = (x1,y1); 10 | while (x,y) != (x2+dx,y2+dy) { 11 | *points.entry((x,y)).or_insert(0) += 1; 12 | x += dx; 13 | y += dy; 14 | } 15 | } 16 | points.values().filter(|&&n| n > 1).count() 17 | } 18 | 19 | #[aoc::main(05)] 20 | fn main(input: &str) -> (usize,usize) { 21 | let lines = input.lines() 22 | .filter_map(|l| l.split(" -> ") 23 | .map(|s| s.split(',')) 24 | .flatten() 25 | .map(|i| i.parse().unwrap()) 26 | .collect_tuple() 27 | ) 28 | .collect::>(); 29 | let p1 = num_overlapping(lines.iter().copied().filter(|(x1,y1,x2,y2)| x1 == x2 || y1 == y2)); 30 | let p2 = num_overlapping(lines.iter().copied()); 31 | (p1,p2) 32 | } 33 | -------------------------------------------------------------------------------- /2015/src/14.clj: -------------------------------------------------------------------------------- 1 | (def input '((19 7 124) (3 15 28) (19 9 164) (19 9 158) (13 7 82) (25 6 145) (14 3 38) (3 16 37) (25 6 143))) 2 | 3 | ; how is this not in the stdlib? 4 | (defn zip [coll1 coll2] (map vector coll1 coll2)) 5 | 6 | (defn flown [t [speed active inactive]] 7 | (let [seconds 8 | (+ 9 | (* (quot t (+ active inactive)) active) 10 | (min (rem t (+ active inactive)) active))] 11 | (* speed seconds))) 12 | 13 | (defn give-point [max-d [points distance]] 14 | (+ points (if (= max-d distance) 1 0))) 15 | 16 | (defn give-points [t points] 17 | (let [distances (map #(flown t %) input)] 18 | (->> distances 19 | (zip points) 20 | (map #(give-point (apply max distances) %)) 21 | doall))) 22 | 23 | (defn part2 24 | ([t points] 25 | (if (= t 2503) 26 | (apply max points) 27 | (recur (inc t) (give-points t points)))) 28 | ([] (part2 1 (replicate (count input) 0)))) 29 | 30 | (->> input (map #(flown 2503 %)) (apply max) (println "Part one:")) 31 | (println "Part two:" (part2)) 32 | -------------------------------------------------------------------------------- /2023/src/bin/15.rs: -------------------------------------------------------------------------------- 1 | fn hash(s: &str) -> usize { 2 | s.bytes().fold(0, |a, c| (a + c) * 17) as usize 3 | } 4 | 5 | #[aoc::main(15)] 6 | fn main(input: &str) -> (usize, usize) { 7 | let mut map = vec![Vec::new(); 256]; 8 | let mut p1 = 0; 9 | for w in input.split(',') { 10 | p1 += hash(w); 11 | let label_end = w.bytes().position(|c| c == b'-' || c == b'=').unwrap(); 12 | let label = &w[..label_end]; 13 | let bucket = &mut map[hash(label)]; 14 | match (w.as_bytes()[label_end], bucket.iter().position(|&(l,_)| l == label)) { 15 | (b'=', Some(i)) => bucket[i] = (label, w[label_end+1..].parse::().unwrap()), 16 | (b'=', None) => bucket.push((label, w[label_end+1..].parse::().unwrap())), 17 | (b'-', Some(i)) => { bucket.remove(i); }, 18 | (b'-', None) => {}, 19 | _ => unreachable!(), 20 | } 21 | } 22 | let p2 = (0..map.len()) 23 | .flat_map(|b| (0..map[b].len()).map(move |i| (b, i))) 24 | .map(|(b, i)| (b + 1) * (i + 1) * map[b][i].1) 25 | .sum(); 26 | (p1, p2) 27 | } 28 | -------------------------------------------------------------------------------- /2022/src/bin/18.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | use itertools::Itertools; 3 | 4 | fn sides((x,y,z): (i32,i32,i32)) -> [(i32,i32,i32); 6] { 5 | [(x-1,y,z),(x+1,y,z),(x,y-1,z),(x,y+1,z),(x,y,z-1),(x,y,z+1)] 6 | } 7 | 8 | #[aoc::main(18)] 9 | fn main(input: &str) -> (usize, usize) { 10 | let drops = input.lines() 11 | .filter_map(|l| l.split(',').map(|x| x.parse().unwrap()).collect_tuple()) 12 | .collect::>(); 13 | let max = drops.iter().flat_map(|&(x,y,z)| [x,y,z]).max().unwrap() + 1; 14 | let (mut seen, mut stack) = (HashSet::new(), vec![(0,0,0)]); 15 | while let Some(p) = stack.pop() { 16 | for (x,y,z) in sides(p) { 17 | if !drops.contains(&(x,y,z)) && !seen.contains(&(x,y,z)) && [x,y,z].iter().all(|&i| -1 <= i && i <= max) { 18 | seen.insert((x,y,z)); 19 | stack.push((x,y,z)); 20 | } 21 | } 22 | } 23 | let p1 = drops.iter().flat_map(|&p| sides(p)).filter(|s| !drops.contains(s)).count(); 24 | let p2 = drops.iter().flat_map(|&p| sides(p)).filter(|s| seen.contains(s)).count(); 25 | (p1, p2) 26 | } 27 | -------------------------------------------------------------------------------- /2021/src/bin/12.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | fn num_paths<'a>( 4 | graph: &HashMap<&'a str, Vec<&'a str>>, 5 | src: &'a str, 6 | path: &mut Vec<&'a str>, 7 | mut seen_twice: bool 8 | ) -> usize { 9 | if src == "end" { 10 | return 1; 11 | } 12 | if src.chars().all(|c| c.is_lowercase()) && path.contains(&src) { 13 | if seen_twice || src == "start" { 14 | return 0; 15 | } 16 | seen_twice = true; 17 | } 18 | path.push(src); 19 | let ans = graph[src].iter() 20 | .map(|n| num_paths(graph, n, path, seen_twice)) 21 | .sum(); 22 | path.pop(); 23 | ans 24 | } 25 | 26 | #[aoc::main(12)] 27 | fn main(input: &str) -> (usize,usize) { 28 | let mut graph = HashMap::new(); 29 | for l in input.lines() { 30 | let (a,b) = l.split_once('-').unwrap(); 31 | graph.entry(a).or_insert(Vec::new()).push(b); 32 | graph.entry(b).or_insert(Vec::new()).push(a); 33 | } 34 | let p1 = num_paths(&graph, "start", &mut Vec::new(), true); 35 | let p2 = num_paths(&graph, "start", &mut Vec::new(), false); 36 | (p1,p2) 37 | } 38 | -------------------------------------------------------------------------------- /2022/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, process::Command, error::Error}; 2 | use itertools::Itertools; 3 | 4 | fn extract_microseconds(output: &str) -> Result> { 5 | let out = output.lines().last().unwrap(); 6 | let time = if out.ends_with("ms") { 7 | out["Time: ".len()..out.len()-2].parse::()? * 1000 8 | } else { 9 | out["Time: ".len()..out.len()-3].parse::()? 10 | }; 11 | Ok(time) 12 | } 13 | 14 | fn main() -> Result<(), Box> { 15 | let days = fs::read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/"))? 16 | .filter_map(|p| p.ok()?.path().file_stem()?.to_str().map(str::to_string)) 17 | .sorted() 18 | .collect::>(); 19 | let mut total_time = 0; 20 | for day in &days { 21 | let cmd = Command::new("cargo").args(["run", "--release", "--bin", day]).output()?; 22 | let output = String::from_utf8(cmd.stdout)?; 23 | println!("Day {}:\n{}", day, output); 24 | total_time += extract_microseconds(&output)?; 25 | } 26 | println!("Total time: {}ms", total_time / 1000); 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /2024/src/bin/11.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | fn update(old_stones: &HashMap) -> HashMap { 4 | let mut stones = HashMap::with_capacity(old_stones.len()); 5 | for (&s, &v) in old_stones { 6 | match s { 7 | 0 => *stones.entry(1).or_default() += v, 8 | s if s.ilog10() % 2 == 1 => { 9 | let m = 10i64.pow((s.ilog10() + 1) / 2); 10 | *stones.entry(s % m).or_default() += v; 11 | *stones.entry(s / m).or_default() += v; 12 | } 13 | _ => *stones.entry(s * 2024).or_default() += v, 14 | } 15 | } 16 | stones 17 | } 18 | 19 | #[aoc::main(11)] 20 | fn main(input: &str) -> (usize, usize) { 21 | let mut stones = input.split(' ') 22 | .map(|w| (w.parse().unwrap(), 1)) 23 | .collect::>(); 24 | let mut p1 = 0; 25 | for i in 0..75 { 26 | if i == 25 { 27 | p1 = stones.values().sum(); 28 | } 29 | stones = update(&stones); 30 | } 31 | (p1, stones.values().sum()) 32 | } 33 | -------------------------------------------------------------------------------- /2018/src/07.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from collections import defaultdict 3 | 4 | def path(reqs: dict[str,list[str]]) -> str: 5 | left, ans = sorted(reqs.keys()), "" 6 | while left: 7 | ans += next(x for x in left if not any(r in left for r in reqs[x])) 8 | left.remove(ans[-1]) 9 | return ans 10 | 11 | def part2(reqs: dict[str,list[str]]) -> int: 12 | left, working, t = sorted(reqs.keys()), dict[str,int](), 0 13 | while left: 14 | for c in list(working.keys()): 15 | working[c] -= 1 16 | if working[c] == 0: 17 | working.pop(c) 18 | left.remove(c) 19 | for x in left: 20 | if x in working or any(r in left for r in reqs[x]): 21 | continue 22 | if len(working) < 5: 23 | working[x] = ord(x) - 4 24 | t += 1 25 | return t-1 26 | 27 | @aoc.main('07') 28 | def main(indata: str): 29 | reqs = defaultdict(list) 30 | for l in indata.split('\n'): 31 | reqs[l[36]].append(l[5]) 32 | reqs[l[5]] # to force the key to be added 33 | return path(reqs), part2(reqs) 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /2023/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{parse_macro_input, Ident, LitInt, ItemFn}; 3 | 4 | #[proc_macro_attribute] 5 | pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { 6 | let day = parse_macro_input!(args as LitInt); 7 | let input_path = format!("../../inputs/{}.in", day.token()); 8 | 9 | let mut aoc_solution = parse_macro_input!(input as ItemFn); 10 | aoc_solution.sig.ident = Ident::new("aoc_solution", aoc_solution.sig.ident.span()); 11 | 12 | let tokens = quote::quote! { 13 | const INPUT: &str = include_str!(#input_path); 14 | #aoc_solution 15 | fn main() { 16 | let now = ::std::time::Instant::now(); 17 | let (p1, p2) = aoc_solution(INPUT.trim_end()); 18 | let elapsed = now.elapsed(); 19 | println!("Part one: {}", p1); 20 | println!("Part two: {}", p2); 21 | if elapsed.as_millis() > 0 { 22 | println!("Time: {}ms", elapsed.as_millis()); 23 | } else { 24 | println!("Time: {}μs", elapsed.as_micros()); 25 | } 26 | } 27 | }; 28 | TokenStream::from(tokens) 29 | } 30 | -------------------------------------------------------------------------------- /2017/src/13.hs: -------------------------------------------------------------------------------- 1 | import Aoc 2 | import Data.List 3 | import Data.Maybe 4 | import Data.Function 5 | import Data.List.Split 6 | 7 | input = "0: 3\n1: 2\n2: 4\n4: 4\n6: 5\n8: 8\n10: 6\n12: 6\n14: 6\n16: 6\n18: 8\n20: 8\n22: 12\n24: 10\n26: 9\n28: 8\n30: 8\n32: 12\n34: 12\n36: 12\n38: 12\n40: 8\n42: 12\n44: 14\n46: 14\n48: 10\n50: 12\n52: 12\n54: 14\n56: 14\n58: 14\n62: 12\n64: 14\n66: 14\n68: 14\n70: 12\n74: 14\n76: 14\n78: 14\n80: 18\n82: 17\n84: 30\n88: 14" 8 | 9 | parseInput :: String -> [(Int,Int)] 10 | parseInput = map parseLine . lines 11 | where 12 | parseLine s = case splitOn ": " s & map read of 13 | [a,b] -> (a,b) 14 | _ -> error "invalid input" 15 | 16 | caught :: Int -> (Int,Int) -> Bool 17 | caught offset (layer,depth) = (layer + offset) `rem` (depth * 2 - 2) == 0 18 | 19 | solveParts :: Int -> (Int,Int) 20 | solveParts _ = (part1, part2) 21 | where 22 | layers = parseInput input 23 | part1 = filter (caught 0) layers & map (uncurry (*)) & sum 24 | part2 = find (\o -> not (any (caught o) layers)) [0..] & fromJust 25 | 26 | main = Aoc.timer solveParts 27 | -------------------------------------------------------------------------------- /2015/src/11.clj: -------------------------------------------------------------------------------- 1 | (require '[clojure.string :as str]) 2 | 3 | (defn next-pw [pw] 4 | (case (last pw) 5 | \z (str (->> pw drop-last next-pw) \a) 6 | \h (str (->> pw drop-last (str/join "")) \j) ; skip i 7 | \n (str (->> pw drop-last (str/join "")) \p) ; skip o 8 | \k (str (->> pw drop-last (str/join "")) \m) ; skip l 9 | (str (->> pw drop-last (str/join "")) (->> pw last int inc char)))) 10 | 11 | (defn increasing-triple? [triple] 12 | (let [[a b c] (map int triple)] 13 | (= (inc a) b (dec c)))) 14 | 15 | (defn has-two-pairs? [[[a b] & pairs]] 16 | (and 17 | a 18 | (or 19 | (and (= a b) (some (fn [[c d]] (and (= c d) (not= a c))) (rest pairs))) 20 | (recur pairs)))) 21 | 22 | (defn valid-pw [pw] 23 | (and 24 | (->> pw (partition 3 1) (some increasing-triple?)) 25 | (->> pw (partition 2 1) has-two-pairs?))) 26 | 27 | (defn find-next-valid [start] 28 | (->> start (iterate next-pw) rest (filter valid-pw) first)) 29 | 30 | (let [[p1 p2] (->> "cqjxjnds" (iterate find-next-valid) (take 2))] 31 | (println "Part one:" p1) 32 | (println "Part two:" p2)) 33 | -------------------------------------------------------------------------------- /2021/src/bin/25.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn step(map: &mut Vec>, dr: usize, dc: usize) -> bool { 4 | let (rows,cols) = (map.len(), map[0].len()); 5 | let mut map2 = vec![vec![b'.'; cols]; rows]; 6 | let mut moved = false; 7 | for (r,c) in (0..rows).cartesian_product(0..cols) { 8 | match map[r][c] { 9 | b'>' if map[r][(c+dc) % cols] == b'.' => { 10 | map2[r][(c+dc) % cols] = b'>'; 11 | moved = true; 12 | } 13 | b'v' if map[(r+dr) % rows][c] == b'.' => { 14 | map2[(r+dr) % rows][c] = b'v'; 15 | moved = true; 16 | } 17 | b'>' => map2[r][c] = b'>', 18 | b'v' => map2[r][c] = b'v', 19 | _ => {} 20 | } 21 | } 22 | *map = map2; 23 | moved 24 | } 25 | 26 | #[aoc::main(25)] 27 | fn main(input: &str) -> (usize,char) { 28 | let mut map = input.lines().map(|l| l.bytes().collect()).collect(); 29 | let mut round = 0; 30 | loop { 31 | let move1 = step(&mut map, 0, 1); 32 | let move2 = step(&mut map, 1, 0); 33 | round += 1; 34 | if !move1 && !move2 { break } 35 | } 36 | (round,'🎄') 37 | } 38 | -------------------------------------------------------------------------------- /2021/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{parse_macro_input, AttributeArgs, ItemFn, NestedMeta, Lit, Ident}; 4 | 5 | #[proc_macro_attribute] 6 | pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { 7 | let input_path = match &parse_macro_input!(args as AttributeArgs)[..] { 8 | [NestedMeta::Lit(Lit::Int(day))] => format!("../../inputs/{}.in", day.token().to_string()), 9 | _ => panic!("Expected one integer argument"), 10 | }; 11 | 12 | let mut aoc_solution = parse_macro_input!(input as ItemFn); 13 | aoc_solution.sig.ident = Ident::new("aoc_solution", aoc_solution.sig.ident.span()); 14 | 15 | let tokens = quote! { 16 | const INPUT: &str = include_str!(#input_path); 17 | #aoc_solution 18 | fn main() { 19 | let now = ::std::time::Instant::now(); 20 | let (p1, p2) = aoc_solution(INPUT.trim_end()); 21 | let time = now.elapsed().as_millis(); 22 | println!("Part one: {}", p1); 23 | println!("Part two: {}", p2); 24 | println!("Time: {}ms", time); 25 | } 26 | }; 27 | TokenStream::from(tokens) 28 | } 29 | -------------------------------------------------------------------------------- /2019/src/bin/04.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | use std::ops::Range; 3 | use itertools::Itertools; 4 | 5 | const INPUT: Range = 206938..679128; 6 | 7 | fn to_digits(n: usize) -> [usize; 6] { 8 | [ 9 | (n / 100000) % 10, 10 | (n / 10000) % 10, 11 | (n / 1000) % 10, 12 | (n / 100) % 10, 13 | (n / 10) % 10, 14 | (n / 1) % 10, 15 | ] 16 | } 17 | 18 | fn count_passwords() -> (usize,usize) { 19 | let passwords = INPUT.map(to_digits) 20 | .filter(|d| (1..d.len()).all(|i| d[i-1] <= d[i])) 21 | .filter(|d| (1..d.len()).any(|i| d[i-1] == d[i])) 22 | .map(|d| d.iter() 23 | .group_by(|&i| i) 24 | .into_iter() 25 | .any(|(_,x)| x.count() == 2) 26 | ) 27 | .collect::>(); 28 | let part_one = passwords.len(); 29 | let part_two = passwords.iter().filter(|&&b| b).count(); 30 | (part_one,part_two) 31 | } 32 | 33 | fn main() { 34 | let now = Instant::now(); 35 | let (part_one, part_two) = count_passwords(); 36 | println!("Part one: {}", part_one); 37 | println!("Part two: {}", part_two); 38 | println!("Time: {}ms", now.elapsed().as_millis()); 39 | } 40 | -------------------------------------------------------------------------------- /2015/src/23.clj: -------------------------------------------------------------------------------- 1 | (def input '[(:jio :a 19) (:inc :a) (:tpl :a) (:inc :a) (:tpl :a) (:inc :a) (:tpl :a) (:tpl :a) (:inc :a) (:inc :a) (:tpl :a) (:tpl :a) (:inc :a) (:inc :a) (:tpl :a) (:inc :a) (:inc :a) (:tpl :a) (:jmp 23) (:tpl :a) (:tpl :a) (:inc :a) (:inc :a) (:tpl :a) (:inc :a) (:inc :a) (:tpl :a) (:inc :a) (:tpl :a) (:inc :a) (:tpl :a) (:inc :a) (:tpl :a) (:inc :a) (:inc :a) (:tpl :a) (:inc :a) (:inc :a) (:tpl :a) (:tpl :a) (:inc :a) (:jio :a 8) (:inc :b) (:jie :a 4) (:tpl :a) (:inc :a) (:jmp 2) (:hlf :a) (:jmp -7)]) 2 | 3 | (defn run-inst [ip regs [code x y]] 4 | (case code 5 | :hlf [(+ ip 1) (update regs x #(/ % 2))] 6 | :tpl [(+ ip 1) (update regs x #(* % 3))] 7 | :inc [(+ ip 1) (update regs x inc)] 8 | :jmp [(+ ip x) regs] 9 | :jie [(+ ip (if (even? (regs x)) y 1)) regs] 10 | :jio [(+ ip (if (= 1 (regs x)) y 1)) regs])) 11 | 12 | (defn run [insts [ip regs]] 13 | (let [i (get insts ip)] 14 | (if (not i) 15 | (regs :b) 16 | (recur insts (run-inst ip regs i))))) 17 | 18 | (->> [0 {:a 0 :b 0}] (run input) (println "Part one:")) 19 | (->> [0 {:a 1 :b 0}] (run input) (println "Part two:")) 20 | -------------------------------------------------------------------------------- /2015/src/21.clj: -------------------------------------------------------------------------------- 1 | (def input '(109 8 2)) 2 | 3 | (def weapons '((8 4 0) (10 5 0) (25 6 0) (40 7 0) (74 8 0))) 4 | (def armor '((0 0 0) (13 0 1) (31 0 2) (53 0 3) (75 0 4) (102 0 5))) 5 | (def rings '((0 0 0) (25 1 0) (50 2 0) (100 3 0) (20 0 1) (40 0 2) (80 0 3))) 6 | 7 | (defn won-battle [dmg boss-hp boss-dmg] 8 | (or 9 | (<= boss-dmg 0) 10 | (< 11 | (quot boss-hp dmg) 12 | (quot 100 boss-dmg)))) 13 | 14 | (defn part1 [dmg boss-hp boss-dmg cost] 15 | (if (won-battle dmg boss-hp boss-dmg) cost 1000)) 16 | 17 | (defn part2 [dmg boss-hp boss-dmg cost] 18 | (if (won-battle dmg boss-hp boss-dmg) 0 cost)) 19 | 20 | (defn find-goal [goal cost-fn] 21 | (apply goal 22 | (for [[c1 d1 a1] weapons 23 | [c2 d2 a2] armor 24 | [c3 d3 a3] rings 25 | [c4 d4 a4] rings 26 | :when (not= c3 c4)] 27 | (let [[boss-hp d a] input 28 | dmg (- (+ d1 d2 d3 d4) a) 29 | boss-dmg (- d (+ a1 a2 a3 a4))] 30 | (cost-fn dmg boss-hp boss-dmg (+ c1 c2 c3 c4)))))) 31 | 32 | (->> part1 (find-goal min) (println "Part one:")) 33 | (->> part2 (find-goal max) (println "Part two:")) 34 | -------------------------------------------------------------------------------- /2025/src/bin/07.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | #[aoc::main(07)] 4 | fn main(input: &str) -> (usize, usize) { 5 | let m = input.split('\n').map(str::as_bytes).collect::>(); 6 | let start = m[0].iter().position(|&c| c == b'S').unwrap(); 7 | let mut beams = HashMap::from([((0, start), 1)]); 8 | let mut p1 = 0; 9 | loop { 10 | let mut new_beams = HashMap::new(); 11 | for (&(r, c), &v) in &beams { 12 | match m.get(r+1).and_then(|row| row.get(c)) { 13 | Some(b'.') => { 14 | *new_beams.entry((r+1, c)).or_default() += v; 15 | } 16 | Some(b'^') => { 17 | for cc in [c-1, c+1] { 18 | *new_beams.entry((r+1, cc)).or_default() += v; 19 | } 20 | p1 += 1; 21 | } 22 | None => continue, 23 | _ => unreachable!() 24 | } 25 | } 26 | if new_beams.is_empty() { 27 | break; 28 | } 29 | beams = new_beams; 30 | } 31 | (p1, beams.values().sum()) 32 | } 33 | -------------------------------------------------------------------------------- /2025/src/bin/11.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | fn count_paths<'a>( 4 | cache: &mut HashMap<(&'a str, bool, bool), usize>, 5 | g: &HashMap<&'a str, Vec<&'a str>>, 6 | node: &'a str, 7 | mut dac: bool, 8 | mut fft: bool, 9 | ) -> usize { 10 | if node == "out" { 11 | return if dac && fft {1} else {0}; 12 | } 13 | dac |= node == "dac"; 14 | fft |= node == "fft"; 15 | let key = (node, dac, fft); 16 | if !cache.contains_key(&key) { 17 | let res = g[node].iter().map(|n| count_paths(cache, g, n, dac, fft)).sum(); 18 | cache.insert(key, res); 19 | } 20 | cache[&key] 21 | } 22 | 23 | #[aoc::main(11)] 24 | fn main(input: &str) -> (usize, usize) { 25 | let mut g = HashMap::new(); 26 | for l in input.split('\n') { 27 | let (n, rest) = l.split_once(": ").unwrap(); 28 | let nodes = rest.split(' ').collect::>(); 29 | g.insert(n, nodes); 30 | } 31 | let mut cache = HashMap::new(); 32 | let p1 = count_paths(&mut cache, &g, "you", true, true); 33 | let p2 = count_paths(&mut cache, &g, "svr", false, false); 34 | (p1, p2) 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2015/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2016/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2017/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2018/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2019/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2020/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2021/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2022/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Axel Lindeberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code :christmas_tree: 2 | My solutions to every single AoC problem, most in Rust :crab: All the way from 2015 to today! 3 | 4 | ![all-stars](./imgs/all-stars.png) 5 | 6 | ## Languages 7 | Year | Language 8 | ---- | -------- 9 | [2025](./2025) | :crab: Rust 10 | [2024](./2024) | :crab: Rust 11 | [2023](./2023) | :crab: Rust 12 | [2022](./2022) | :crab: Rust 13 | [2021](./2021) | :crab: Rust 14 | [2020](./2020) | :crab: Rust 15 | [2019](./2019) | :crab: Rust 16 | [2018](./2018) | :snake: Python 17 | [2017](./2017) | haskell Haskell 18 | [2016](./2016) | :camel: OCaml 19 | [2015](./2015) | clojure Clojure 20 | 21 | ## Usage 22 | ```sh 23 | # to be able to download inputs (see fetch.sh) 24 | export AOC_SESSION=[value from session cookie] 25 | 26 | make # run todays (if AOC is currently running) 27 | make 2018-09 # run a specific day 28 | make 2020-all # run all from a specific year 29 | ``` 30 | -------------------------------------------------------------------------------- /2021/src/bin/17.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn try_vel(xmin: i32, xmax: i32, ymin: i32, ymax: i32, mut dx: i32, mut dy: i32) -> Option { 4 | let (mut x, mut y, mut maxy) = (0,0,0); 5 | loop { 6 | x += dx; 7 | y += dy; 8 | dx -= dx.signum(); 9 | dy -= 1; 10 | if y > maxy { maxy = y; } 11 | match (xmin <= x && x <= xmax, ymin <= y && y <= ymax) { 12 | (true,true) => return Some(maxy), 13 | (false,_) if dx == 0 => return None, 14 | (_,false) if dy < 0 && y < ymin => return None, 15 | _ => {} 16 | } 17 | } 18 | } 19 | 20 | fn parse_range(s: &str) -> (i32,i32) { 21 | let (a,b) = s.split_once("..").unwrap(); 22 | (a.parse().unwrap(), b.parse().unwrap()) 23 | } 24 | 25 | #[aoc::main(17)] 26 | fn main(input: &str) -> (i32,usize) { 27 | let (xrange, yrange) = input[15..].split_once(", y=").unwrap(); 28 | let (xmin,xmax) = parse_range(xrange); 29 | let (ymin,ymax) = parse_range(yrange); 30 | let maxys = (0..=xmax).cartesian_product(ymin..1000) 31 | .filter_map(|(x,y)| try_vel(xmin,xmax,ymin,ymax,x,y)) 32 | .collect::>(); 33 | (*maxys.iter().max().unwrap(), maxys.len()) 34 | } 35 | -------------------------------------------------------------------------------- /2015/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2015 λ 2 | Solutions to all 25 AoC 2015 problems in Clojure! 3 | 4 | Having done both [2019](../2019) and [2020](../2020), I wanted to go back and do some of the earlier AoC years. I figured 2015 was a perfect opportunity to force myself to become better at functional languages so I did all problems in Clojure. I had never used the language before, or any of the ones in the Lisp family for that matter! It was quite a steep learning curve for me. Mainly it was difficult to get used to reading the _parenthesis syntax_ as well as the _prefix notation_ that Clojure uses. Using a functional approach to solve the problems was really nice most of the time and [really awful](./src/06.clj) for some specific ones. 5 | 6 | Only thing I did not like was the awful error messages. When you got an exception the top most call site would be reported, not where the actual exception occurred. How is that a thing? Overall, I was presently surprised by Clojure, after I got into it a bit. The language is so small and therefore quite simple. I also extensively used the `->>` threading macro, which was amazing for readability. 7 | 8 | ![end-screen](../imgs/end-screen-2015.png) 9 | -------------------------------------------------------------------------------- /2022/src/bin/02.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::identity_op)] 2 | fn score(rounds: &[(char, char)]) -> usize { 3 | rounds.iter().map(|(a,b)| match (a,b) { 4 | ('A', 'Y') => 6 + 2, 5 | ('B', 'Z') => 6 + 3, 6 | ('C', 'X') => 6 + 1, 7 | ('A', 'X') => 3 + 1, 8 | ('B', 'Y') => 3 + 2, 9 | ('C', 'Z') => 3 + 3, 10 | ('A', 'Z') => 0 + 3, 11 | ('B', 'X') => 0 + 1, 12 | ('C', 'Y') => 0 + 2, 13 | _ => unreachable!(), 14 | }).sum() 15 | } 16 | 17 | #[aoc::main(02)] 18 | fn main(input: &str) -> (usize, usize) { 19 | let r1 = input.lines() 20 | .map(|l| (l.as_bytes()[0] as char, l.as_bytes()[2] as char)) 21 | .collect::>(); 22 | let r2 = r1.iter().copied().map(|(a,b)| match (a,b) { 23 | // lose 24 | ('A', 'X') => ('A', 'Z'), 25 | ('B', 'X') => ('B', 'X'), 26 | ('C', 'X') => ('C', 'Y'), 27 | // draw 28 | ('A', 'Y') => ('A', 'X'), 29 | ('B', 'Y') => ('B', 'Y'), 30 | ('C', 'Y') => ('C', 'Z'), 31 | // win 32 | ('A', 'Z') => ('A', 'Y'), 33 | ('B', 'Z') => ('B', 'Z'), 34 | ('C', 'Z') => ('C', 'X'), 35 | _ => unreachable!() 36 | }).collect::>(); 37 | (score(&r1), score(&r2)) 38 | } 39 | -------------------------------------------------------------------------------- /2019/src/bin/02.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | use itertools::Itertools; 3 | 4 | fn run(i: usize, j: usize) -> usize { 5 | let mut program = [1,i,j,3,1,1,2,3,1,3,4,3,1,5,0,3,2,10,1,19,1,6,19,23,2,23,6,27,1,5,27,31,1,31,9,35,2,10,35,39,1,5,39,43,2,43,10,47,1,47,6,51,2,51,6,55,2,55,13,59,2,6,59,63,1,63,5,67,1,6,67,71,2,71,9,75,1,6,75,79,2,13,79,83,1,9,83,87,1,87,13,91,2,91,10,95,1,6,95,99,1,99,13,103,1,13,103,107,2,107,10,111,1,9,111,115,1,115,10,119,1,5,119,123,1,6,123,127,1,10,127,131,1,2,131,135,1,135,10,0,99,2,14,0,0]; 6 | let mut pc = 0; 7 | loop { 8 | let a = program[program[pc+1]]; 9 | let b = program[program[pc+2]]; 10 | let pos = program[pc+3]; 11 | match program[pc] { 12 | 1 => program[pos] = a + b, 13 | 2 => program[pos] = a * b, 14 | _ => break 15 | } 16 | pc += 4; 17 | } 18 | program[0] 19 | } 20 | 21 | fn main() { 22 | let now = Instant::now(); 23 | let (i,j) = (0..100).cartesian_product(0..100) 24 | .find(|&(i,j)| run(i,j) == 19690720) 25 | .unwrap(); 26 | println!("Part one: {}", run(12,2)); 27 | println!("Part two: {}", 100 * i + j); 28 | println!("Time: {}ms", now.elapsed().as_millis()); 29 | } 30 | -------------------------------------------------------------------------------- /2021/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2021 :christmas_tree: 2 | Solutions to all 25 AoC 2021 problems in Rust, using less than **100 lines** per day, with a total execution time of less than a second :crab: 3 | 4 | All solutions are quite short and efficient while still being readable and idiomatic. The code emits zero clippy warnings and has a total execution time of about `0.85s`! 5 | 6 | See [timings.md](./timings.md) for execution time and output of each solution. 7 | 8 | Thanks again for another amazing year of AoC, especially the subreddit community and of course [@ericwastl](https://twitter.com/ericwastl)! Overall, I thought 2021 was one of the best years yet. The difficult was just right. Definitely harder than 2020 I thought but not more than 2019. 9 | 10 | ## Leaderboard placings 11 | The puzzles unlock at `06:00` in the morning in my timezone. This is the second year in a row I've set an alarm for `5:50` for 25 days in a row to complete for leaderboard placings (except day 11 when I overslept)! 12 | 13 | Went very well this year compared to [last year](../2020), top 1000 for `18/25` days. Set a new personal best of `153`! 14 | 15 | ![leaderboard](../imgs/leaderboard-2021.png) 16 | -------------------------------------------------------------------------------- /2024/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{parse_macro_input, Ident, LitInt, ItemFn}; 3 | 4 | #[proc_macro_attribute] 5 | pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { 6 | let day = parse_macro_input!(args as LitInt); 7 | let input_path = format!("../../inputs/{}.in", day.token()); 8 | 9 | let mut aoc_solution = parse_macro_input!(input as ItemFn); 10 | aoc_solution.sig.ident = Ident::new("aoc_solution", aoc_solution.sig.ident.span()); 11 | 12 | let tokens = quote::quote! { 13 | const INPUT: &str = include_str!(#input_path); 14 | #aoc_solution 15 | fn main() { 16 | let now = ::std::time::Instant::now(); 17 | let (p1, p2) = aoc_solution(INPUT.trim_end()); 18 | let elapsed = now.elapsed(); 19 | println!("Part one: {}", p1); 20 | println!("Part two: {}", p2); 21 | if elapsed.as_millis() > 0 { 22 | println!("Time: {}ms", elapsed.as_millis()); 23 | } else { 24 | println!("Time: {}μs", elapsed.as_micros()); 25 | } 26 | } 27 | }; 28 | TokenStream::from(tokens) 29 | } 30 | -------------------------------------------------------------------------------- /2025/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{parse_macro_input, Ident, LitInt, ItemFn}; 3 | 4 | #[proc_macro_attribute] 5 | pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { 6 | let day = parse_macro_input!(args as LitInt); 7 | let input_path = format!("../../inputs/{}.in", day.token()); 8 | 9 | let mut aoc_solution = parse_macro_input!(input as ItemFn); 10 | aoc_solution.sig.ident = Ident::new("aoc_solution", aoc_solution.sig.ident.span()); 11 | 12 | let tokens = quote::quote! { 13 | const INPUT: &str = include_str!(#input_path); 14 | #aoc_solution 15 | fn main() { 16 | let now = ::std::time::Instant::now(); 17 | let (p1, p2) = aoc_solution(INPUT.trim_end()); 18 | let elapsed = now.elapsed(); 19 | println!("Part one: {}", p1); 20 | println!("Part two: {}", p2); 21 | if elapsed.as_millis() > 0 { 22 | println!("Time: {}ms", elapsed.as_millis()); 23 | } else { 24 | println!("Time: {}μs", elapsed.as_micros()); 25 | } 26 | } 27 | }; 28 | TokenStream::from(tokens) 29 | } 30 | -------------------------------------------------------------------------------- /2024/src/bin/10.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use hashbrown::HashSet; 3 | 4 | fn reachable_nines(g: &[&[u8]], r: usize, c: usize) -> Vec<(usize, usize)> { 5 | let mut q = VecDeque::from([(r, c)]); 6 | let mut seen = Vec::new(); 7 | while let Some((r, c)) = q.pop_front() { 8 | if g[r][c] == b'9' { 9 | seen.push((r, c)); 10 | continue; 11 | } 12 | for (rr, cc) in [(r+1, c), (r-1, c), (r, c+1), (r, c-1)] { 13 | if *g.get(rr).and_then(|row| row.get(cc)).unwrap_or(&0) == g[r][c] + 1 { 14 | q.push_back((rr, cc)); 15 | } 16 | } 17 | } 18 | seen 19 | } 20 | 21 | #[aoc::main(10)] 22 | fn main(input: &str) -> (usize, usize) { 23 | let g = input.lines().map(|l| l.as_bytes()).collect::>(); 24 | let (mut p1, mut p2) = (0, 0); 25 | for r in 0..g.len() { 26 | for c in 0..g[0].len() { 27 | if g[r][c] == b'0' { 28 | let seen = reachable_nines(&g, r, c); 29 | p1 += seen.iter().collect::>().len(); 30 | p2 += seen.len(); 31 | } 32 | } 33 | } 34 | (p1, p2) 35 | } 36 | -------------------------------------------------------------------------------- /2018/src/06.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from itertools import product 3 | from collections import Counter 4 | from typing import Iterable 5 | 6 | def bounding_box(points: list[list[int]]) -> Iterable[tuple[int,int]]: 7 | xmin, xmax = min(x for x,_ in points), max(x for x,_ in points) 8 | ymin, ymax = min(y for _,y in points), max(y for _,y in points) 9 | yield from product(range(xmin,xmax+1), range(ymin,ymax+1)) 10 | 11 | def part1(points: list[list[int]]) -> int: 12 | closest = {} 13 | for x,y in bounding_box(points): 14 | distances = [abs(a-x) + abs(b-y) for a,b in points] 15 | d = min(distances) 16 | if distances.count(d) == 1: 17 | closest[(x,y)] = distances.index(d) 18 | return Counter(closest.values()).most_common(1)[0][1] 19 | 20 | def part2(points: list[list[int]]) -> int: 21 | ans = 0 22 | for x,y in bounding_box(points): 23 | total_dist = sum(abs(a-x) + abs(b-y) for a,b in points) 24 | ans += total_dist < 10000 25 | return ans 26 | 27 | @aoc.main('06') 28 | def main(indata: str): 29 | points = [[int(i) for i in l.split(', ')] for l in indata.split('\n')] 30 | return part1(points), part2(points) 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /2016/src/day13.ml: -------------------------------------------------------------------------------- 1 | let input = 1364 2 | 3 | let rec popcnt c i = 4 | if i = 0 then c else popcnt (c + (i mod 2)) (i / 2) 5 | 6 | let is_walkable (x,y) = 7 | let sum = x*x + 3*x + 2*x*y + y + y*y + input in 8 | popcnt 0 sum mod 2 = 0 9 | 10 | let valid_neighbours visited (x,y) = [(x+1,y);(x-1,y);(x,y+1);(x,y-1)] 11 | |> List.filter (fun (x,y) -> x > -1 && y > -1) 12 | |> List.filter (fun pos -> pos |> Hashtbl.mem visited |> not) 13 | |> List.filter is_walkable 14 | 15 | let bfs end_fn = 16 | let rec bfs_impl visited queue (steps, curr) = 17 | if end_fn steps curr then (steps, Hashtbl.length visited) 18 | else 19 | let neighbours = valid_neighbours visited curr in 20 | neighbours |> List.iter (fun n -> Hashtbl.add visited n true); 21 | neighbours |> List.to_seq |> Seq.map (fun n -> (steps+1, n)) |> Queue.add_seq queue; 22 | bfs_impl visited queue (Queue.take queue) 23 | in 24 | bfs_impl (Hashtbl.create 256) (Queue.create ()) (0,(1,1)) 25 | 26 | let main () = 27 | let part1 = bfs (fun _ pos -> pos = (31,39)) |> fst in 28 | let part2 = bfs (fun steps _ -> steps = 50) |> snd in 29 | (string_of_int part1, string_of_int part2) 30 | 31 | let () = Aoc.timer main 32 | -------------------------------------------------------------------------------- /2018/src/12.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | 3 | def step(transitions: set[str], pots: set[int]) -> set[int]: 4 | new_pots = set() 5 | for p in range(min(pots)-2,max(pots)+3): 6 | s = ''.join('#' if p+j in pots else '.' for j in range(-2,3)) 7 | if s in transitions: 8 | new_pots.add(p) 9 | return new_pots 10 | 11 | def part1(transitions: set[str], pots: set[int]) -> int: 12 | for _ in range(20): 13 | pots = step(transitions, pots) 14 | return sum(pots) 15 | 16 | def part2(transitions: set[str], pots: set[int]) -> int: 17 | seen = set() 18 | for n in range(1,1000): 19 | pots = step(transitions, pots) 20 | minpot = min(pots) 21 | pattern = tuple(sorted(p - minpot for p in pots)) 22 | if pattern in seen: 23 | return (50000000000 - n) * len(pots) + sum(pots) 24 | seen.add(pattern) 25 | assert False 26 | 27 | @aoc.main('12') 28 | def main(indata: str) -> tuple[int,int]: 29 | state, rest = indata.split('\n\n') 30 | pots = {i for i,c in enumerate(state.split(": ")[-1]) if c == '#'} 31 | transitions = {s[:5] for s in rest.split('\n') if s[-1] == '#'} 32 | return part1(transitions, pots), part2(transitions, pots) 33 | 34 | if __name__ == "__main__": 35 | main() 36 | -------------------------------------------------------------------------------- /2022/src/bin/08.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::max; 2 | use itertools::Itertools; 3 | 4 | fn examine_tree(grid: &[Vec], r: usize, c: usize) -> (bool, usize) { 5 | let tree = grid[r][c]; 6 | let (mut invisible, mut score) = (true, 1); 7 | for (dr, dc) in [(-1,0), (0,-1), (1,0), (0,1)] { 8 | let (mut r, mut c, mut i, mut visible) = (r, c, 0, true); 9 | while let Some(&next) = grid.get((r as isize + dr) as usize).and_then(|x| x.get((c as isize + dc) as usize)) { 10 | i += 1; 11 | if tree <= next { 12 | visible = false; 13 | break; 14 | } 15 | r = (r as isize + dr) as usize; 16 | c = (c as isize + dc) as usize; 17 | } 18 | if visible { 19 | invisible = false; 20 | } 21 | score *= i; 22 | } 23 | (!invisible, score) 24 | } 25 | 26 | #[aoc::main(08)] 27 | fn main(input: &str) -> (usize, usize) { 28 | let grid = input.lines() 29 | .map(|l| l.as_bytes().iter().map(|b| b - b'0').collect::>()) 30 | .collect::>(); 31 | (0..grid.len()).cartesian_product(0..grid[0].len()).fold((0, 0), |(p1, p2), (r, c)| { 32 | let (visible, score) = examine_tree(&grid, r, c); 33 | (p1 + visible as usize, max(p2, score)) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /2022/src/bin/09.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | 3 | fn simulate_rope(moves: &[(i32, i32, usize)], followers: usize) -> usize { 4 | let mut rope = vec![(0i32, 0i32); followers + 1]; 5 | let mut visited = HashSet::with_capacity(10000); 6 | for &(dx, dy, len) in moves { 7 | for _ in 0..len { 8 | rope[0] = (rope[0].0 + dx, rope[0].1 + dy); 9 | for i in 1..rope.len() { 10 | let (dx, dy) = (rope[i-1].0 - rope[i].0, rope[i-1].1 - rope[i].1); 11 | if dx.abs() > 1 || dy.abs() > 1 { 12 | rope[i].0 += dx.signum(); 13 | rope[i].1 += dy.signum(); 14 | } 15 | } 16 | visited.insert(rope[followers]); 17 | } 18 | } 19 | visited.len() 20 | } 21 | 22 | #[aoc::main(09)] 23 | fn main(input: &str) -> (usize, usize) { 24 | let moves = input.lines().map(|l| { 25 | let (a, b) = l.split_once(' ').unwrap(); 26 | let (dx, dy) = match a.as_bytes()[0] as char { 27 | 'U' => ( 0, 1), 28 | 'D' => ( 0,-1), 29 | 'R' => ( 1, 0), 30 | 'L' => (-1, 0), 31 | _ => unreachable!() 32 | }; 33 | (dx, dy, b.parse::().unwrap()) 34 | }).collect::>(); 35 | (simulate_rope(&moves, 1), simulate_rope(&moves, 9)) 36 | } 37 | -------------------------------------------------------------------------------- /2023/src/bin/11.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn solve(universe: &Vec<&[u8]>, mut galaxies: Vec<(usize, usize)>, size: usize) -> usize { 4 | let (rows, cols) = (universe.len(), universe[0].len()); 5 | let empty_rows = (0..rows).filter(|&r| universe[r].iter().all(|&c| c == b'.')); 6 | let empty_cols = (0..cols).filter(|&c| (0..rows).all(|r| universe[r][c] == b'.')); 7 | for r in empty_rows.rev() { 8 | for g in &mut galaxies { 9 | if g.0 > r { g.0 += size - 1 } 10 | } 11 | } 12 | for c in empty_cols.rev() { 13 | for g in &mut galaxies { 14 | if g.1 > c { g.1 += size - 1 } 15 | } 16 | } 17 | galaxies.iter() 18 | .tuple_combinations() 19 | .map(|(&(r1,c1), &(r2,c2))| r1.abs_diff(r2) + c1.abs_diff(c2)) 20 | .sum() 21 | } 22 | 23 | #[aoc::main(11)] 24 | fn main(input: &str) -> (usize, usize) { 25 | let universe = input.split('\n').map(str::as_bytes).collect::>(); 26 | let galaxies = (0..universe.len()) 27 | .cartesian_product(0..universe[0].len()) 28 | .filter(|&(r,c)| universe[r][c] == b'#') 29 | .collect::>(); 30 | let p1 = solve(&universe, galaxies.clone(), 2); 31 | let p2 = solve(&universe, galaxies, 1000000); 32 | (p1, p2) 33 | } 34 | -------------------------------------------------------------------------------- /2021/src/bin/11.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | use itertools::Itertools; 3 | 4 | fn flash(flashed: &mut HashSet<(usize,usize)>, map: &mut [Vec], c: usize, r: usize) { 5 | if map[r][c] < 10 || !flashed.insert((r,c)) { 6 | return; 7 | } 8 | for (r,c) in [(r-1,c-1),(r-1,c),(r-1,c+1),(r,c-1),(r,c+1),(r+1,c-1),(r+1,c),(r+1,c+1)] { 9 | if r < 10 && c < 10 { 10 | map[r][c] += 1; 11 | flash(flashed, map, c, r); 12 | } 13 | } 14 | } 15 | 16 | fn round(map: &mut [Vec]) -> usize { 17 | let mut flashed = HashSet::new(); 18 | for (r,c) in (0..10).cartesian_product(0..10) { 19 | map[r][c] += 1; 20 | } 21 | for (r,c) in (0..10).cartesian_product(0..10) { 22 | flash(&mut flashed, map, c, r); 23 | } 24 | for (r,c) in (0..10).cartesian_product(0..10) { 25 | if map[r][c] > 9 { map[r][c] = 0 } 26 | } 27 | flashed.len() 28 | } 29 | 30 | #[aoc::main(11)] 31 | fn main(input: &str) -> (usize,usize) { 32 | let mut map = input.lines() 33 | .map(|l| l.bytes().map(|b| b - b'0').collect::>()) 34 | .collect::>(); 35 | let p1 = (0..100).map(|_| round(&mut map)).sum::(); 36 | let p2 = (100..).find(|_| round(&mut map) == 100).unwrap(); 37 | (p1,p2+1) 38 | } 39 | -------------------------------------------------------------------------------- /2024/src/bin/09.rs: -------------------------------------------------------------------------------- 1 | fn solve(mut files: Vec<(usize, i32)>) -> usize { 2 | let mut i = files.len() - 1; 3 | while i > 0 { 4 | let (size, id) = files[i]; 5 | if id == -1 { 6 | i -= 1; 7 | continue; 8 | } 9 | if let Some(j) = files[0..i].iter().position(|&(s, id)| id == -1 && size <= s) { 10 | let s = files[j].0; 11 | files[j] = (size, id); 12 | files[i] = (size, -1); 13 | if size < s { 14 | files.insert(j+1, (s - size, -1)); 15 | } 16 | } 17 | i -= 1; 18 | } 19 | files.iter().flat_map(|&(s, id)| (0..s).map(move |_| id)).enumerate().map(|(i, id)| if id == -1 {0} else {i * id as usize}).sum() 20 | } 21 | 22 | #[aoc::main(09)] 23 | fn main(input: &str) -> (usize, usize) { 24 | let mut fs1 = Vec::new(); 25 | let mut fs2 = Vec::new(); 26 | let mut fid = 0; 27 | for (i, b) in input.bytes().enumerate() { 28 | let v = if i % 2 == 0 {fid += 1; fid - 1} else {-1}; 29 | fs1.extend((0..b - b'0').map(|_| (1, v))); 30 | fs2.push(((b - b'0') as usize, v)); 31 | } 32 | let p1 = solve(fs1); 33 | let p2 = solve(fs2); 34 | (p1, p2) 35 | } 36 | -------------------------------------------------------------------------------- /2021/src/bin/10.rs: -------------------------------------------------------------------------------- 1 | fn line_score(s: &str) -> (Vec, Option) { 2 | let mut stack = Vec::new(); 3 | let score = s.chars().find_map(|c| { 4 | match c { 5 | '('|'{'|'['|'<' => stack.push(c), 6 | ')' => if stack.pop() != Some('(') { return Some(3) } 7 | ']' => if stack.pop() != Some('[') { return Some(57) } 8 | '}' => if stack.pop() != Some('{') { return Some(1197) } 9 | '>' => if stack.pop() != Some('<') { return Some(25137) } 10 | _ => unreachable!(), 11 | } 12 | None 13 | }); 14 | (stack, score) 15 | } 16 | 17 | fn fixed_score(stack: &[char]) -> usize { 18 | stack.iter().rev().fold(0, |s,c| match c { 19 | '(' => s * 5 + 1, 20 | '[' => s * 5 + 2, 21 | '{' => s * 5 + 3, 22 | '<' => s * 5 + 4, 23 | _ => unreachable!(), 24 | }) 25 | } 26 | 27 | #[aoc::main(10)] 28 | fn main(input: &str) -> (usize,usize) { 29 | let (mut p1, mut fixed_scores) = (0, Vec::new()); 30 | for (stack,score) in input.lines().map(line_score) { 31 | match score { 32 | Some(score) => p1 += score, 33 | None => fixed_scores.push(fixed_score(&stack)), 34 | } 35 | } 36 | fixed_scores.sort_unstable(); 37 | (p1, fixed_scores[fixed_scores.len() / 2]) 38 | } 39 | -------------------------------------------------------------------------------- /2024/src/bin/14.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn safety_factor(robots: &[(i64, i64, i64, i64)]) -> usize { 4 | let mut sectors = [0; 4]; 5 | for &(r, c, _, _) in robots { 6 | if r == 101 / 2 || c == 103 / 2 { 7 | continue; 8 | } 9 | let a = (r < 101 / 2) as usize; 10 | let b = (c < 103 / 2) as usize; 11 | sectors[a * 2 + b] += 1; 12 | } 13 | sectors.iter().product() 14 | } 15 | 16 | #[aoc::main(14)] 17 | fn main(input: &str) -> (usize, usize) { 18 | let mut robots = input 19 | .split(|c: char| !c.is_ascii_digit() && c != '-') 20 | .filter(|w| !w.is_empty()) 21 | .map(|w| w.parse::().unwrap()) 22 | .tuples() 23 | .collect::>(); 24 | let (mut p1, mut p2) = (0, 0); 25 | for i in 1.. { 26 | for (r, c, dr, dc) in &mut robots { 27 | *r = (*r + *dr).rem_euclid(101); 28 | *c = (*c + *dc).rem_euclid(103); 29 | } 30 | if i == 100 { 31 | p1 = safety_factor(&robots); 32 | } 33 | if robots.iter().map(|&(r, c, _, _)| (r, c)).all_unique() { 34 | p2 = i; 35 | break 36 | } 37 | } 38 | (p1, p2) 39 | } 40 | -------------------------------------------------------------------------------- /2022/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{parse_macro_input, AttributeArgs, ItemFn, NestedMeta, Lit, Ident}; 4 | 5 | #[proc_macro_attribute] 6 | pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { 7 | let input_path = match &parse_macro_input!(args as AttributeArgs)[..] { 8 | [NestedMeta::Lit(Lit::Int(day))] => format!("../../inputs/{}.in", day.token()), 9 | _ => panic!("Expected one integer argument"), 10 | }; 11 | 12 | let mut aoc_solution = parse_macro_input!(input as ItemFn); 13 | aoc_solution.sig.ident = Ident::new("aoc_solution", aoc_solution.sig.ident.span()); 14 | 15 | let tokens = quote! { 16 | const INPUT: &str = include_str!(#input_path); 17 | #aoc_solution 18 | fn main() { 19 | let now = ::std::time::Instant::now(); 20 | let (p1, p2) = aoc_solution(INPUT.trim_end()); 21 | let elapsed = now.elapsed(); 22 | println!("Part one: {}", p1); 23 | println!("Part two: {}", p2); 24 | if elapsed.as_millis() > 0 { 25 | println!("Time: {}ms", elapsed.as_millis()); 26 | } else { 27 | println!("Time: {}μs", elapsed.as_micros()); 28 | } 29 | } 30 | }; 31 | TokenStream::from(tokens) 32 | } 33 | -------------------------------------------------------------------------------- /2019/src/bin/01.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | const INPUT: [i32; 100] = [56583,83363,127502,138143,113987,147407,111181,92655,79802,64636,108805,148885,51022,120002,52283,53573,142374,143523,121158,63332,63203,142400,105515,140150,89910,93081,129752,86731,128755,134756,131066,77990,77081,85779,137271,72889,117608,132442,115294,59414,75495,79459,107669,81496,144432,69138,53410,71199,141799,63964,110945,102174,87697,88838,93552,145531,54602,65080,66865,139693,98048,60409,88384,138807,130854,75997,130900,125974,129123,93480,86042,128187,74981,88144,96629,148836,124473,57616,93477,104174,97407,123017,85408,64862,85298,88142,62182,128983,62981,124580,56339,94335,125521,121373,78777,125132,94411,57789,97384,79900]; 4 | 5 | fn part_one() -> i32 { 6 | INPUT.iter().map(|i| i / 3 - 2).sum::() 7 | } 8 | 9 | fn part_two() -> i32 { 10 | INPUT.iter().map(|&i| { 11 | let (mut ans, mut mass) = (0, i); 12 | loop { 13 | mass = (mass / 3) - 2; 14 | if mass <= 0 { break ans; } 15 | ans += mass; 16 | } 17 | }).sum::() 18 | } 19 | 20 | fn main() { 21 | let now = Instant::now(); 22 | println!("Part one: {}", part_one()); 23 | println!("Part two: {}", part_two()); 24 | println!("Time: {}ms", now.elapsed().as_millis()); 25 | } 26 | -------------------------------------------------------------------------------- /2021/src/bin/20.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn updated_tile(m: &[u8], grid: &[Vec], r: usize, c: usize, val: u8) -> u8 { 4 | let ns = [(r-1,c-1),(r-1,c),(r-1,c+1),(r,c-1),(r,c),(r,c+1),(r+1,c-1),(r+1,c),(r+1,c+1)]; 5 | let i = ns.iter().fold(0, |n, &(r,c)| { 6 | let x = *grid.get(r).and_then(|row| row.get(c)).unwrap_or(&val) as usize; 7 | n << 1 | x 8 | }); 9 | (m[i] == b'#') as u8 10 | } 11 | 12 | fn enhance(m: &[u8], grid: &[Vec], val: u8) -> Vec> { 13 | let mut ans = vec![vec![0;grid[0].len()+2];grid.len()+2]; 14 | for (r,c) in (0..ans.len()).cartesian_product(0..ans[0].len()) { 15 | ans[r][c] = updated_tile(m, grid, r-1, c-1, val); 16 | } 17 | ans 18 | } 19 | 20 | #[aoc::main(20)] 21 | fn main(input: &str) -> (usize,usize) { 22 | let (m, rest) = input.split_once("\n\n").unwrap(); 23 | let mut grid = rest.lines() 24 | .map(|l| l.chars().map(|c| (c == '#') as u8).collect()) 25 | .collect::>(); 26 | for i in 0..2 { grid = enhance(m.as_bytes(), &grid, i & 1) } 27 | let p1 = grid.iter().flatten().filter(|&&b| b == 1).count(); 28 | for i in 2..50 { grid = enhance(m.as_bytes(), &grid, i & 1) } 29 | let p2 = grid.iter().flatten().filter(|&&b| b == 1).count(); 30 | (p1, p2) 31 | } 32 | -------------------------------------------------------------------------------- /2016/src/day05.ml: -------------------------------------------------------------------------------- 1 | let input = "uqwqemis" 2 | 3 | let chars_to_str cs = cs |> List.map (String.make 1) |> String.concat "" 4 | 5 | let rec next_hash c = 6 | let h = input ^ (string_of_int c) |> Digest.string |> Digest.to_hex in 7 | let valid = [0;1;2;3;4] |> List.for_all (fun i -> h.[i] = '0') in 8 | if valid then (c+1, h.[5], h.[6]) else next_hash (c+1) 9 | 10 | let part1 () = 11 | let c1,i1,_ = next_hash 0 in 12 | let c2,i2,_ = next_hash c1 in 13 | let c3,i3,_ = next_hash c2 in 14 | let c4,i4,_ = next_hash c3 in 15 | let c5,i5,_ = next_hash c4 in 16 | let c6,i6,_ = next_hash c5 in 17 | let c7,i7,_ = next_hash c6 in 18 | let _, i8,_ = next_hash c7 in 19 | chars_to_str [i1;i2;i3;i4;i5;i6;i7;i8] 20 | 21 | let rec find_pw digits c = 22 | let c_next, i, d = next_hash c in 23 | let i = Char.code i - 48 in 24 | if i < 9 && digits.(i) = None then 25 | let () = digits.(i) <- Some(d) in 26 | if digits |> Array.for_all Option.is_some then digits 27 | else find_pw digits c_next 28 | else 29 | find_pw digits c_next 30 | 31 | let part2 () = find_pw (Array.make 8 None) 0 32 | |> Array.map Option.get 33 | |> Array.to_list 34 | |> chars_to_str 35 | 36 | let main () = (part1 (), part2 ()) 37 | 38 | let () = Aoc.timer main 39 | -------------------------------------------------------------------------------- /2017/src/10.hs: -------------------------------------------------------------------------------- 1 | import Aoc 2 | import Data.Function 3 | import Data.Char 4 | import Numeric (showHex) 5 | import Data.List.Split (chunksOf, splitOn) 6 | import Data.Bits (xor) 7 | 8 | input = "192,69,168,160,78,1,166,28,0,83,198,2,254,255,41,12" 9 | 10 | performKnot :: ([Int], Int, Int) -> Int -> ([Int], Int, Int) 11 | performKnot (lst, i, skip) len = (newList, (i+skip+len) `mod` 256, skip+1) 12 | where 13 | (a,tmp) = splitAt i lst 14 | (b,c) = splitAt len (tmp++lst) 15 | revList = a ++ reverse b ++ c 16 | newList = 17 | let spillover = i + len - 256 in 18 | let part = take spillover (drop 256 revList) in 19 | take 256 (part ++ drop spillover revList) 20 | 21 | knotHash :: String -> String 22 | knotHash s = chunksOf 16 lst & concatMap (toHex . foldl xor 0) 23 | where 24 | lengths = map ord s ++ [17, 31, 73, 47, 23] 25 | (lst,_,_) = concatMap (const lengths) [1..64] & foldl performKnot ([0..255],0,0) 26 | toHex i = (if i < 0x10 then "0" else "") ++ showHex i "" 27 | 28 | solveParts :: Int -> (Int,String) 29 | solveParts _ = (part1, knotHash input) 30 | where 31 | (lst,_,_) = splitOn "," input & map read & foldl performKnot ([0..255],0,0) 32 | part1 = head lst * lst!!1 33 | 34 | main = Aoc.timer solveParts 35 | -------------------------------------------------------------------------------- /2018/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2018 :snake: 2 | Solutions to all 25 AoC 2018 problems in modern, type-annotated Python 3.10! 3 | 4 | Finishing this year finally gave me the last stars of Advent of Code, I now have all 350 :star: I wanted to finish AoC off in Python to see what it would be like to maybe switch from Rust to Python for AoC 2022. I must say that using Python for AoC is a delight. Things are so simple, while still being really expressive. Unsurprisingly, you skip so much of the boilerplate compared to something like Rust. If I ever want to get a top 100 placing I will probably have to use Python. 5 | 6 | I wanted to try the newest version of Python, `3.10`, which finally adds pattern matching to Python. It was nice but I failed to find a usecase in these challenges beyond just strictly comparing values. Using such a recent version of Python was a bit annoying though, I could not get `pip` working and `mypy` does not support the new syntax yet. 7 | 8 | ## Usage 9 | ```sh 10 | # run a solution 11 | python3 src/.py 12 | 13 | # run all solutions, and run static type checking on each 14 | for DAY in src/*.py; do 15 | echo -e "\nRunning $DAY" 16 | python3 $DAY 17 | mypy $DAY 18 | done 19 | ``` 20 | 21 | ![end-screen](../imgs/end-screen-2018.png) 22 | -------------------------------------------------------------------------------- /2023/src/bin/13.rs: -------------------------------------------------------------------------------- 1 | fn find_col(grid: &[&[u8]], limit: usize) -> Option { 2 | (0..grid[0].len()-1).find(|&c| { 3 | let incorrect = (0..=c.min(grid[0].len() - c - 2)).map(|dc| { 4 | let a = c - dc; 5 | let b = c + 1 + dc; 6 | (0..grid.len()).filter(|&r| grid[r][a] != grid[r][b]).count() 7 | }).sum::(); 8 | incorrect == limit 9 | }) 10 | } 11 | 12 | fn find_row(grid: &[&[u8]], limit: usize) -> Option { 13 | (0..grid.len()-1).find(|&r| { 14 | let incorrect = (0..=r.min(grid.len() - r - 2)).map(|dr| { 15 | let a = r - dr; 16 | let b = r + 1 + dr; 17 | (0..grid[0].len()).filter(|&c| grid[a][c] != grid[b][c]).count() 18 | }).sum::(); 19 | incorrect == limit 20 | }) 21 | } 22 | 23 | fn solve(grids: &[Vec<&[u8]>], limit: usize) -> usize { 24 | grids.iter().map(|grid| 25 | find_row(grid, limit).map(|r| (r + 1) * 100) 26 | .or_else(|| find_col(grid, limit).map(|c| c + 1)) 27 | .unwrap() 28 | ).sum() 29 | } 30 | 31 | #[aoc::main(13)] 32 | fn main(input: &str) -> (usize, usize) { 33 | let grids = input.split("\n\n").map(|s| 34 | s.split('\n').map(|l| l.as_bytes()).collect::>() 35 | ).collect::>(); 36 | (solve(&grids, 0), solve(&grids, 1)) 37 | } 38 | -------------------------------------------------------------------------------- /2020/src/bin/23.rs: -------------------------------------------------------------------------------- 1 | static INPUT: [usize; 9] = [4,6,3,5,2,8,1,7,9]; 2 | 3 | fn build_list(size: usize) -> Vec { 4 | let mut v = vec![0; size+1]; 5 | let last = (1..size).fold(INPUT[0], |curr, i| { 6 | v[curr] = if i < 9 {INPUT[i]} else {i+1}; 7 | v[curr] 8 | }); 9 | v[last] = INPUT[0]; 10 | v 11 | } 12 | 13 | fn simulate_game(size: usize, rounds: usize) -> Vec { 14 | let mut list = build_list(size); 15 | let mut curr = INPUT[0]; 16 | for _ in 0..rounds { 17 | let (a,b,c) = (list[curr], list[list[curr]], list[list[list[curr]]]); 18 | let mut t = if curr == 1 {size} else {curr-1}; 19 | while t == a || t == b || t == c { 20 | t = if t == 1 {size} else {t-1}; 21 | } 22 | list[curr] = list[c]; 23 | list[c] = list[t]; 24 | list[t] = a; 25 | curr = list[curr]; 26 | } 27 | list 28 | } 29 | 30 | fn part_one() -> usize { 31 | let list = simulate_game(9, 100); 32 | let (mut ans, mut curr) = (0, list[1]); 33 | while curr != 1 { 34 | ans = ans * 10 + curr; 35 | curr = list[curr]; 36 | } 37 | ans 38 | } 39 | 40 | fn part_two() -> usize { 41 | let list = simulate_game(1000000, 10000000); 42 | list[1] * list[list[1]] 43 | } 44 | 45 | aoc2020::main! { 46 | (part_one(), part_two()) 47 | } 48 | -------------------------------------------------------------------------------- /2015/src/09.clj: -------------------------------------------------------------------------------- 1 | (require '[clojure.math.combinatorics :as combo]) 2 | 3 | (def input '(("Faerun" "Norrath" 129) ("Faerun" "Tristram" 58) ("Faerun" "AlphaCentauri" 13) ("Faerun" "Arbre" 24) ("Faerun" "Snowdin" 60) ("Faerun" "Tambi" 71) ("Faerun" "Straylight" 67) ("Norrath" "Tristram" 142) ("Norrath" "AlphaCentauri" 15) ("Norrath" "Arbre" 135) ("Norrath" "Snowdin" 75) ("Norrath" "Tambi" 82) ("Norrath" "Straylight" 54) ("Tristram" "AlphaCentauri" 118) ("Tristram" "Arbre" 122) ("Tristram" "Snowdin" 103) ("Tristram" "Tambi" 49) ("Tristram" "Straylight" 97) ("AlphaCentauri" "Arbre" 116) ("AlphaCentauri" "Snowdin" 12) ("AlphaCentauri" "Tambi" 18) ("AlphaCentauri" "Straylight" 91) ("Arbre" "Snowdin" 129) ("Arbre" "Tambi" 53) ("Arbre" "Straylight" 40) ("Snowdin" "Tambi" 15) ("Snowdin" "Straylight" 99) ("Tambi" "Straylight" 70))) 4 | 5 | (defn add-edge [g [a b d]] 6 | (assoc g [a b] d [b a] d)) 7 | 8 | (def graph (reduce add-edge {} input)) 9 | 10 | (def cities (->> graph keys (map first) set)) 11 | 12 | (defn distance [cities] 13 | (->> cities (partition 2 1) (map graph) (reduce +))) 14 | 15 | (defn find-distance [f] 16 | (->> cities combo/permutations (map distance) (apply f))) 17 | 18 | (->> min find-distance (println "Part one:")) 19 | (->> max find-distance (println "Part two:")) 20 | -------------------------------------------------------------------------------- /2024/src/bin/04.rs: -------------------------------------------------------------------------------- 1 | fn get(m: &[&[u8]], r: usize, c: usize) -> u8 { 2 | *m.get(r).and_then(|row| row.get(c)).unwrap_or(&b'_') 3 | } 4 | 5 | fn find_xmas(m: &[&[u8]], r: usize, c: usize) -> usize { 6 | [(0, -1), (-1, 0), (0, 1), (1, 0), (1,1), (-1,1), (1,-1), (-1,-1)].iter() 7 | .filter(|(dr, dc)| 8 | (1..4).all(|i| { 9 | let (rr, cc) = (r + (dr * i) as usize, c + (dc * i) as usize); 10 | get(m, rr, cc) == b"XMAS"[i as usize] 11 | }) 12 | ) 13 | .count() 14 | } 15 | 16 | fn find_x_mas(m: &[&[u8]], r: usize, c: usize) -> bool { 17 | let w1 = [get(m, r-1, c-1), get(m, r+1, c+1)]; 18 | let w2 = [get(m, r-1, c+1), get(m, r+1, c-1)]; 19 | [w1, w2].iter().all(|w| w == b"MS" || w == b"SM") 20 | } 21 | 22 | #[aoc::main(04)] 23 | fn main(input: &str) -> (usize, usize) { 24 | let m = input.lines().map(|l| l.as_bytes()).collect::>(); 25 | let (mut p1, mut p2) = (0, 0); 26 | for r in 0..m.len() { 27 | for c in 0..m[0].len() { 28 | match m[r][c] { 29 | b'X' => p1 += find_xmas(&m, r, c), 30 | b'A' => p2 += find_x_mas(&m, r, c) as usize, 31 | _ => {}, 32 | } 33 | } 34 | } 35 | (p1, p2) 36 | } 37 | -------------------------------------------------------------------------------- /2018/src/11.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from itertools import product 3 | 4 | def part1(dp: list[list[list[int]]]) -> str: 5 | xmax,ymax,v = 0,0,0 6 | for x,y in product(range(1,297), repeat=2): 7 | if dp[x][y][3] > v: 8 | xmax,ymax,v = x,y,dp[x][y][3] 9 | return f"{xmax},{ymax}" 10 | 11 | def part2(dp: list[list[list[int]]]) -> str: 12 | xmax,ymax,sizemax,v = 0,0,0,0 13 | for size in range(2,300): 14 | for x,y in product(range(1,300-size), repeat=2): 15 | if dp[x][y][size] > v: 16 | xmax,ymax,sizemax,v = x,y,size,dp[x][y][size] 17 | return f"{xmax},{ymax},{sizemax}" 18 | 19 | @aoc.main('11') 20 | def main(indata: str) -> tuple[str,str]: 21 | rackid = int(indata) 22 | dp = [[[0]*300 for _ in range(300)] for _ in range(300)] 23 | for x,y in product(range(1,300), repeat=2): 24 | dp[x][y][1] = (((x+10) * y + rackid) * (x+10) // 100) % 10 - 5 25 | for size in range(2,300): 26 | for x,y in product(range(1,300-size), repeat=2): 27 | dp[x][y][size] = dp[x][y][1] \ 28 | + dp[x+1][y][size-1] \ 29 | + dp[x][y+1][size-1] \ 30 | - dp[x+1][y+1][size-2] \ 31 | + dp[x+size-1][y+size-1][1] 32 | return part1(dp), part2(dp) 33 | 34 | if __name__ == "__main__": 35 | main() 36 | -------------------------------------------------------------------------------- /2023/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, process::Command, error::Error}; 2 | use itertools::Itertools; 3 | 4 | fn extract_microseconds(output: &str) -> Result> { 5 | let out = output.lines().last().unwrap(); 6 | let time = if out.ends_with("ms") { 7 | out["Time: ".len()..out.len()-2].parse::()? * 1000 8 | } else { 9 | out["Time: ".len()..out.len()-3].parse::()? 10 | }; 11 | Ok(time) 12 | } 13 | 14 | fn main() -> Result<(), Box> { 15 | let days = fs::read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/"))? 16 | .filter_map(|p| p.ok()?.path().file_stem()?.to_str().map(str::to_string)) 17 | .filter(|day| day.chars().all(|c| c.is_ascii_digit())) 18 | .sorted() 19 | .collect::>(); 20 | let mut total_time = 0; 21 | for day in &days { 22 | let cmd = Command::new("cargo").args(["run", "--release", "--bin", day]).output()?; 23 | if !cmd.status.success() { 24 | println!("{}", String::from_utf8(cmd.stderr)?); 25 | return Err(format!("Failed to compile day {day}!").into()); 26 | } 27 | let output = String::from_utf8(cmd.stdout)?; 28 | println!("Day {}:\n{}", day, output); 29 | total_time += extract_microseconds(&output)?; 30 | } 31 | println!("Total time: {}ms", total_time / 1000); 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /2018/src/17.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | import re 3 | import sys 4 | from collections import defaultdict 5 | sys.setrecursionlimit(10000) 6 | 7 | def fill(m: dict[tuple[int,int],str], x: int, y: int, d: int) -> None: 8 | while m[x,y] == '|': 9 | m[x,y] = '~' 10 | x += d 11 | 12 | def flow(m: dict[tuple[int,int],str], ymax: int, x: int, y: int, d: int) -> bool: 13 | if y > ymax: 14 | return True 15 | if m[x,y] != '.': 16 | return m[x,y] == '|' 17 | 18 | m[x,y] = '|' 19 | if flow(m,ymax,x,y+1,0): 20 | return True 21 | 22 | if (d != 1 and flow(m,ymax,x-1,y,-1)) | (d != -1 and flow(m,ymax,x+1,y,1)): 23 | return True 24 | 25 | if d == 0: 26 | fill(m,x,y,-1) 27 | fill(m,x+1,y,1) 28 | return False 29 | 30 | @aoc.main('17') 31 | def main(indata: str) -> tuple[int,int]: 32 | m = defaultdict(lambda: '.') 33 | for l in indata.split('\n'): 34 | s,a,b = [int(i) for i in re.findall("\d+",l)] 35 | hori = l[0] == 'x' 36 | for t in range(a,b+1): 37 | m[(s,t) if hori else (t,s)] = '#' 38 | ymin, ymax = min(y for _,y in m), max(y for _,y in m) 39 | flow(m,ymax,500,0,0) 40 | water = sum(m[x,y] == '~' for x,y in m if y >= ymin) 41 | flows = sum(m[x,y] == '|' for x,y in m if y >= ymin) 42 | return water+flows, water 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /2025/src/bin/04.rs: -------------------------------------------------------------------------------- 1 | const D: [(i64, i64); 8] = [ 2 | (-1, -1), (-1, 0), (-1, 1), 3 | ( 0, -1), ( 0, 1), 4 | ( 1, -1), ( 1, 0), ( 1, 1), 5 | ]; 6 | 7 | fn round(m: &mut [Vec], remove: bool) -> usize { 8 | let mut res = 0; 9 | for r in 0..m.len() { 10 | for c in 0..m[0].len() { 11 | if m[r][c] != b'@' { 12 | continue; 13 | } 14 | let n = D.iter().filter(|&&(dr, dc)| 15 | m.get((r as i64 + dr) as usize) 16 | .and_then(|row| row.get((c as i64 + dc) as usize)) 17 | .is_some_and(|&x| x == b'@') 18 | ).count(); 19 | if n < 4 { 20 | if remove { 21 | m[r][c] = b'.'; 22 | } 23 | res += 1; 24 | } 25 | } 26 | } 27 | res 28 | } 29 | 30 | #[aoc::main(04)] 31 | fn main(input: &str) -> (usize, usize) { 32 | let (mut p1, mut p2) = (0, 0); 33 | let mut m = input.split('\n') 34 | .map(|l| l.as_bytes().to_vec()) 35 | .collect::>(); 36 | p1 += round(&mut m, false); 37 | loop { 38 | let n = round(&mut m, true); 39 | if n == 0 { 40 | break; 41 | } 42 | p2 += n; 43 | } 44 | (p1, p2) 45 | } 46 | -------------------------------------------------------------------------------- /2023/src/bin/08.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | fn gcd(a: usize, b: usize) -> usize { 4 | match ((a, b), (a & 1, b & 1)) { 5 | _ if a == b => a, 6 | ((_, 0), _) => a, 7 | ((0, _), _) => b, 8 | (_, (0, 1) | (1, 0)) => gcd(a >> 1, b), 9 | (_, (0, 0)) => gcd(a >> 1, b >> 1) << 1, 10 | (_, (1, 1)) => { 11 | let (a, b) = (a.min(b), a.max(b)); 12 | gcd((b - a) >> 1, a) 13 | } 14 | _ => unreachable!(), 15 | } 16 | } 17 | 18 | fn steps(path: &[u8], graph: &HashMap<&[u8],(&[u8],&[u8])>, start: &[u8], goal: &[u8]) -> usize { 19 | let (mut n, mut i) = (start, 1); 20 | for &d in path.iter().cycle() { 21 | n = if d == b'L' {graph[n].0} else {graph[n].1}; 22 | if n.ends_with(goal) { 23 | break; 24 | } 25 | i += 1; 26 | } 27 | i 28 | } 29 | 30 | #[aoc::main(08)] 31 | fn main(input: &str) -> (usize, usize) { 32 | let (path, rest) = input.split_once("\n\n").unwrap(); 33 | let graph = rest.split('\n').map(|l| { 34 | let l = l.as_bytes(); 35 | (&l[0..3], (&l[7..10], &l[12..15])) 36 | }).collect::>(); 37 | let p2 = graph.keys() 38 | .filter(|k| k.ends_with(b"A")) 39 | .map(|node| steps(path.as_bytes(), &graph, node, b"Z")) 40 | .fold(1, |ans, x| (x*ans) / gcd(x,ans)); 41 | (steps(path.as_bytes(), &graph, b"AAA", b"ZZZ"), p2) 42 | } 43 | -------------------------------------------------------------------------------- /2024/src/bin/08.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::{HashMap, HashSet}; 2 | use itertools::Itertools; 3 | 4 | #[aoc::main(08)] 5 | fn main(input: &str) -> (usize, usize) { 6 | let grid = input.lines().map(|l| l.as_bytes()).collect::>(); 7 | let mut nodes = HashMap::<_, Vec<_>>::new(); 8 | for i in 0..grid.len() { 9 | for j in 0..grid[0].len() { 10 | if grid[i][j] != b'.' { 11 | nodes.entry(grid[i][j]).or_default().push((i, j)); 12 | } 13 | } 14 | } 15 | 16 | let (mut p1, mut p2) = (HashSet::new(), HashSet::new()); 17 | for v in nodes.values() { 18 | for (&a, &b) in v.iter().tuple_combinations() { 19 | for ((r1, c1), (r2, c2)) in [(a, b), (b, a)] { 20 | let (dr, dc) = (r2 - r1, c2 - c1); 21 | let mut c = c2 + dc; 22 | let mut r = r2 + dr; 23 | 24 | if (0..grid.len()).contains(&r) && (0..grid[0].len()).contains(&c) { 25 | p1.insert((r, c)); 26 | } 27 | 28 | p2.insert((r2, c2)); 29 | while (0..grid.len()).contains(&r) && (0..grid[0].len()).contains(&c) { 30 | p2.insert((r, c)); 31 | c += dc; 32 | r += dr; 33 | } 34 | } 35 | } 36 | } 37 | (p1.len(), p2.len()) 38 | } 39 | -------------------------------------------------------------------------------- /2024/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, process::Command, error::Error}; 2 | use itertools::Itertools; 3 | 4 | fn extract_microseconds(output: &str) -> Result> { 5 | let out = output.lines().last().unwrap(); 6 | let time = if out.ends_with("ms") { 7 | out["Time: ".len()..out.len()-2].parse::()? * 1000 8 | } else { 9 | out["Time: ".len()..out.len()-3].parse::()? 10 | }; 11 | Ok(time) 12 | } 13 | 14 | fn main() -> Result<(), Box> { 15 | let days = fs::read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/"))? 16 | .filter_map(|p| p.ok()?.path().file_stem()?.to_str().map(str::to_string)) 17 | .filter(|day| day.chars().all(|c| c.is_ascii_digit())) 18 | .sorted() 19 | .collect::>(); 20 | let mut total_time = 0; 21 | for day in &days { 22 | let cmd = Command::new("cargo").args(["run", "--release", "--bin", day]).output()?; 23 | if !cmd.status.success() { 24 | println!("{}", String::from_utf8(cmd.stderr)?); 25 | return Err(format!("Failed to compile day {day}!").into()); 26 | } 27 | let output = String::from_utf8(cmd.stdout)?; 28 | println!("Day {}:\n{}", day, output); 29 | total_time += extract_microseconds(&output)?; 30 | } 31 | println!("Total time: {}ms", total_time / 1000); 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /2025/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, process::Command, error::Error}; 2 | use itertools::Itertools; 3 | 4 | fn extract_microseconds(output: &str) -> Result> { 5 | let out = output.lines().last().unwrap(); 6 | let time = if out.ends_with("ms") { 7 | out["Time: ".len()..out.len()-2].parse::()? * 1000 8 | } else { 9 | out["Time: ".len()..out.len()-3].parse::()? 10 | }; 11 | Ok(time) 12 | } 13 | 14 | fn main() -> Result<(), Box> { 15 | let days = fs::read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/"))? 16 | .filter_map(|p| p.ok()?.path().file_stem()?.to_str().map(str::to_string)) 17 | .filter(|day| day.chars().all(|c| c.is_ascii_digit())) 18 | .sorted() 19 | .collect::>(); 20 | let mut total_time = 0; 21 | for day in &days { 22 | let cmd = Command::new("cargo").args(["run", "--release", "--bin", day]).output()?; 23 | if !cmd.status.success() { 24 | println!("{}", String::from_utf8(cmd.stderr)?); 25 | return Err(format!("Failed to compile day {day}!").into()); 26 | } 27 | let output = String::from_utf8(cmd.stdout)?; 28 | println!("Day {}:\n{}", day, output); 29 | total_time += extract_microseconds(&output)?; 30 | } 31 | println!("Total time: {}ms", total_time / 1000); 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /2016/src/day17.ml: -------------------------------------------------------------------------------- 1 | let input = "bwnlcvfs" 2 | 3 | let valid_neighbours ((x,y),s) = 4 | let h = s |> Digest.string |> Digest.to_hex in 5 | [ x, y-1, "U", h.[0]; 6 | x, y+1, "D", h.[1]; 7 | x-1, y, "L", h.[2]; 8 | x+1, y, "R", h.[3] ] 9 | |> List.filter (fun (x,y,_,_) -> -1 < x && x < 4 && -1 < y && y < 4) 10 | |> List.filter (fun (_,_,_,c) -> String.contains "bcdef" c) 11 | |> List.map (fun (x,y,d,_) -> ((x,y),s ^ d)) 12 | 13 | let rec bfs_part_1 queue curr = 14 | if fst curr = (3,3) then snd curr 15 | else 16 | let neighbours = valid_neighbours curr in 17 | neighbours |> List.to_seq |> Queue.add_seq queue; 18 | bfs_part_1 queue (Queue.take queue) 19 | 20 | let rec bfs_part_2 longest queue curr = 21 | let longest = 22 | if fst curr = (3,3) then 23 | let (l1,l2) = (String.length longest, String.length (snd curr)) in 24 | if l1 < l2 then snd curr else longest 25 | else ( 26 | curr |> valid_neighbours |> List.to_seq |> Queue.add_seq queue; 27 | longest 28 | ) 29 | in 30 | match Queue.take_opt queue with 31 | | Some next -> bfs_part_2 longest queue next 32 | | None -> longest 33 | 34 | let main () = 35 | let part1 = bfs_part_1 (Queue.create ()) ((0,0),input) in 36 | let part2 = bfs_part_2 "" (Queue.create ()) ((0,0),input) |> String.length in 37 | (part1, part2 - 8 |> string_of_int) 38 | 39 | let () = Aoc.timer main 40 | -------------------------------------------------------------------------------- /2025/src/bin/06.rs: -------------------------------------------------------------------------------- 1 | fn ans(problems: &[Vec], ops: &[u8]) -> usize { 2 | problems.iter().zip(ops) 3 | .map(|(p, o)| match o { 4 | b'*' => p.iter().product::(), 5 | b'+' => p.iter().sum::(), 6 | _ => unreachable!() 7 | }) 8 | .sum() 9 | } 10 | 11 | #[aoc::main(06)] 12 | fn main(input: &str) -> (usize, usize) { 13 | let lines = input.split('\n').collect::>(); 14 | let ops = lines[lines.len()-1].bytes().filter(|&c| c != b' ').collect::>(); 15 | 16 | let mut p1 = Vec::new(); 17 | for l in &lines[0..lines.len()-1] { 18 | let ws = l.split_ascii_whitespace().collect::>(); 19 | for i in 0..ws.len() { 20 | if p1.len() <= i { 21 | p1.push(Vec::new()); 22 | } 23 | p1[i].push(ws[i].parse().unwrap()) 24 | } 25 | } 26 | 27 | let mut p2 = Vec::from([Vec::new()]); 28 | for c in 0..lines[0].len() { 29 | let w = (0..lines.len()-1) 30 | .map(|r| lines[r].as_bytes()[c] as char) 31 | .filter(|&c| c.is_ascii_digit()) 32 | .collect::(); 33 | if !w.is_empty() { 34 | p2.last_mut().unwrap().push(w.parse().unwrap()); 35 | } else { 36 | p2.push(Vec::new()); 37 | } 38 | } 39 | 40 | (ans(&p1, &ops), ans(&p2, &ops)) 41 | } 42 | -------------------------------------------------------------------------------- /2020/src/bin/01.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | static INPUT: [u32; 200] = [1863,1750,1767,1986,1180,1719,1946,1866,1939,1771,1766,1941,1728,1322,1316,1775,1776,1742,1726,1994,1949,1318,1223,1741,1816,1111,1991,1406,1230,1170,1823,1792,1148,1953,1706,1724,1307,1844,1943,1862,1812,1286,1837,1785,1998,1938,1248,1822,1829,1903,1131,1826,1892,1143,1898,1981,1225,1980,1850,1885,324,289,1914,1249,1848,1995,1962,1875,1827,1931,1244,1739,1897,1687,1907,1867,1922,1972,1842,1757,1610,1945,1835,1894,1265,1872,1963,1712,891,1813,1800,1235,1879,1732,1522,1335,1936,1830,1772,1700,2005,1253,1836,1935,1137,1951,1849,1883,1192,1824,1918,1965,1759,1195,1882,1748,1168,1200,1761,1896,527,1769,1560,1947,1997,1461,1828,1801,1877,1900,1924,1782,1718,515,1814,1744,1126,1791,1149,1932,1690,1707,1808,1957,1313,1132,1942,1934,1798,2009,1708,1774,1710,1797,1747,959,1955,1717,1716,1290,1654,1857,1968,1874,1853,1175,1493,1425,1125,1973,1790,467,1804,987,1944,2001,1895,1917,1218,1147,1884,1819,1179,1859,620,1219,2008,1871,1852,1263,1751,1989,1381,1250,1754,1725,1665,1352,1805,1325]; 4 | 5 | aoc2020::main! { 6 | let part_one = INPUT.iter() 7 | .tuple_combinations() 8 | .find(|&(a,b)| a + b == 2020) 9 | .map(|(a,b)| a * b); 10 | let part_two = INPUT.iter() 11 | .tuple_combinations() 12 | .find(|&(a,b,c)| a + b + c == 2020) 13 | .map(|(a,b,c)| a * b * c); 14 | (part_one.unwrap(), part_two.unwrap()) 15 | } 16 | -------------------------------------------------------------------------------- /2021/src/bin/09.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | use itertools::Itertools; 3 | 4 | fn part1(grid: &[Vec]) -> usize { 5 | let mut ans = 0; 6 | for (x,y) in (0..grid[0].len()).cartesian_product(0..grid.len()) { 7 | let is_low = [(x-1,y),(x+1,y),(x,y-1),(x,y+1)].iter() 8 | .filter_map(|&(x,y)| grid.get(y as usize).and_then(|line| line.get(x as usize))) 9 | .all(|&i| i > grid[y][x]); 10 | if is_low { ans += grid[y][x] as usize + 1; } 11 | } 12 | ans 13 | } 14 | 15 | fn remove_component((x,y): (usize,usize), coords: &mut HashSet<(usize,usize)>) -> usize { 16 | if !coords.remove(&(x,y)) { 17 | return 0; 18 | } 19 | 1 + [(x-1,y),(x+1,y),(x,y-1),(x,y+1)].iter() 20 | .map(|&neighbour| remove_component(neighbour, coords)) 21 | .sum::() 22 | } 23 | 24 | fn part2(grid: &[Vec]) -> usize { 25 | let mut points = (0..grid[0].len()).cartesian_product(0..grid.len()) 26 | .filter(|&(x,y)| grid[y][x] != 9) 27 | .collect::>(); 28 | let mut cs = vec![]; 29 | while let Some(&p) = points.iter().next() { 30 | cs.push(remove_component(p, &mut points)); 31 | } 32 | cs.iter().sorted().rev().take(3).product() 33 | } 34 | 35 | #[aoc::main(09)] 36 | fn main(input: &str) -> (usize,usize) { 37 | let grid = input.lines() 38 | .map(|l| l.bytes().map(|c| c - b'0').collect()) 39 | .collect::>(); 40 | (part1(&grid), part2(&grid)) 41 | } 42 | -------------------------------------------------------------------------------- /2021/src/bin/14.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use itertools::Itertools; 3 | 4 | fn simulate_polymer(init: &str, recipes: &HashMap<(char,char),char>, steps: usize) -> usize { 5 | let init_count = init.chars().tuple_windows().counts(); 6 | let pair_counts = (0..steps).fold(init_count, |counts, _| { 7 | let mut next_counts = HashMap::new(); 8 | for (&(a,b),count) in &counts { 9 | let c = recipes[&(a,b)]; 10 | *next_counts.entry((a,c)).or_insert(0) += count; 11 | *next_counts.entry((c,b)).or_insert(0) += count; 12 | } 13 | next_counts 14 | }); 15 | let mut count = HashMap::new(); 16 | for (&(a,_), c) in &pair_counts { 17 | *count.entry(a).or_insert(0) += c; 18 | } 19 | *count.entry(init.chars().last().unwrap()).or_insert(0) += 1; 20 | let (min,max) = count.values().minmax().into_option().unwrap(); 21 | max - min 22 | } 23 | 24 | #[aoc::main(14)] 25 | fn main(input: &str) -> (usize,usize) { 26 | let (init, ingredients) = input.split_once("\n\n").unwrap(); 27 | let recipes = ingredients.lines() 28 | .map(|l| { 29 | let (a,b) = l.split_once(" -> ").unwrap(); 30 | let k = (a.as_bytes()[0] as char, a.as_bytes()[1] as char); 31 | (k, b.as_bytes()[0] as char) 32 | }) 33 | .collect::>(); 34 | let p1 = simulate_polymer(init, &recipes, 10); 35 | let p2 = simulate_polymer(init, &recipes, 40); 36 | (p1,p2) 37 | } 38 | -------------------------------------------------------------------------------- /2021/src/bin/13.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | 3 | fn fold_grid(points: &HashSet<(i32,i32)>, (dir,pos): (char,i32)) -> HashSet<(i32,i32)> { 4 | points.iter().map(|&(x,y)| match (dir,x,y) { 5 | ('x',x,y) if x < pos => (x,y), 6 | ('y',x,y) if y < pos => (x,y), 7 | ('x',x,y) => (pos*2 - x,y), 8 | ('y',x,y) => (x,pos*2 - y), 9 | _ => unreachable!() 10 | }).collect() 11 | } 12 | 13 | fn grid_to_string(grid: &HashSet<(i32,i32)>) -> String { 14 | let xmax = grid.iter().map(|&(x,_)| x).max().unwrap(); 15 | let ymax = grid.iter().map(|&(_,y)| y).max().unwrap(); 16 | let mut s = "\n".to_string(); 17 | for y in 0..=ymax { 18 | for x in 0..=xmax { 19 | s += if grid.contains(&(x,y)) {"█"} else {" "}; 20 | } 21 | s += "\n"; 22 | } 23 | s 24 | } 25 | 26 | #[aoc::main(13)] 27 | fn main(input: &str) -> (usize,String) { 28 | let (points, folds) = input.split_once("\n\n").unwrap(); 29 | let grid = points.lines() 30 | .map(|l| { 31 | let (a,b) = l.split_once(',').unwrap(); 32 | (a.parse().unwrap(), b.parse().unwrap()) 33 | }) 34 | .collect(); 35 | let folds = folds.lines() 36 | .map(|s| (s.as_bytes()[11] as char, s[13..].parse().unwrap())) 37 | .collect::>(); 38 | let p1 = fold_grid(&grid, folds[0]).len(); 39 | let final_grid = folds.iter().fold(grid, |grid, &fold| fold_grid(&grid, fold)); 40 | (p1, grid_to_string(&final_grid)) 41 | } 42 | -------------------------------------------------------------------------------- /2023/src/bin/03.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | fn find_symbol(lines: &[&[u8]], r: usize, c: usize) -> Option<(usize, usize, char)> { 4 | for (dr, dc) in [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)] { 5 | let rr = (r as i32 + dr) as usize; 6 | let cc = (c as i32 + dc) as usize; 7 | let Some(&s) = lines.get(rr).and_then(|l| l.get(cc)) else { continue }; 8 | if s != b'.' && !s.is_ascii_digit() { 9 | return Some((cc, rr, s as char)); 10 | } 11 | } 12 | None 13 | } 14 | 15 | #[aoc::main(03)] 16 | fn main(input: &str) -> (usize, usize) { 17 | let lines = input.split('\n').map(str::as_bytes).collect::>(); 18 | let mut symbols = HashMap::<_,Vec<_>>::new(); 19 | for (r, l) in lines.iter().enumerate() { 20 | let mut c = 0; 21 | while c < l.len() { 22 | let (start, mut symbol) = (c, None); 23 | while c < l.len() && l[c].is_ascii_digit() { 24 | if symbol.is_none() { 25 | symbol = find_symbol(&lines, r, c); 26 | } 27 | c += 1; 28 | } 29 | if let Some(symbol) = symbol { 30 | let num = l[start..c].iter().fold(0, |n, c| n * 10 + (c - b'0') as usize); 31 | symbols.entry(symbol).or_default().push(num); 32 | } 33 | c += 1; 34 | } 35 | } 36 | let p2 = symbols.iter() 37 | .filter(|(&(_,_,s),v)| s == '*' && v.len() == 2) 38 | .map(|(_,v)| v[0] * v[1]) 39 | .sum(); 40 | (symbols.values().flatten().sum(), p2) 41 | } 42 | -------------------------------------------------------------------------------- /2022/src/bin/14.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::max; 2 | use itertools::Itertools; 3 | 4 | fn simulate(mut map: Vec>, floor: usize, breakpoint: usize) -> usize { 5 | for ans in 0.. { 6 | let (mut x, mut y) = (500, 0); 7 | while y + 1 < floor { 8 | let Some(&dx) = [0,-1,1].iter().find(|&&dx| !map[x + dx as usize][y+1]) else { break }; 9 | x += dx as usize; 10 | y += 1; 11 | } 12 | if y == breakpoint { return ans; } 13 | map[x][y] = true; 14 | } 15 | unreachable!() 16 | } 17 | 18 | #[aoc::main(14)] 19 | fn main(input: &str) -> (usize, usize) { 20 | let mut map = vec![vec![false; 1000]; 1000]; 21 | let mut max_y = 0; 22 | for l in input.lines() { 23 | let coords = l.split(" -> ").map(|x| { 24 | let (a,b) = x.split_once(',').unwrap(); 25 | (a.parse::().unwrap(), b.parse::().unwrap()) 26 | }); 27 | for ((x1,y1),(x2,y2)) in coords.tuple_windows() { 28 | max_y = max(max_y, max(y1, y2)); 29 | let (mut x1,mut y1,x2,y2) = (x1 as isize,y1 as isize,x2 as isize,y2 as isize); 30 | let dx = (x2 - x1).signum(); 31 | let dy = (y2 - y1).signum(); 32 | map[x1 as usize][y1 as usize] = true; 33 | while (x1,y1) != (x2,y2) { 34 | x1 += dx; 35 | y1 += dy; 36 | map[x1 as usize][y1 as usize] = true; 37 | } 38 | } 39 | } 40 | let p1 = simulate(map.clone(), max_y + 2, max_y + 1); 41 | let p2 = simulate(map, max_y + 2, 0) + 1; 42 | (p1,p2) 43 | } 44 | -------------------------------------------------------------------------------- /2021/src/bin/21.rs: -------------------------------------------------------------------------------- 1 | use std::mem::swap; 2 | use hashbrown::HashMap; 3 | use itertools::Itertools; 4 | 5 | type Cache = HashMap<(usize,usize,usize,usize),(usize,usize)>; 6 | 7 | fn regular_game(mut pos1: usize, mut pos2: usize) -> usize { 8 | let (mut s1, mut s2, mut die, mut nrolls) = (0,0,1,0); 9 | while s2 < 1000 { 10 | for _ in 0..3 { 11 | pos1 += die; 12 | die = (die + 1) % 100; 13 | } 14 | pos1 = 1+(pos1-1)%10; 15 | s1 += pos1; 16 | nrolls += 3; 17 | swap(&mut pos1, &mut pos2); 18 | swap(&mut s1, &mut s2); 19 | } 20 | nrolls * s1 21 | } 22 | 23 | fn quantum_game(cache: &mut Cache, s1: usize, s2: usize, pos1: usize, pos2: usize) -> (usize,usize) { 24 | if s2 >= 21 { return (0,1); } 25 | if let Some(&score) = cache.get(&(s1,s2,pos1,pos2)) { return score; } 26 | 27 | let mut score = (0,0); 28 | for (die,times) in [(3,1),(4,3),(5,6),(6,7),(7,6),(8,3),(9,1)] { 29 | let pos1 = 1+(pos1+die-1)%10; 30 | let (s1,s2) = quantum_game(cache,s2,s1+pos1,pos2,pos1); 31 | score = (score.0+s2*times, score.1+s1*times); 32 | } 33 | 34 | cache.insert((s1,s2,pos1,pos2), score); 35 | score 36 | } 37 | 38 | #[aoc::main(21)] 39 | fn main(input: &str) -> (usize,usize) { 40 | let (a,b) = input.lines() 41 | .map(|l| l.split_once(": ").unwrap().1.parse().unwrap()) 42 | .collect_tuple() 43 | .unwrap(); 44 | let (s1,s2) = quantum_game(&mut HashMap::new(),0,0,a,b); 45 | (regular_game(a,b), std::cmp::max(s1,s2)) 46 | } 47 | -------------------------------------------------------------------------------- /2022/src/bin/21.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | use itertools::Itertools; 3 | 4 | enum Op<'a> { Num(i64), Op(&'a str, char, &'a str) } 5 | 6 | fn val(monkies: &HashMap<&str, Op>, name: &str) -> i64 { 7 | match &monkies[name] { 8 | Op::Num(i) => *i, 9 | Op::Op(m1, '+', m2) => val(monkies, m1) + val(monkies, m2), 10 | Op::Op(m1, '-', m2) => val(monkies, m1) - val(monkies, m2), 11 | Op::Op(m1, '*', m2) => val(monkies, m1) * val(monkies, m2), 12 | Op::Op(m1, '/', m2) => val(monkies, m1) / val(monkies, m2), 13 | _ => unreachable!(), 14 | } 15 | } 16 | 17 | fn get_eq(monkies: &HashMap<&str, Op>, name: &str) -> String { 18 | match &monkies[name] { 19 | _ if name == "humn" => "x".to_string(), 20 | Op::Num(i) => i.to_string(), 21 | Op::Op(m1, op, m2) => format!("({} {} {})", get_eq(monkies, m1), op, get_eq(monkies, m2)), 22 | } 23 | } 24 | 25 | #[aoc::main(21)] 26 | fn main(input: &str) -> (i64, &str) { 27 | let monkies = input.lines().map(|l| { 28 | let (name, rest) = l.split_once(": ").unwrap(); 29 | let op = rest.split(' ').collect_tuple() 30 | .map(|(m1,op,m2)| Op::Op(m1, op.as_bytes()[0] as char, m2)) 31 | .unwrap_or_else(|| Op::Num(rest.parse().unwrap())); 32 | (name, op) 33 | }).collect::>(); 34 | let Op::Op(a,_,b) = monkies["root"] else { panic!() }; 35 | println!("{} = {}", val(&monkies, b), get_eq(&monkies, a)); 36 | (val(&monkies, "root"), "glhf -> https://www.mathpapa.com/equation-solver/") 37 | } 38 | -------------------------------------------------------------------------------- /2017/src/24.hs: -------------------------------------------------------------------------------- 1 | import Aoc 2 | import Data.List.Split 3 | import Data.Function 4 | import Data.Set (Set) 5 | import qualified Data.Set as Set 6 | 7 | input = "42/37\n28/28\n29/25\n45/8\n35/23\n49/20\n44/4\n15/33\n14/19\n31/44\n39/14\n25/17\n34/34\n38/42\n8/42\n15/28\n0/7\n49/12\n18/36\n45/45\n28/7\n30/43\n23/41\n0/35\n18/9\n3/31\n20/31\n10/40\n0/22\n1/23\n20/47\n38/36\n15/8\n34/32\n30/30\n30/44\n19/28\n46/15\n34/50\n40/20\n27/39\n3/14\n43/45\n50/42\n1/33\n6/39\n46/44\n22/35\n15/20\n43/31\n23/23\n19/27\n47/15\n43/43\n25/36\n26/38\n1/10" 8 | 9 | parseInput :: String -> Set (Int,Int) 10 | parseInput = Set.fromList . map parseLine . lines 11 | where 12 | parseLine s = case splitOn "/" s & map read of 13 | [a,b] -> (a,b) 14 | _ -> error "invalid input" 15 | 16 | maxStrength :: Set (Int,Int) -> ((Int,Int) -> (Int,Int) -> (Int,Int)) -> Int -> Int -> Int -> (Int,Int) 17 | maxStrength ports maxFn len strength x = availablePorts & map childStrength & foldl maxFn (len,strength) 18 | where 19 | availablePorts = Set.filter (\(a,b) -> a == x || b == x) ports & Set.toList 20 | childStrength (a,b) = maxStrength (Set.delete (a,b) ports) maxFn (len+1) (strength+a+b) (if a == x then b else a) 21 | 22 | solveParts :: Int -> (Int,Int) 23 | solveParts _ = (part1, part2) 24 | where 25 | ports = parseInput input 26 | (_,part1) = maxStrength ports (\a b -> if snd a > snd b then a else b) 0 0 0 27 | (_,part2) = maxStrength ports max 0 0 0 28 | 29 | main = Aoc.timer solveParts 30 | -------------------------------------------------------------------------------- /2021/src/bin/15.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BinaryHeap; 2 | 3 | fn shortest_path(maze: &[Vec]) -> i32 { 4 | let goal = (maze.len() - 1, maze[0].len() - 1); 5 | let mut dist = vec![vec![i32::MAX; maze[0].len()]; maze.len()]; 6 | let mut q = BinaryHeap::new(); 7 | q.push((0,0,0)); 8 | while let Some((cost,x,y)) = q.pop() { 9 | if (x,y) == goal { return -cost; } 10 | if -cost > dist[x][y] { continue; } 11 | for (x1,y1) in [(x-1,y), (x+1,y), (x,y-1), (x,y+1)] { 12 | let next_cost = match maze.get(x1).and_then(|row| row.get(y1)) { 13 | Some(c) => -cost + c, 14 | None => continue, 15 | }; 16 | if next_cost < dist[x1][y1] { 17 | q.push((-next_cost,x1,y1)); 18 | dist[x1][y1] = next_cost; 19 | } 20 | } 21 | } 22 | unreachable!() 23 | } 24 | 25 | #[aoc::main(15)] 26 | fn main(input: &str) -> (i32,i32) { 27 | let maze = input.lines() 28 | .map(|l| l.bytes().map(|c| (c - b'0') as i32).collect::>()) 29 | .collect::>(); 30 | let expanded = (0..(5*maze.len())) 31 | .map(|x| (0..(5*maze[0].len())) 32 | .map(|y| { 33 | let cost = maze[x % maze.len()][y % maze[0].len()] 34 | + (x / maze.len()) as i32 35 | + (y / maze[0].len()) as i32; 36 | if cost < 10 {cost} else {cost - 9} 37 | }) 38 | .collect::>() 39 | ) 40 | .collect::>(); 41 | let p1 = shortest_path(&maze); 42 | let p2 = shortest_path(&expanded); 43 | (p1,p2) 44 | } 45 | -------------------------------------------------------------------------------- /2017/README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2017 λ 2 | Solutions to all 25 AoC 2017 problems in Haskell! 3 | 4 | Aside from some small things at university, this was pretty much my first time writing Haskell. Having just done an AoC year in [OCaml](../2016), I felt like the time was right to give Haskell a shot. I surprised at how much I liked it actually. The focus of writing really terse code makes it easier to write and often understand. The things that really bothered me with OCaml was not really present in Haskell. The standard library for example is really nice, meaning you really can write short and understandable functional code. In OCaml you spent most of your time implementing helper functions you in Haskell find in the stdlib. 5 | 6 | My only gripe with Haskell is the very unreliable performance. Some functions that I would expect to compile down to a while loop take literally seconds to execute, while a Rust equivalent finishes in less than a millisecond. Sometimes adding a [bang pattern](https://downloads.haskell.org/~ghc/7.8.3/docs/html/users_guide/bang-patterns.html) would increase performance by 100x, why can't the compiler realize that? Sometimes transforming a naive recursive function with a counter into an `iterate fn !! count` would make it 10x slower. All of this was compiled with `-O`, so that was a bit disappointing. You probably learn to recognize performance pitfalls but to a newbie it was very unintuitive what will be fast and what will be slow in Haskell. 7 | 8 | ![end-screen](../imgs/end-screen-2017.png) 9 | -------------------------------------------------------------------------------- /2023/src/bin/17.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BinaryHeap; 2 | use hashbrown::HashMap; 3 | 4 | fn dijkstra(grid: &[&[u8]], minstep: isize, maxstep: isize) -> i64 { 5 | let mut dists = HashMap::new(); 6 | let mut q = BinaryHeap::from_iter([(0, (0,0,(0,0)))]); 7 | while let Some((cost, (r, c, d))) = q.pop() { 8 | if (r,c) == (grid.len() - 1, grid[0].len() - 1) { 9 | return -cost; 10 | } 11 | if dists.get(&(r, c, d)).is_some_and(|&c| -cost > c) { 12 | continue; 13 | } 14 | for (dr, dc) in [(-1,0), (1,0), (0,-1), (0,1)] { 15 | if d == (dr, dc) || d == (-dr, -dc) { 16 | continue; 17 | } 18 | let mut next_cost = -cost; 19 | for dist in 1..=maxstep { 20 | let rr = (r as isize + dr * dist) as usize; 21 | let cc = (c as isize + dc * dist) as usize; 22 | if rr >= grid.len() || cc >= grid[0].len() { 23 | break; 24 | } 25 | next_cost += (grid[rr][cc] - b'0') as i64; 26 | if dist < minstep { 27 | continue; 28 | } 29 | let key = (rr, cc, (dr, dc)); 30 | if next_cost < *dists.get(&key).unwrap_or(&i64::MAX) { 31 | dists.insert(key, next_cost); 32 | q.push((-next_cost, key)); 33 | } 34 | } 35 | } 36 | } 37 | unreachable!() 38 | } 39 | 40 | #[aoc::main(17)] 41 | fn main(input: &str) -> (i64, i64) { 42 | let grid = input.split('\n').map(str::as_bytes).collect::>(); 43 | (dijkstra(&grid, 1, 3), dijkstra(&grid, 4, 10)) 44 | } 45 | -------------------------------------------------------------------------------- /2024/src/bin/18.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use itertools::Itertools; 3 | 4 | fn bfs(coords: &[(usize, usize)]) -> Option { 5 | let mut q = VecDeque::from([(70, 70, 0)]); 6 | let mut seen = [[false; 71]; 71]; 7 | let mut g = [[false; 71]; 71]; 8 | for &(r, c) in coords { 9 | g[r][c] = true; 10 | } 11 | while let Some((r, c, n)) = q.pop_front() { 12 | if (r, c) == (0, 0) { 13 | return Some(n); 14 | } 15 | if seen[r][c] { 16 | continue; 17 | } 18 | seen[r][c] = true; 19 | for (dr, dc) in [(0, -1), (0, 1), (-1, 0), (1, 0)] { 20 | let (rr, cc) = (r + dr as usize, c + dc as usize); 21 | if rr < 71 && cc < 71 && !g[rr][cc] { 22 | q.push_back((rr, cc, n + 1)); 23 | } 24 | } 25 | } 26 | None 27 | } 28 | 29 | #[aoc::main(18)] 30 | fn main(input: &str) -> (usize, String) { 31 | let coords = input 32 | .split(|c: char| !c.is_ascii_digit()) 33 | .filter_map(|w| w.parse().ok()) 34 | .tuples() 35 | .collect::>(); 36 | let (mut low, mut high) = (1024, coords.len()); 37 | while low <= high { 38 | let mid = (low + high) / 2; 39 | if bfs(&coords[..mid]).is_none() { 40 | high = mid - 1; 41 | } else { 42 | low = mid + 1; 43 | } 44 | } 45 | let (r, c) = coords[high]; 46 | (bfs(&coords[..1024]).unwrap(), format!("{r},{c}")) 47 | } 48 | -------------------------------------------------------------------------------- /2023/src/bin/21.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | use itertools::Itertools; 3 | 4 | fn bfs(grid: &[&[u8]], start: (isize, isize), steps: usize) -> usize { 5 | let mut pos = HashSet::from_iter([start]); 6 | let mut new_pos = HashSet::new(); 7 | for _ in 0..steps { 8 | new_pos.clear(); 9 | for &(r,c) in &pos { 10 | for (dr, dc) in [(-1,0),(1,0),(0,-1),(0,1)] { 11 | let (rr,cc) = (r + dr, c + dc); 12 | let tile = grid[rr as usize % grid.len()][cc as usize % grid.len()]; 13 | if tile != b'#' { 14 | new_pos.insert((rr,cc)); 15 | } 16 | } 17 | } 18 | (pos, new_pos) = (new_pos, pos); 19 | } 20 | pos.len() 21 | } 22 | 23 | fn find_polynomial(grid: &[&[u8]], start: (isize, isize), n: usize) -> usize { 24 | let n1 = bfs(grid, start, n % grid.len()); 25 | let n2 = bfs(grid, start, n % grid.len() + grid.len()); 26 | let n3 = bfs(grid, start, n % grid.len() + grid.len()*2); 27 | let n = n / grid.len(); 28 | let [a, b, c] = [n1, n2-n1, n3-n2]; 29 | a + b*n + (n * (n-1)/2) * (c-b) 30 | } 31 | 32 | #[aoc::main(21)] 33 | fn main(input: &str) -> (usize, usize) { 34 | let grid = input.split('\n').map(str::as_bytes).collect::>(); 35 | let start = (0..grid.len()) 36 | .cartesian_product(0..grid[0].len()) 37 | .find(|&(r,c)| grid[r][c] == b'S') 38 | .map(|(r,c)| (r as isize, c as isize)) 39 | .unwrap(); 40 | let p1 = bfs(&grid, start, 64); 41 | let p2 = find_polynomial(&grid, start, 26501365); 42 | (p1, p2) 43 | } 44 | -------------------------------------------------------------------------------- /2024/src/bin/24.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | use itertools::Itertools; 3 | 4 | fn val<'a>(g: &HashMap<&str, (&'a str, &str, &'a str)>, s: &mut HashMap<&'a str, bool>, n: &'a str) -> bool { 5 | if let Some(&v) = s.get(n) { 6 | return v; 7 | } 8 | let (a, op, b) = g[n]; 9 | let a = val(g, s, a); 10 | let b = val(g, s, b); 11 | let v = match op { 12 | "AND" => a && b, 13 | "XOR" => a != b, 14 | "OR" => a || b, 15 | _ => unreachable!(), 16 | }; 17 | s.insert(n, v); 18 | v 19 | } 20 | 21 | fn v(s: &HashMap<&str, bool>, p: char) -> usize { 22 | let mut v = 0; 23 | for &n in s.keys() { 24 | if !n.starts_with(p) { 25 | continue; 26 | } 27 | let i = n[1..].parse::().unwrap(); 28 | if s[n] { 29 | v |= 1 << i; 30 | } 31 | } 32 | v 33 | } 34 | 35 | #[aoc::main(24)] 36 | fn main(input: &str) -> (usize, &'static str) { 37 | let (s1, s2) = input.split_once("\n\n").unwrap(); 38 | let mut s = HashMap::new(); 39 | let mut g = HashMap::new(); 40 | for l in s1.lines() { 41 | let (n, v) = l.split_once(": ").unwrap(); 42 | s.insert(n, v == "1"); 43 | } 44 | for l in s2.lines() { 45 | let (a, op, b, _, c) = l.split(' ').collect_tuple().unwrap(); 46 | g.insert(c, (a, op, b)); 47 | } 48 | for n in g.keys() { 49 | val(&g, &mut s, n); 50 | } 51 | // Solved by hand 52 | (v(&s, 'z'), "jqf,mdd,skh,wpd,wts,z11,z19,z37") 53 | } 54 | -------------------------------------------------------------------------------- /2022/src/bin/07.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use hashbrown::{HashMap, HashSet}; 3 | 4 | fn compute_dir_size(fs: &HashMap>, sizes: &mut HashMap, dir: &PathBuf) { 5 | if sizes.contains_key(dir) { 6 | return; 7 | } 8 | let size = fs[dir].iter().map(|&(s, d)| match s { 9 | -1 => { 10 | let dir = dir.join(d); 11 | compute_dir_size(fs, sizes, &dir); 12 | sizes[&dir] 13 | } 14 | s => s, 15 | }).sum(); 16 | sizes.insert(dir.clone(), size); 17 | } 18 | 19 | #[aoc::main(07)] 20 | fn main(input: &str) -> (i64, i64) { 21 | let mut fs = HashMap::<_, HashSet<_>>::new(); 22 | let mut pwd = PathBuf::new(); 23 | for l in input.split('$').skip(1) { 24 | match l.trim().lines().next().unwrap() { 25 | "ls" => { 26 | let entries = l.lines().skip(1).map(|output| { 27 | let (size, f) = output.split_once(' ').unwrap(); 28 | (size.parse::().unwrap_or(-1), f) 29 | }); 30 | fs.entry(pwd.clone()).or_default().extend(entries); 31 | } 32 | "cd .." => { pwd.pop(); }, 33 | cd_dir => { pwd.push(cd_dir.split_once(' ').unwrap().1); } 34 | } 35 | } 36 | 37 | let mut sizes = HashMap::new(); 38 | for k in fs.keys() { 39 | compute_dir_size(&fs, &mut sizes, k); 40 | } 41 | let total_size = sizes[&PathBuf::from("/")]; 42 | let p1 = sizes.values().filter(|&&s| s <= 100000).sum::(); 43 | let p2 = sizes.values().filter(|&&s| 40000000 + s >= total_size).min().copied().unwrap(); 44 | (p1, p2) 45 | } 46 | -------------------------------------------------------------------------------- /2024/src/bin/20.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use hashbrown::HashMap; 3 | use itertools::Itertools; 4 | 5 | #[aoc::main(20)] 6 | fn main(input: &str) -> (usize, usize) { 7 | let g = input.lines().map(str::as_bytes).collect::>(); 8 | let (mut start, mut end) = ((0, 0), (0, 0)); 9 | for r in 0..g.len() { 10 | for c in 0..g[0].len() { 11 | match g[r][c] { 12 | b'S' => start = (r, c), 13 | b'E' => end = (r, c), 14 | _ => {} 15 | } 16 | } 17 | } 18 | let mut q = VecDeque::from([(start.0, start.1, 0usize)]); 19 | let mut dists = HashMap::new(); 20 | while let Some((r, c, n)) = q.pop_front() { 21 | if dists.contains_key(&(r, c)) { 22 | continue; 23 | } 24 | dists.insert((r, c), n); 25 | if (r, c) == end { 26 | continue; 27 | } 28 | for (dr, dc) in [(-1, 0), (1, 0), (0, -1), (0, 1)] { 29 | let (rr, cc) = (r + dr as usize, c + dc as usize); 30 | if g[rr][cc] != b'#' { 31 | q.push_back((rr, cc, n + 1)); 32 | } 33 | } 34 | } 35 | let (mut p1, mut p2) = (0, 0); 36 | for ((&(r1, c1), &n1), (&(r2, c2), &n2)) in dists.iter().tuple_combinations() { 37 | let d = r1.abs_diff(r2) + c1.abs_diff(c2); 38 | if d <= 20 && n2.abs_diff(n1) >= d + 100 { 39 | if d <= 2 { 40 | p1 += 1; 41 | } 42 | p2 += 1; 43 | } 44 | } 45 | (p1, p2) 46 | } 47 | -------------------------------------------------------------------------------- /2018/src/18.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from collections import Counter 3 | 4 | def step(grid: list[str], times: int) -> list[str]: 5 | for _ in range(times): 6 | nextgrid = [] 7 | for r in range(len(grid)): 8 | row = "" 9 | for c in range(len(grid[0])): 10 | ns = [(r-1,c-1),(r-1,c),(r-1,c+1),(r,c-1),(r,c+1),(r+1,c-1),(r+1,c),(r+1,c+1)] 11 | trees,yards = 0,0 12 | for r1,c1 in ns: 13 | if r1 < 0 or r1 >= len(grid) or c1 < 0 or c1 >= len(grid[0]): 14 | continue 15 | match grid[r1][c1]: 16 | case '|': trees += 1 17 | case '#': yards += 1 18 | match grid[r][c]: 19 | case '.': row += '|' if trees >= 3 else '.' 20 | case '|': row += '#' if yards >= 3 else '|' 21 | case '#': row += '#' if yards > 0 and trees > 0 else '.' 22 | nextgrid.append(row) 23 | grid = nextgrid 24 | return grid 25 | 26 | def resource_value(grid: list[str]) -> int: 27 | c = Counter(tile for row in grid for tile in row) 28 | return c['#'] * c['|'] 29 | 30 | @aoc.main('18') 31 | def main(indata: str) -> tuple[int,int]: 32 | grid = indata.split('\n') 33 | p1 = resource_value(step(grid,10)) 34 | seen = {} 35 | for i in range(1000000000): 36 | grid = step(grid,1) 37 | s = ''.join(''.join(row) for row in grid) 38 | if s in seen: 39 | left = (1000000000 - seen[s] - 1) % (i-seen[s]) 40 | grid = step(grid,left) 41 | break 42 | seen[s] = i 43 | return p1, resource_value(grid) 44 | 45 | if __name__ == "__main__": 46 | main() 47 | -------------------------------------------------------------------------------- /2022/src/bin/12.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use itertools::Itertools; 3 | 4 | fn bfs(grid: &[Vec], start: &[(usize, usize)], goal: (usize, usize)) -> Option { 5 | let mut visited = vec![vec![false; grid[0].len()]; grid.len()]; 6 | let mut queue = start.iter().map(|&(x,y)| (x, y, 0)).collect::>(); 7 | while let Some((x, y, len)) = queue.pop_front() { 8 | if (x, y) == goal { 9 | return Some(len); 10 | } 11 | for (dx, dy) in [(0,-1), (-1,0), (0,1), (1,0)] { 12 | let (nx, ny) = ((x as isize + dx) as usize, (y as isize + dy) as usize); 13 | let Some(&square) = grid.get(nx).and_then(|row| row.get(ny)) else { continue }; 14 | if grid[x][y] + 1 >= square && !visited[nx][ny] { 15 | visited[nx][ny] = true; 16 | queue.push_back((nx, ny, len + 1)); 17 | } 18 | } 19 | } 20 | None 21 | } 22 | 23 | #[aoc::main(12)] 24 | fn main(input: &str) -> (usize, usize) { 25 | let mut grid = input.lines().map(|l| l.as_bytes().to_vec()).collect::>(); 26 | let (sx, sy) = (0..grid.len()).cartesian_product(0..grid[0].len()).find(|&(x,y)| grid[x][y] == b'S').unwrap(); 27 | let (gx, gy) = (0..grid.len()).cartesian_product(0..grid[0].len()).find(|&(x,y)| grid[x][y] == b'E').unwrap(); 28 | grid[sx][sy] = b'a'; 29 | grid[gx][gy] = b'z'; 30 | let positions = (0..grid.len()).cartesian_product(0..grid[0].len()) 31 | .filter(|&(x,y)| grid[x][y] == b'a') 32 | .collect::>(); 33 | (bfs(&grid, &[(sx, sy)], (gx, gy)).unwrap(), bfs(&grid, &positions, (gx, gy)).unwrap()) 34 | } 35 | -------------------------------------------------------------------------------- /2022/src/bin/15.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn part_one(beacons: &[(i64,i64,i64)]) -> i64 { 4 | let compressed = beacons.iter() 5 | .map(|&(x,y,d)| (x, d - (2000000 - y).abs())) 6 | .filter(|&(_,left)| left >= 0) 7 | .flat_map(|(x,left)| [(x - left, true), (x + left + 1, false)]) 8 | .sorted() 9 | .collect::>(); 10 | let (mut ans, mut inside) = (-1, 1); 11 | for ((prev, _), &(x, start)) in compressed.iter().tuple_windows() { 12 | if inside > 0 { ans += x - prev } 13 | inside += if start {1} else {-1}; 14 | } 15 | ans 16 | } 17 | 18 | fn part_two(beacons: &[(i64,i64,i64)]) -> i64 { 19 | for &(x,y,d) in beacons { 20 | for (dir_x, dir_y) in [(-1,-1), (-1,1), (1,-1), (1,1)] { 21 | for dist in 0..d { 22 | let bx = x + dir_x * dist; 23 | let by = y + dir_y * (d + 1 - dist); 24 | if bx < 0 || by < 0 || bx > 4000000 || by > 4000000 { 25 | break; 26 | } 27 | if beacons.iter().all(|&(x,y,d)| (bx - x).abs() + (by - y).abs() >= d) { 28 | return bx * 4000000 + by; 29 | } 30 | } 31 | } 32 | } 33 | unreachable!() 34 | } 35 | 36 | #[aoc::main(15)] 37 | fn main(input: &str) -> (i64, i64) { 38 | let beacons = input.lines().map(|l| 39 | l.split(|c: char| !c.is_ascii_digit() && c != '-') 40 | .filter_map(|w| w.parse::().ok()) 41 | .collect_tuple() 42 | .map(|(x,y,dx,dy)| (x, y, (x - dx).abs() + (y - dy).abs())) 43 | .unwrap() 44 | ).collect::>(); 45 | (part_one(&beacons), part_two(&beacons)) 46 | } 47 | -------------------------------------------------------------------------------- /2017/src/02.hs: -------------------------------------------------------------------------------- 1 | import Aoc 2 | import Data.List 3 | import Data.Function 4 | 5 | input = "157 564 120 495 194 520 510 618 244 443 471 473 612 149 506 138\n1469 670 47 604 1500 238 1304 1426 54 749 1218 1409 60 51 1436 598\n578 184 2760 3057 994 167 2149 191 2913 2404 213 1025 1815 588 2421 3138\n935 850 726 155 178 170 275 791 1028 75 781 138 176 621 773 688\n212 977 297 645 229 194 207 640 804 509 833 726 197 825 242 743\n131 43 324 319 64 376 231 146 382 162 464 314 178 353 123 446\n551 121 127 155 1197 288 1412 1285 557 137 145 1651 1549 1217 681 1649\n1723 1789 5525 4890 3368 188 3369 4842 3259 2502 4825 163 146 2941 126 5594\n311 2420 185 211 2659 2568 2461 231 2599 1369 821 506 2227 180 220 1372\n197 4490 141 249 3615 3314 789 4407 169 352 4383 5070 5173 3115 132 3513\n4228 2875 3717 504 114 2679 165 3568 3002 116 756 151 4027 261 4813 2760\n651 3194 2975 2591 1019 835 3007 248 3028 1382 282 3242 296 270 3224 3304\n1858 1650 1720 1848 95 313 500 1776 207 1186 72 259 281 1620 79 77\n3841 3217 440 3481 3643 940 3794 4536 1994 4040 3527 202 193 1961 230 217\n2837 2747 2856 426 72 78 2361 96 2784 2780 98 2041 2444 1267 2167 2480\n411 178 4263 4690 3653 162 3201 4702 3129 2685 3716 147 3790 4888 79 165" 6 | 7 | part1 :: [Int] -> Int 8 | part1 l = maximum l - minimum l 9 | 10 | part2 :: [Int] -> Int 11 | part2 l = sum [x `div` y | (x:ys) <- tails l, y <- ys, x `mod` y == 0] 12 | 13 | solveParts :: Int -> (Int,Int) 14 | solveParts _ = (sum $ map part1 rows, sum $ map part2 rows) 15 | where rows = lines input & map (map read . words) 16 | 17 | main = Aoc.timer solveParts 18 | -------------------------------------------------------------------------------- /2022/src/bin/05.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn part_one(instructions: &[(usize, usize, usize)], mut stacks: Vec>) -> String { 4 | for &(times, from, to) in instructions { 5 | for _ in 0..times { 6 | let item = stacks[from-1].pop().unwrap(); 7 | stacks[to-1].push(item); 8 | } 9 | } 10 | stacks.iter().map(|s| s.last().unwrap()).join("") 11 | } 12 | 13 | fn part_two(instructions: &[(usize, usize, usize)], mut stacks: Vec>) -> String { 14 | for &(times, from, to) in instructions { 15 | let len = stacks[to-1].len() + times; 16 | stacks[to-1].resize(len, 'x'); 17 | for i in 0..times { 18 | let item = stacks[from-1].pop().unwrap(); 19 | stacks[to-1][len-1-i] = item; 20 | } 21 | } 22 | stacks.iter().map(|s| s.last().unwrap()).join("") 23 | } 24 | 25 | #[aoc::main(05)] 26 | fn main(input: &str) -> (String, String) { 27 | let (boxes, rest) = input.split_once("\n\n").unwrap(); 28 | let mut stacks = vec![vec![]; 9]; 29 | for l in boxes.lines().rev().skip(1).map(str::as_bytes) { 30 | for i in 0..stacks.len() { 31 | let c = l[i*4+1]; 32 | if c.is_ascii_alphabetic() { 33 | stacks[i].push(c as char); 34 | } 35 | } 36 | } 37 | let instructions = rest.lines() 38 | .map(|l| l.split_whitespace() 39 | .filter_map(|s| s.parse::().ok()) 40 | .collect_tuple() 41 | .unwrap() 42 | ) 43 | .collect::>(); 44 | let p1 = part_one(&instructions, stacks.clone()); 45 | let p2 = part_two(&instructions, stacks); 46 | (p1,p2) 47 | } 48 | -------------------------------------------------------------------------------- /2023/src/bin/16.rs: -------------------------------------------------------------------------------- 1 | fn step(r: usize, c: usize, d: usize) -> (usize, usize, usize) { 2 | let (dr, dc) = [(-1,0),(0,1),(1,0),(0,-1)][d]; 3 | ((r as isize + dr) as _, (c as isize + dc) as _, d) 4 | } 5 | 6 | fn energized_tiles(grid: &[&[u8]], start: (usize,usize,usize)) -> usize { 7 | let mut seen = vec![vec![[false; 4]; grid[0].len()]; grid.len()]; 8 | let (mut beams, mut new_beams) = (vec![start], vec![]); 9 | while !beams.is_empty() { 10 | new_beams.clear(); 11 | for &(r,c,d) in &beams { 12 | if r >= grid.len() || c >= grid[0].len() || seen[r][c][d] { 13 | continue; 14 | } 15 | seen[r][c][d] = true; 16 | match (grid[r][c], d) { 17 | (b'|', 1|3) => new_beams.extend([step(r,c,0), step(r,c,2)]), 18 | (b'-', 0|2) => new_beams.extend([step(r,c,1), step(r,c,3)]), 19 | (b'/', _) => new_beams.push(step(r,c,[1,0,3,2][d])), 20 | (b'\\', _) => new_beams.push(step(r,c,[3,2,1,0][d])), 21 | _ => new_beams.push(step(r,c,d)), 22 | } 23 | } 24 | (beams, new_beams) = (new_beams, beams); 25 | } 26 | seen.iter().flatten().filter(|x| x.iter().any(|&b| b)).count() 27 | } 28 | 29 | #[aoc::main(16)] 30 | fn main(input: &str) -> (usize, usize) { 31 | let grid = input.split('\n').map(str::as_bytes).collect::>(); 32 | let p2 = (0..grid.len()).flat_map(|r| [(r,0,1), (r,grid[0].len()-1,3)]) 33 | .chain((0..grid[0].len()).flat_map(|c| [(0,c,2), (grid.len()-1,c,0)])) 34 | .map(|start| energized_tiles(&grid, start)) 35 | .max() 36 | .unwrap(); 37 | (energized_tiles(&grid, (0,0,1)), p2) 38 | } 39 | -------------------------------------------------------------------------------- /2023/src/bin/14.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | use itertools::Itertools; 3 | 4 | fn roll_north(map: &mut Vec>) { 5 | for r in 0..map.len() - 1 { 6 | for c in 0..map[0].len() { 7 | if map[r+1][c] != b'O' { 8 | continue; 9 | } 10 | for rr in (0..=r).rev() { 11 | if map[rr][c] != b'.' { 12 | break; 13 | } 14 | map[rr][c] = b'O'; 15 | map[rr+1][c] = b'.'; 16 | } 17 | } 18 | } 19 | } 20 | 21 | fn rotate(map: &mut Vec>, tmpmap: &mut Vec>) { 22 | for (r,c) in (0..map.len()).cartesian_product(0..map[0].len()) { 23 | tmpmap[c][map.len() - 1 - r] = map[r][c]; 24 | } 25 | std::mem::swap(map, tmpmap); 26 | } 27 | 28 | fn total_load(map: &Vec>) -> usize { 29 | (0..map.len()) 30 | .map(|r| (map.len() - r) * map[r].iter().filter(|&&t| t == b'O').count()) 31 | .sum() 32 | } 33 | 34 | #[aoc::main(14)] 35 | fn main(input: &str) -> (usize, usize) { 36 | let mut map = input.split('\n').map(|l| l.as_bytes().to_vec()).collect::>(); 37 | let p1 = { 38 | let mut map = map.clone(); 39 | roll_north(&mut map); 40 | total_load(&map) 41 | }; 42 | 43 | let mut tmpmap = vec![vec![0; map.len()]; map[0].len()]; 44 | let mut seen = HashMap::new(); 45 | for i in 1..1000000000 { 46 | for _ in 0..4 { 47 | roll_north(&mut map); 48 | rotate(&mut map, &mut tmpmap); 49 | } 50 | if let Some(seen_at) = seen.insert(map.clone(), i) { 51 | if (1000000000 - i) % (i - seen_at) == 0 { 52 | break; 53 | } 54 | } 55 | } 56 | (p1, total_load(&map)) 57 | } 58 | -------------------------------------------------------------------------------- /2024/src/bin/06.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn walk(m: &[Vec], mut r: usize, mut c: usize, return_squares: bool) -> Option> { 4 | let mut seen = vec![vec![[false; 4]; m[0].len()]; m.len()]; 5 | let mut d = 0; 6 | loop { 7 | if seen[r][c][d] { 8 | return None; 9 | } 10 | seen[r][c][d] = true; 11 | let (dr, dc) = [(-1,0), (0,1), (1,0), (0, -1)][d]; 12 | let (rr, cc) = (r + dr as usize, c + dc as usize); 13 | if !(0..m.len()).contains(&rr) || !(0..m[0].len()).contains(&cc) { 14 | if !return_squares { 15 | return Some(Vec::new()); 16 | } 17 | let visited = (0..m.len()).cartesian_product(0..m[0].len()) 18 | .filter(|&(r, c)| seen[r][c].iter().any(|&b| b)) 19 | .collect(); 20 | return Some(visited); 21 | } 22 | if m[rr][cc] == b'#' { 23 | d = (d + 1) % 4; 24 | } else { 25 | (r, c) = (rr, cc); 26 | } 27 | } 28 | } 29 | 30 | #[aoc::main(06)] 31 | fn main(input: &str) -> (usize, usize) { 32 | let mut m = input.lines().map(|l| l.as_bytes().to_vec()).collect::>(); 33 | let (sr, sc) = (0..m.len()).cartesian_product(0..m[0].len()) 34 | .find(|&(r, c)| m[r][c] == b'^') 35 | .unwrap(); 36 | let p1 = walk(&m, sr, sc, true).unwrap(); 37 | let p2 = p1.iter().filter(|&&(r, c)| { 38 | m[r][c] = b'#'; 39 | let ok = walk(&m, sr, sc, false).is_none(); 40 | m[r][c] = b'.'; 41 | ok 42 | }).count(); 43 | (p1.len(), p2) 44 | } 45 | -------------------------------------------------------------------------------- /2020/src/bin/23.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define VAL(n) (n - list) // assumes the list is visible 6 | 7 | int INPUT[] = {4,6,3,5,2,8,1,7,9}; 8 | 9 | typedef struct Node { struct Node *next; } Node; 10 | 11 | Node* build_list(int size) { 12 | Node *list = malloc(sizeof(Node) * size); 13 | Node *curr = list + INPUT[0]; 14 | for (int i = 1; i < size; curr = curr->next, ++i) 15 | curr->next = list + (i < 9 ? INPUT[i] : i + 1); 16 | curr->next = list + INPUT[0]; 17 | return list; 18 | } 19 | 20 | Node* simulate_game(int size, int rounds) { 21 | Node *list = build_list(size); 22 | Node *curr = list + INPUT[0]; 23 | for (int i = 0; i < rounds; curr = curr->next, ++i) { 24 | Node *a = curr->next, *b = a->next, *c = b->next; 25 | int t = (VAL(curr) == 1 ? size : VAL(curr) - 1); 26 | while (t == VAL(a) || t == VAL(b) || t == VAL(c)) 27 | t = (t == 1 ? size : t - 1); 28 | Node *dest = list + t; 29 | 30 | curr->next = c->next; 31 | c->next = dest->next; 32 | dest->next = a; 33 | } 34 | return list; 35 | } 36 | 37 | int part_one(void) { 38 | Node *list = simulate_game(9, 100); 39 | int ans = 0; 40 | for (Node *n = list[1].next; n != list + 1; n = n->next) 41 | ans = ans * 10 + VAL(n); 42 | return ans; 43 | } 44 | 45 | long part_two(void) { 46 | Node *list = simulate_game(1000000, 10000000); 47 | return VAL(list[1].next) * VAL(list[1].next->next); 48 | } 49 | 50 | int main() { 51 | printf("Part one: %d\n", part_one()); 52 | printf("Part two: %ld\n", part_two()); 53 | printf("Time: %ldms\n", clock() / (CLOCKS_PER_SEC / 1000)); 54 | } 55 | -------------------------------------------------------------------------------- /2022/src/bin/13.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use serde_json::Value; 3 | use std::cmp::{max, Ordering}; 4 | 5 | fn comp(a: &Value, b: &Value) -> Ordering { 6 | match (a,b) { 7 | (Value::Number(x), Value::Number(y)) => x.as_u64().unwrap().cmp(&y.as_u64().unwrap()), 8 | (Value::Array(a), Value::Array(b)) => { 9 | for i in 0..max(a.len(), b.len()) { 10 | match (a.get(i), b.get(i)) { 11 | (None, _) => return Ordering::Less, 12 | (_, None) => return Ordering::Greater, 13 | (Some(x), Some(y)) => match comp(x,y) { 14 | Ordering::Equal => {}, 15 | c => return c, 16 | } 17 | } 18 | } 19 | Ordering::Equal 20 | }, 21 | (Value::Array(_), Value::Number(_)) => comp(a, &Value::Array(vec![b.clone()])), 22 | (Value::Number(_), Value::Array(_)) => comp(&Value::Array(vec![a.clone()]), b), 23 | _ => unreachable!(), 24 | } 25 | } 26 | 27 | #[aoc::main(13)] 28 | fn main(input: &str) -> (usize, usize) { 29 | let mut signals = input.lines() 30 | .filter(|l| !l.is_empty()) 31 | .map(|l| serde_json::from_str::(l).unwrap()) 32 | .collect::>(); 33 | let p1 = signals.iter() 34 | .tuples() 35 | .positions(|(a,b)| comp(a,b) != Ordering::Greater) 36 | .map(|i| i + 1) 37 | .sum(); 38 | let beacons = [ 39 | serde_json::from_str::("[[2]]").unwrap(), 40 | serde_json::from_str::("[[6]]").unwrap(), 41 | ]; 42 | signals.extend(beacons.iter().cloned()); 43 | signals.sort_by(comp); 44 | let p2 = signals.iter().positions(|b| beacons.contains(b)).map(|i| i + 1).product(); 45 | (p1,p2) 46 | } 47 | -------------------------------------------------------------------------------- /2018/src/13.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | from itertools import product 3 | 4 | DR,DC = [0,1,0,-1],[1,0,-1,0] 5 | 6 | def move_cart(crosses: dict[tuple[int,int],str], cart: list[int]) -> list[int]: 7 | r,c,d,t = cart 8 | match crosses.get((r,c)): 9 | case '+': 10 | match t: 11 | case 0: d = [3,0,1,2][d] 12 | case 2: d = [1,2,3,0][d] 13 | t = (t+1)%3 14 | case '/': d = [3,2,1,0][d] 15 | case '\\': d = [1,0,3,2][d] 16 | return [r+DR[d],c+DC[d],d,t] 17 | 18 | def step(crosses: dict[tuple[int,int],str], carts: list[list[int]]) -> list[int]: 19 | carts.sort() 20 | crashes = set() 21 | for i in range(len(carts)): 22 | r,c,d,t = move_cart(crosses, carts[i]) 23 | for j in range(len(carts)): 24 | if carts[j][0] == r and carts[j][1] == c: 25 | crashes |= {i,j} 26 | carts[i] = [r,c,d,t] 27 | return sorted(crashes) 28 | 29 | @aoc.main('13') 30 | def main(indata: str): 31 | grid, crosses, carts = indata.split('\n'), {}, [] 32 | for r,c in product(range(len(grid)), range(len(grid[0]))): 33 | match grid[r][c]: 34 | case '>': carts.append([r,c,0,0]) 35 | case 'v': carts.append([r,c,1,0]) 36 | case '<': carts.append([r,c,2,0]) 37 | case '^': carts.append([r,c,3,0]) 38 | case '+': crosses[r,c] = "+" 39 | case '/': crosses[r,c] = '/' 40 | case '\\': crosses[r,c] = '\\' 41 | r1,c1 = None,None 42 | while len(carts) > 1: 43 | for i in reversed(step(crosses, carts)): 44 | if not r1: 45 | r1,c1 = carts[i][:2] 46 | carts.pop(i) 47 | r2,c2 = carts[0][:2] 48 | return f"{c1},{r1}", f"{c2},{r2}" 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /2022/src/bin/16.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | use itertools::Itertools; 3 | 4 | #[aoc::main(16)] 5 | fn main(input: &str) -> (u16, u16) { 6 | let valves = input.lines() 7 | .map(|l| { 8 | let mut words = l.split(|c: char| !c.is_uppercase() && !c.is_ascii_digit()).filter(|w| !w.is_empty()).skip(1); 9 | let valve = words.next().unwrap(); 10 | let flow = words.next().unwrap().parse::().unwrap(); 11 | let tunnels = words.collect::>(); 12 | (valve, flow, tunnels) 13 | }) 14 | .sorted_by_key(|(_, flow, _)| -(*flow as i32)) 15 | .collect::>(); 16 | let labels = valves.iter().enumerate().map(|(i, v)| (v.0, i)).collect::>(); 17 | let flow = valves.iter().map(|(_,flow,_)| *flow).collect::>(); 18 | let adj = valves.iter().map(|(_, _, tunnels)| tunnels.iter().map(|t| labels[t]).collect::>()).collect::>(); 19 | let m = valves.iter().position(|(_, flow, _)| *flow == 0).unwrap(); 20 | let mm = 1 << m; 21 | 22 | // opt[time][node][available valves] 23 | let mut opt = vec![vec![vec![0; mm]; valves.len()]; 30]; 24 | for t in 1..30 { 25 | for i in 0..valves.len() { 26 | let ii = 1 << i; 27 | for x in 0..mm { 28 | let mut tmp = opt[t][i][x]; 29 | if ii & x != 0 && t >= 2 { 30 | tmp = tmp.max(opt[t-1][i][x - ii] + flow[i] * t as u16); 31 | } 32 | opt[t][i][x] = tmp.max(adj[i].iter().map(|&j| opt[t-1][j][x]).max().unwrap()); 33 | } 34 | } 35 | } 36 | 37 | let start = labels["AA"]; 38 | let p1 = opt[29][start][mm - 1]; 39 | let p2 = (0..mm/2).map(|x| opt[25][start][x] + opt[25][start][mm-1-x]).max().unwrap(); 40 | (p1, p2) 41 | } 42 | -------------------------------------------------------------------------------- /2018/src/19.py: -------------------------------------------------------------------------------- 1 | import aoc 2 | 3 | # Function reverse-engineered from the assembly: 4 | # 00: jump 17 5 | # 01: seti 1 6 5 ; r5 = 1 6 | # 02: seti 1 8 2 ; r2 = 1 7 | # 03: mulr 5 2 1 ; r1 = 1*1 = 1 8 | # 04: eqrr 1 4 1 ; r1 = 1 if r1 == r4 else 0 9 | # 05: addr 1 pc pc ; if r1 == r4 { jump 7 } 10 | # 06: jump 8 11 | # 07: addr 5 0 0 ; r0 += r5 12 | # 08: addi 2 1 2 ; r2 += 1 13 | # 09: gtrr 2 4 1 ; r1 = 1 if r2 > r4 else 0 14 | # 10: addr pc 1 pc ; if r2 > r4 { jump 12 } 15 | # 11: jump 3 16 | # 12: addi 5 1 5 ; r5 += 1 17 | # 13: gtrr 5 4 1 ; r1 = 1 if r5 > r4 else 0 18 | # 14: addr 1 pc pc ; if r5 > r4 { jump 16 } 19 | # 15: jump 2 20 | # 16: HALT 21 | # 17: addi 4 2 4 ; r4 += 2 22 | # 18: mulr 4 4 4 ; r4 *= r4 23 | # 19: mulr pc 4 4 ; r4 *= 19 24 | # 20: muli 4 11 4 ; r4 *= 11, r4 = 836 25 | # 21: addi 1 6 1 ; r1 += 6 26 | # 22: mulr 1 pc 1 ; r1 *= 22 27 | # 23: addi 1 10 1 ; r1 += 10 28 | # 24: addr 4 1 4 ; r4 += r1 29 | # 25: jump r0 30 | # 26: jump 0 31 | # 27: setr pc 9 1 ; r1 = 27 32 | # 28: mulr 1 pc 1 ; r1 *= 28 33 | # 29: addr pc 1 1 ; r1 += 29 34 | # 30: mulr pc 1 1 ; r1 *= 30 35 | # 31: muli 1 14 1 ; r1 *= 14 36 | # 32: mulr 1 pc 1 ; r1 *= 32, r1 = 10550400 37 | # 33: addr 4 1 4 ; r4 += 10550400 38 | # 34: seti 0 4 0 ; r0 = 0 39 | # 35: jump 1 40 | 41 | def f(n: int) -> int: 42 | return sum(d+n//d for d in range(1, int(n**0.5)+1) if n % d == 0) 43 | 44 | @aoc.main('19') 45 | def main(indata: str): 46 | lines = indata.split('\n') 47 | a, b = [int(lines[n].split(' ')[2]) for n in [22,24]] 48 | n = 836 + 22 * a + b 49 | return f(n), f(n+10550400) 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /2017/src/03.hs: -------------------------------------------------------------------------------- 1 | import Aoc 2 | import Data.Function 3 | import qualified Data.Map as Map 4 | 5 | type PosMap = Map.Map (Int,Int) Int 6 | type State = ((Int,Int), (Int,Int), PosMap) 7 | 8 | initState = ((0,0), (1,0), Map.fromList [((0,0),1)]) 9 | 10 | turn :: (Int,Int) -> (Int,Int) 11 | turn ( 1,0) = (0,1) 12 | turn (-1,0) = (0,-1) 13 | turn (0, 1) = (-1,0) 14 | turn (0,-1) = (1,0) 15 | turn dir = dir 16 | 17 | atCorner :: PosMap -> (Int,Int) -> Bool 18 | atCorner m (x,y) = neighbours & filter (`Map.notMember` m) & length & (==3) 19 | where neighbours = [(x+1,y),(x-1,y),(x,y+1),(x,y-1)] 20 | 21 | move :: Int -> State -> State 22 | move v ((x,y),(dx,dy),m) = (newPos, newDir, newMap) 23 | where 24 | newPos = (x+dx, y+dy) 25 | newDir = if atCorner m newPos then turn (dx,dy) else (dx,dy) 26 | newMap = Map.insert newPos v m 27 | 28 | findPosOf :: Int -> Int -> State -> (Int,Int) 29 | findPosOf goal prev state 30 | | prev+1 == goal = newPos 31 | | otherwise = findPosOf goal (prev+1) (newPos, newDir, newMap) 32 | where (newPos, newDir, newMap) = move (prev+1) state 33 | 34 | findPart1 :: Int -> Int 35 | findPart1 goal = abs x + abs y 36 | where (x,y) = findPosOf goal 1 initState 37 | 38 | findPart2 :: Int -> State -> Int 39 | findPart2 goal ((x,y),(dx,dy),m) 40 | | newVal > goal = newVal 41 | | otherwise = findPart2 goal newState 42 | where 43 | neighbours = [(1,-1),(1,0),(1,1),(0,-1),(0,1),(-1,-1),(-1,0),(-1,1)] 44 | newVal = neighbours & map (\(a,b) -> Map.findWithDefault 0 (x+dx+a,y+dy+b) m) & sum 45 | newState = move newVal ((x,y),(dx,dy),m) 46 | 47 | solveParts :: Int -> (Int,Int) 48 | solveParts _ = (findPart1 289326, findPart2 289326 initState) 49 | 50 | main = Aoc.timer solveParts 51 | -------------------------------------------------------------------------------- /2020/src/bin/13.rs: -------------------------------------------------------------------------------- 1 | static START: i64 = 1003240; 2 | static INPUT: &str = "19,x,x,x,x,x,x,x,x,41,x,x,x,37,x,x,x,x,x,787,x,x,x,x,x,x,x,x,x,x,x,x,13,x,x,x,x,x,x,x,x,x,23,x,x,x,x,x,29,x,571,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,17"; 3 | 4 | fn part_one(busses: &[(i64,i64)]) -> i64 { 5 | for i in START.. { 6 | if let Some((_,b)) = busses.iter().find(|(_,b)| i % b == 0) { 7 | return b * (i - START) 8 | } 9 | } 10 | unreachable!() 11 | } 12 | 13 | fn egcd(a: i64, b: i64) -> (i64, i64, i64) { 14 | if a == 0 { 15 | (b, 0, 1) 16 | } else { 17 | let (g, x, y) = egcd(b % a, a); 18 | (g, y - (b / a) * x, x) 19 | } 20 | } 21 | 22 | fn mod_inv(x: i64, n: i64) -> Option { 23 | let (g, x, _) = egcd(x, n); 24 | if g == 1 { 25 | Some((x % n + n) % n) 26 | } else { 27 | None 28 | } 29 | } 30 | 31 | // from: https://rosettacode.org/wiki/Chinese_remainder_theorem#Rust 32 | fn chinese_remainder(residues: &[i64], modulii: &[i64]) -> Option { 33 | let prod = modulii.iter().product::(); 34 | let mut sum = 0; 35 | for (&residue, &modulus) in residues.iter().zip(modulii) { 36 | let p = prod / modulus; 37 | sum += residue * mod_inv(p, modulus)? * p 38 | } 39 | Some(sum % prod) 40 | } 41 | 42 | fn part_two(busses: &[(i64, i64)]) -> i64 { 43 | let mods = busses.iter().map(|&(_,b)| b).collect::>(); 44 | let res = busses.iter().map(|&(i,b)| b-i).collect::>(); 45 | chinese_remainder(&res, &mods).unwrap() 46 | } 47 | 48 | aoc2020::main! { 49 | let busses = INPUT.split(',') 50 | .enumerate() 51 | .filter(|&(_,s)| s != "x") 52 | .map(|(i,s)| (i as i64, s.parse().unwrap())) 53 | .collect::>(); 54 | (part_one(&busses), part_two(&busses)) 55 | } 56 | -------------------------------------------------------------------------------- /2023/src/bin/22.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::{HashMap, HashSet}; 2 | use itertools::Itertools; 3 | 4 | fn disintegrate_all(adjacent: &[(HashSet, HashSet)], falling: &mut HashSet, i: usize) { 5 | falling.insert(i); 6 | for &above in &adjacent[i].0 { 7 | if adjacent[above].1.iter().all(|x| falling.contains(x)) { 8 | disintegrate_all(adjacent, falling, above); 9 | } 10 | } 11 | } 12 | 13 | #[aoc::main(22)] 14 | fn main(input: &str) -> (usize, usize) { 15 | let mut bricks = input.split('\n').map(|l| 16 | l.split(|c: char| !c.is_ascii_digit()) 17 | .map(|w| w.parse::().unwrap()) 18 | .collect_tuple() 19 | .unwrap() 20 | ).collect::>(); 21 | bricks.sort_by_key(|&(_,_,z1,_,_,_)| z1); 22 | 23 | let mut adjacent = vec![(HashSet::new(), HashSet::new()); bricks.len()]; 24 | let mut space = HashMap::new(); 25 | for i in 0..bricks.len() { 26 | let (x1, y1, mut z1, x2, y2, mut z2) = bricks[i]; 27 | while z1 > 1 && (x1..=x2).cartesian_product(y1..=y2).all(|(x,y)| !space.contains_key(&(x,y,z1-1))) { 28 | z2 -= 1; 29 | z1 -= 1; 30 | } 31 | for (x,y) in (x1..=x2).cartesian_product(y1..=y2) { 32 | for z in z1..=z2 { 33 | space.insert((x,y,z), i); 34 | } 35 | if let Some(&j) = space.get(&(x,y,z1-1)) { 36 | adjacent[j].0.insert(i); 37 | adjacent[i].1.insert(j); 38 | } 39 | } 40 | bricks[i] = (x1, y1, z1, x2, y2, z2); 41 | } 42 | 43 | let mut falling = HashSet::new(); 44 | let (mut p1, mut p2) = (0, 0); 45 | for b in 0..bricks.len() { 46 | falling.clear(); 47 | disintegrate_all(&adjacent, &mut falling, b); 48 | p1 += (falling.len() == 1) as usize; 49 | p2 += falling.len() - 1; 50 | } 51 | (p1, p2) 52 | } 53 | -------------------------------------------------------------------------------- /2016/src/day12.ml: -------------------------------------------------------------------------------- 1 | let input = "cpy 1 a\ncpy 1 b\ncpy 26 d\njnz c 2\njnz 1 5\ncpy 7 c\ninc d\ndec c\njnz c -2\ncpy a c\ninc a\ndec b\njnz b -2\ncpy c b\ndec d\njnz d -6\ncpy 19 c\ncpy 11 d\ninc a\ndec d\njnz d -2\ndec c\njnz c -5" 2 | 3 | type opcode = 4 | | Cpy of (bool * int) * int 5 | | Jnz of (bool * int) * (bool * int) 6 | | Inc of int 7 | | Dec of int 8 | 9 | let parse_line s = 10 | let parse_reg_or_value s = match s.[0] with 11 | | 'a' -> (true, 0) 12 | | 'b' -> (true, 1) 13 | | 'c' -> (true, 2) 14 | | 'd' -> (true, 3) 15 | | _ -> (false, int_of_string s) in 16 | match s |> String.split_on_char ' ' with 17 | | ["cpy";a;b] -> Cpy(parse_reg_or_value a, b |> parse_reg_or_value |> snd) 18 | | ["jnz";a;b] -> Jnz(parse_reg_or_value a, parse_reg_or_value b) 19 | | ["inc";a] -> Inc(a |> parse_reg_or_value |> snd) 20 | | ["dec";a] -> Dec(a |> parse_reg_or_value |> snd) 21 | | _ -> failwith "unreachable" 22 | 23 | let exec_inst regs inst = 24 | let reg_val (is_reg, i) = if is_reg then regs.(i) else i in 25 | let () = match inst with 26 | | Cpy (src, dst) -> regs.(dst) <- reg_val src 27 | | Inc dst -> regs.(dst) <- regs.(dst) + 1 28 | | Dec dst -> regs.(dst) <- regs.(dst) - 1 29 | | _ -> () in 30 | match inst with 31 | | Jnz (src, off) when reg_val src != 0 -> reg_val off 32 | | _ -> 1 33 | 34 | let rec exec insts regs ip = 35 | if ip < List.length insts then 36 | let offset = exec_inst regs (ip |> List.nth insts) in 37 | exec insts regs (ip + offset) 38 | else regs.(0) 39 | 40 | let main () = 41 | let insts = input |> Aoc.parse_lines parse_line in 42 | let part1 = exec insts [|0;0;0;0|] 0 in 43 | let part2 = exec insts [|0;0;1;0|] 0 in 44 | (string_of_int part1, string_of_int part2) 45 | 46 | let () = Aoc.timer main 47 | -------------------------------------------------------------------------------- /2022/src/bin/11.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | #[derive(Clone, Copy, Debug)] 4 | enum Op { Add(u64), Mul(u64), Special } 5 | 6 | fn simulate(mut monkies: Vec<(Vec, Op, u64, usize, usize)>, rounds: usize, f: impl Fn(u64) -> u64) -> usize { 7 | let mut inspections = vec![0; monkies.len()]; 8 | for _ in 0..rounds { 9 | for i in 0..monkies.len() { 10 | let (items, op, div, m1, m2) = monkies[i].clone(); 11 | for item in items { 12 | let worry = match op { 13 | Op::Add(v) => f(item + v), 14 | Op::Mul(v) => f(item * v), 15 | Op::Special => f(item * item), 16 | }; 17 | monkies[if worry % div == 0 {m1} else {m2}].0.push(worry); 18 | } 19 | inspections[i] += monkies[i].0.len(); 20 | monkies[i].0.clear(); 21 | } 22 | } 23 | inspections.sort_by_key(|&x| -(x as isize)); 24 | inspections[0] * inspections[1] 25 | } 26 | 27 | #[aoc::main(11)] 28 | fn main(input: &str) -> (usize, usize) { 29 | let monkies = input.split("\n\n").map(|m| { 30 | let (l1, l2, l3, l4, l5) = m.lines().skip(1).map(str::trim).collect_tuple().unwrap(); 31 | ( 32 | l1["Starting items: ".len()..].split(", ").map(|x| x.parse().unwrap()).collect(), 33 | l2["Operation: new = old * ".len()..].parse().map(|v| if l2.contains('+') {Op::Add(v)} else {Op::Mul(v)}).unwrap_or(Op::Special), 34 | l3["Test: divisible by ".len()..].parse().unwrap(), 35 | l4["If true: throw to monkey ".len()..].parse().unwrap(), 36 | l5["If false: throw to monkey ".len()..].parse().unwrap(), 37 | ) 38 | }).collect::>(); 39 | let modulus = monkies.iter().map(|m| m.2).product::(); 40 | let p1 = simulate(monkies.clone(), 20, |x| x / 3); 41 | let p2 = simulate(monkies, 10000, |x| x % modulus); 42 | (p1, p2) 43 | } 44 | -------------------------------------------------------------------------------- /2021/src/bin/08.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn part1((_,b): &(Vec<&[u8]>, Vec<&[u8]>)) -> usize { 4 | b.iter().filter(|x| [2,3,4,7].contains(&x.len())).count() 5 | } 6 | 7 | fn display_digit(perm: &[u8], s: &[u8]) -> Option { 8 | let mut panels = 0; 9 | for c in s.iter().map(|c| perm[(c - b'a') as usize]) { 10 | panels |= 1 << (c - b'a'); 11 | } 12 | match panels { 13 | 0b1111101 => Some(0), 14 | 0b1100000 => Some(1), 15 | 0b1011011 => Some(2), 16 | 0b1111010 => Some(3), 17 | 0b1100110 => Some(4), 18 | 0b0111110 => Some(5), 19 | 0b0111111 => Some(6), 20 | 0b1101000 => Some(7), 21 | 0b1111111 => Some(8), 22 | 0b1111110 => Some(9), 23 | _ => None 24 | } 25 | } 26 | 27 | fn try_permutation(perm: &[u8], (a,b): &(Vec<&[u8]>, Vec<&[u8]>)) -> Option { 28 | let invalid = a.iter() 29 | .map(|s| display_digit(perm, s)) 30 | .any(|o| o.is_none()); 31 | if invalid { return None; } 32 | let ans = b.iter() 33 | .rev() 34 | .enumerate() 35 | .map(|(i,s)| display_digit(perm, s).unwrap() * 10usize.pow(i as u32)) 36 | .sum(); 37 | Some(ans) 38 | } 39 | 40 | fn part2(display: &(Vec<&[u8]>, Vec<&[u8]>)) -> usize { 41 | "abcdefg".bytes() 42 | .permutations(7) 43 | .find_map(|perm| try_permutation(&perm, display)) 44 | .unwrap() 45 | } 46 | 47 | #[aoc::main(08)] 48 | fn main(input: &str) -> (usize,usize) { 49 | let displays = input.lines() 50 | .map(|l| { 51 | let (a,b) = l.split_once(" | ").unwrap(); 52 | let x = a.split_whitespace().map(str::as_bytes).collect(); 53 | let y = b.split_whitespace().map(str::as_bytes).collect(); 54 | (x,y) 55 | }) 56 | .collect::>(); 57 | let p1 = displays.iter().map(part1).sum(); 58 | let p2 = displays.iter().map(part2).sum(); 59 | (p1,p2) 60 | } 61 | -------------------------------------------------------------------------------- /2016/src/day14.ml: -------------------------------------------------------------------------------- 1 | let input = "ihaygndm" 2 | 3 | let hash_num times i = 4 | let rec hash_impl left h = 5 | if left = 0 then h 6 | else h |> Digest.string |> Digest.to_hex |> hash_impl (left-1) 7 | in 8 | hash_impl times (input ^ (string_of_int i)) 9 | 10 | let rec all_fives res i h = 11 | if i + 4 = String.length h then res 12 | else 13 | let found = [1;2;3;4] |> List.for_all (fun j -> h.[i] = h.[i+j]) in 14 | let res = if found then (h.[i]::res) else res in 15 | all_fives res (i+1) h 16 | 17 | let rec hash_triple i h = 18 | if i = String.length h then None 19 | else 20 | let (a,b,c) = (h.[i-2], h.[i-1], h.[i]) in 21 | if a = b && b = c then Some a else hash_triple (i+1) h 22 | 23 | let handle_hash_num times i = 24 | let h = hash_num times i in 25 | let c = hash_triple 2 h in 26 | let fives = all_fives [] 0 h in 27 | (c, fives) 28 | 29 | let init_queue times = List.init 1000 (fun i -> i) 30 | |> List.to_seq 31 | |> Seq.map (handle_hash_num times) 32 | |> Queue.of_seq 33 | 34 | let find_64th times = 35 | let q = init_queue times in 36 | let rec find_impl res i = 37 | if List.length res = 64 then List.hd res 38 | else ( 39 | let (triple, fives) = Queue.take q in 40 | Queue.add (handle_hash_num times (i+1000)) q; 41 | let res = match triple with 42 | | Some c -> 43 | let valid_hashes = Queue.to_seq q 44 | |> Seq.filter (fun (_,fives) -> fives |> List.mem c) 45 | |> List.of_seq 46 | |> List.length in 47 | if valid_hashes > 0 then i::res else res 48 | | None -> res in 49 | find_impl res (i+1) 50 | ) 51 | in 52 | find_impl [] 0 53 | 54 | let main () = 55 | let part1 = find_64th 1 in 56 | let part2 = find_64th 2017 in 57 | (string_of_int part1, string_of_int part2) 58 | 59 | let () = Aoc.timer main 60 | -------------------------------------------------------------------------------- /2024/src/bin/17.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_assignments)] 2 | use itertools::Itertools; 3 | use z3::ast::{Ast, BV}; 4 | 5 | fn part1((mut a, mut b, mut c): (i64, i64, i64)) -> String { 6 | let mut output = Vec::new(); 7 | while a != 0{ 8 | b = a % 8; 9 | b = b ^ 5; 10 | c = a / (1 << b); 11 | b = b ^ c; 12 | b = b ^ 6; 13 | a = a / (1 << 3); 14 | output.push(b % 8); 15 | } 16 | output.iter().map(|x| x.to_string()).join(",") 17 | } 18 | 19 | #[aoc::main(17)] 20 | fn main(input: &str) -> (String, i64) { 21 | let mut ints = input 22 | .split(|c: char| !c.is_ascii_digit() && c != '-') 23 | .filter(|w| !w.is_empty()) 24 | .map(|w| w.parse::().unwrap()); 25 | let start_state = ints.next_tuple().unwrap(); 26 | let insts = ints.collect::>(); 27 | assert_eq!(insts, [2,4,1,5,7,5,4,3,1,6,0,3,5,5,3,0]); 28 | let p1 = part1(start_state); 29 | 30 | let ctx = z3::Context::new(&z3::Config::new()); 31 | let opt = z3::Optimize::new(&ctx); 32 | let s = BV::new_const(&ctx, "s", 64); 33 | let (mut a, mut b, mut c) = (s.clone(), BV::from_i64(&ctx, 0, 64), BV::from_i64(&ctx, 0, 64)); 34 | for x in [2,4,1,5,7,5,4,3,1,6,0,3,5,5,3,0] { 35 | b = &a & &BV::from_i64(&ctx, 7, 64); 36 | b = b ^ &BV::from_i64(&ctx, 5, 64); 37 | c = a.bvlshr(&b); 38 | b = b ^ c; 39 | b = b ^ &BV::from_i64(&ctx, 6, 64); 40 | a = a.bvlshr(&BV::from_i64(&ctx, 3, 64)); 41 | opt.assert(&(&b & &BV::from_i64(&ctx, 7, 64))._eq(&BV::from_i64(&ctx, x, 64))); 42 | } 43 | opt.assert(&(a._eq(&BV::from_i64(&ctx, 0, 64)))); 44 | opt.minimize(&s); 45 | assert_eq!(opt.check(&[]), z3::SatResult::Sat); 46 | let res = opt.get_model().unwrap().eval(&s, true).unwrap().as_i64().unwrap(); 47 | (p1, res) 48 | } 49 | --------------------------------------------------------------------------------