From 48e35ad3b0b0c62f936784e4aca70b17c3b0e3f9 Mon Sep 17 00:00:00 2001 From: Hunky Jimpjorps Date: Fri, 2 Feb 2024 17:05:12 -0500 Subject: renaming --- aoc2023-gleam/.DS_Store | Bin 0 -> 6148 bytes aoc2023-gleam/.github/workflows/test.yml | 23 +++ aoc2023-gleam/.gitignore | 6 + aoc2023-gleam/README.md | 28 +++ aoc2023-gleam/gleam.toml | 22 +++ aoc2023-gleam/manifest.toml | 29 ++++ aoc2023-gleam/src/.gitignore | 1 + aoc2023-gleam/src/day1/.gitignore | 1 + aoc2023-gleam/src/day1/solve.gleam | 64 +++++++ aoc2023-gleam/src/day10/.gitignore | 1 + aoc2023-gleam/src/day10/solve.gleam | 177 +++++++++++++++++++ aoc2023-gleam/src/day11/.gitignore | 1 + aoc2023-gleam/src/day11/solve.gleam | 84 +++++++++ aoc2023-gleam/src/day12/.gitignore | 1 + aoc2023-gleam/src/day12/solve.gleam | 91 ++++++++++ aoc2023-gleam/src/day13/.gitignore | 1 + aoc2023-gleam/src/day13/solve.gleam | 86 ++++++++++ aoc2023-gleam/src/day14/.gitignore | 1 + aoc2023-gleam/src/day14/solve.gleam | 93 ++++++++++ aoc2023-gleam/src/day15/.gitignore | 1 + aoc2023-gleam/src/day15/solve.gleam | 104 ++++++++++++ aoc2023-gleam/src/day16/.gitignore | 1 + aoc2023-gleam/src/day16/solve.gleam | 119 +++++++++++++ aoc2023-gleam/src/day17/.gitignore | 1 + aoc2023-gleam/src/day17/solve.gleam | 143 ++++++++++++++++ aoc2023-gleam/src/day18/.gitignore | 1 + aoc2023-gleam/src/day18/solve.gleam | 113 ++++++++++++ aoc2023-gleam/src/day19/.gitignore | 1 + aoc2023-gleam/src/day19/solve.gleam | 255 ++++++++++++++++++++++++++++ aoc2023-gleam/src/day2/.gitignore | 1 + aoc2023-gleam/src/day2/solve.gleam | 67 ++++++++ aoc2023-gleam/src/day20/.gitignore | 1 + aoc2023-gleam/src/day20/solve.gleam | 251 +++++++++++++++++++++++++++ aoc2023-gleam/src/day21/.gitignore | 1 + aoc2023-gleam/src/day21/solve.gleam | 25 +++ aoc2023-gleam/src/day22/.gitignore | 1 + aoc2023-gleam/src/day22/solve.gleam | 199 ++++++++++++++++++++++ aoc2023-gleam/src/day23/.gitignore | 1 + aoc2023-gleam/src/day23/solve.gleam | 194 +++++++++++++++++++++ aoc2023-gleam/src/day3/.gitignore | 1 + aoc2023-gleam/src/day3/solve.gleam | 180 ++++++++++++++++++++ aoc2023-gleam/src/day4/.gitignore | 1 + aoc2023-gleam/src/day4/solve.gleam | 98 +++++++++++ aoc2023-gleam/src/day5/.gitignore | 1 + aoc2023-gleam/src/day5/solve.gleam | 167 ++++++++++++++++++ aoc2023-gleam/src/day6/.gitignore | 1 + aoc2023-gleam/src/day6/solve.gleam | 85 ++++++++++ aoc2023-gleam/src/day7/.gitignore | 1 + aoc2023-gleam/src/day7/solve.gleam | 140 +++++++++++++++ aoc2023-gleam/src/day8/.gitignore | 1 + aoc2023-gleam/src/day8/solve.gleam | 91 ++++++++++ aoc2023-gleam/src/day9/.gitignore | 1 + aoc2023-gleam/src/day9/solve.gleam | 70 ++++++++ aoc2023-gleam/src/utilities/array2d.gleam | 74 ++++++++ aoc2023-gleam/src/utilities/memo.gleam | 57 +++++++ aoc2023-gleam/src/utilities/prioqueue.gleam | 62 +++++++ aoc2023-gleam/test/aoc2023_test.gleam | 5 + aoc2023-gleam/test/day1/day1_test.gleam | 57 +++++++ aoc2023-gleam/test/day10/day10_test.gleam | 60 +++++++ aoc2023-gleam/test/day11/day11_test.gleam | 66 +++++++ aoc2023-gleam/test/day12/day12_test.gleam | 48 ++++++ aoc2023-gleam/test/day13/day13_test.gleam | 76 +++++++++ aoc2023-gleam/test/day14/day14_test.gleam | 66 +++++++ aoc2023-gleam/test/day15/day15_test.gleam | 42 +++++ aoc2023-gleam/test/day16/day16_test.gleam | 66 +++++++ aoc2023-gleam/test/day17/day17_test.gleam | 55 ++++++ aoc2023-gleam/test/day18/day18_test.gleam | 74 ++++++++ aoc2023-gleam/test/day19/day19_test.gleam | 80 +++++++++ aoc2023-gleam/test/day2/day2_test.gleam | 57 +++++++ aoc2023-gleam/test/day20/day20_test.gleam | 56 ++++++ aoc2023-gleam/test/day21/day21_test.gleam | 38 +++++ aoc2023-gleam/test/day22/day22_test.gleam | 60 +++++++ aoc2023-gleam/test/day23/day23_test.gleam | 92 ++++++++++ aoc2023-gleam/test/day3/day3_test.gleam | 66 +++++++ aoc2023-gleam/test/day4/day4_test.gleam | 58 +++++++ aoc2023-gleam/test/day5/day5_test.gleam | 112 ++++++++++++ aoc2023-gleam/test/day6/day6_test.gleam | 50 ++++++ aoc2023-gleam/test/day7/day7_test.gleam | 56 ++++++ aoc2023-gleam/test/day8/day8_test.gleam | 61 +++++++ aoc2023-gleam/test/day9/day9_test.gleam | 52 ++++++ aoc2023-other/day-01/day-01.rkt | 38 ----- aoc2023-other/day-02/day-02-parser.rkt | 55 ------ aoc2023-other/day-02/day-02.rkt | 35 ---- aoc2023-other/day-03/day-03.rkt | 72 -------- aoc2023-other/day-04/day-04.rkt | 40 ----- aoc2023-other/day-05/day-05.rkt | 91 ---------- aoc2023-other/day-06/day-06.rkt | 32 ---- aoc2023-other/day-07/day-07.rkt | 82 --------- aoc2023-other/day-08/day-08.rkt | 36 ---- aoc2023-other/day-09/day-09-polynomial.rkt | 17 -- aoc2023-other/day-09/day-09.rkt | 32 ---- aoc2023-other/day-10/day-10.rkt | 97 ----------- aoc2023-other/day-11/day-11.rkt | 40 ----- aoc2023-other/day-12/day-12.rkt | 65 ------- aoc2023-other/day-13/day-13.rkt | 47 ----- aoc2023-other/day-14/day-14.rkt | 49 ------ aoc2023-other/day-15/day-15.rkt | 41 ----- aoc2023-other/day-16/day-16.rkt | 70 -------- aoc2023-other/day-17/day-17.rkt | 86 ---------- aoc2023-other/day-18/day-18.rkt | 48 ------ aoc2023-other/day-19/day-19.rkt | 134 --------------- aoc2023-other/day-20/day-20.rkt | 144 ---------------- aoc2023-other/day-21/day-21.rkt | 69 -------- aoc2023-other/day-22/day-22.rkt | 109 ------------ aoc2023-other/day-23/day-23.rkt | 89 ---------- aoc2023-other/day-24/day-24a.rkt | 51 ------ aoc2023-other/day-24/day-24b.rkt | 37 ---- aoc2023-other/day-25/day-25.rkt | 43 ----- aoc2023-racket/day-01/day-01.rkt | 38 +++++ aoc2023-racket/day-02/day-02-parser.rkt | 55 ++++++ aoc2023-racket/day-02/day-02.rkt | 35 ++++ aoc2023-racket/day-03/day-03.rkt | 72 ++++++++ aoc2023-racket/day-04/day-04.rkt | 40 +++++ aoc2023-racket/day-05/day-05.rkt | 91 ++++++++++ aoc2023-racket/day-06/day-06.rkt | 32 ++++ aoc2023-racket/day-07/day-07.rkt | 82 +++++++++ aoc2023-racket/day-08/day-08.rkt | 36 ++++ aoc2023-racket/day-09/day-09-polynomial.rkt | 17 ++ aoc2023-racket/day-09/day-09.rkt | 32 ++++ aoc2023-racket/day-10/day-10.rkt | 97 +++++++++++ aoc2023-racket/day-11/day-11.rkt | 40 +++++ aoc2023-racket/day-12/day-12.rkt | 65 +++++++ aoc2023-racket/day-13/day-13.rkt | 47 +++++ aoc2023-racket/day-14/day-14.rkt | 49 ++++++ aoc2023-racket/day-15/day-15.rkt | 41 +++++ aoc2023-racket/day-16/day-16.rkt | 70 ++++++++ aoc2023-racket/day-17/day-17.rkt | 86 ++++++++++ aoc2023-racket/day-18/day-18.rkt | 48 ++++++ aoc2023-racket/day-19/day-19.rkt | 134 +++++++++++++++ aoc2023-racket/day-20/day-20.rkt | 144 ++++++++++++++++ aoc2023-racket/day-21/day-21.rkt | 69 ++++++++ aoc2023-racket/day-22/day-22.rkt | 109 ++++++++++++ aoc2023-racket/day-23/day-23.rkt | 89 ++++++++++ aoc2023-racket/day-24/day-24a.rkt | 51 ++++++ aoc2023-racket/day-24/day-24b.rkt | 37 ++++ aoc2023-racket/day-25/day-25.rkt | 43 +++++ aoc2023/.DS_Store | Bin 6148 -> 0 bytes aoc2023/.github/workflows/test.yml | 23 --- aoc2023/.gitignore | 6 - aoc2023/README.md | 28 --- aoc2023/gleam.toml | 22 --- aoc2023/manifest.toml | 29 ---- aoc2023/src/.gitignore | 1 - aoc2023/src/aoc2023.gleam | 12 -- aoc2023/src/day1/.gitignore | 1 - aoc2023/src/day1/solve.gleam | 64 ------- aoc2023/src/day10/.gitignore | 1 - aoc2023/src/day10/solve.gleam | 177 ------------------- aoc2023/src/day11/.gitignore | 1 - aoc2023/src/day11/solve.gleam | 84 --------- aoc2023/src/day12/.gitignore | 1 - aoc2023/src/day12/solve.gleam | 91 ---------- aoc2023/src/day13/.gitignore | 1 - aoc2023/src/day13/solve.gleam | 86 ---------- aoc2023/src/day14/.gitignore | 1 - aoc2023/src/day14/solve.gleam | 93 ---------- aoc2023/src/day15/.gitignore | 1 - aoc2023/src/day15/solve.gleam | 104 ------------ aoc2023/src/day16/.gitignore | 1 - aoc2023/src/day16/solve.gleam | 119 ------------- aoc2023/src/day17/.gitignore | 1 - aoc2023/src/day17/solve.gleam | 143 ---------------- aoc2023/src/day18/.gitignore | 1 - aoc2023/src/day18/solve.gleam | 113 ------------ aoc2023/src/day19/.gitignore | 1 - aoc2023/src/day19/solve.gleam | 255 ---------------------------- aoc2023/src/day2/.gitignore | 1 - aoc2023/src/day2/solve.gleam | 67 -------- aoc2023/src/day20/.gitignore | 1 - aoc2023/src/day20/solve.gleam | 251 --------------------------- aoc2023/src/day21/.gitignore | 1 - aoc2023/src/day21/solve.gleam | 25 --- aoc2023/src/day22/.gitignore | 1 - aoc2023/src/day22/solve.gleam | 199 ---------------------- aoc2023/src/day23/.gitignore | 1 - aoc2023/src/day23/solve.gleam | 194 --------------------- aoc2023/src/day3/.gitignore | 1 - aoc2023/src/day3/solve.gleam | 180 -------------------- aoc2023/src/day4/.gitignore | 1 - aoc2023/src/day4/solve.gleam | 98 ----------- aoc2023/src/day5/.gitignore | 1 - aoc2023/src/day5/solve.gleam | 167 ------------------ aoc2023/src/day6/.gitignore | 1 - aoc2023/src/day6/solve.gleam | 85 ---------- aoc2023/src/day7/.gitignore | 1 - aoc2023/src/day7/solve.gleam | 140 --------------- aoc2023/src/day8/.gitignore | 1 - aoc2023/src/day8/solve.gleam | 91 ---------- aoc2023/src/day9/.gitignore | 1 - aoc2023/src/day9/solve.gleam | 70 -------- aoc2023/src/utilities/array2d.gleam | 74 -------- aoc2023/src/utilities/memo.gleam | 57 ------- aoc2023/src/utilities/prioqueue.gleam | 62 ------- aoc2023/test/aoc2023_test.gleam | 5 - aoc2023/test/day1/day1_test.gleam | 57 ------- aoc2023/test/day10/day10_test.gleam | 60 ------- aoc2023/test/day11/day11_test.gleam | 66 ------- aoc2023/test/day12/day12_test.gleam | 48 ------ aoc2023/test/day13/day13_test.gleam | 76 --------- aoc2023/test/day14/day14_test.gleam | 66 ------- aoc2023/test/day15/day15_test.gleam | 42 ----- aoc2023/test/day16/day16_test.gleam | 66 ------- aoc2023/test/day17/day17_test.gleam | 55 ------ aoc2023/test/day18/day18_test.gleam | 74 -------- aoc2023/test/day19/day19_test.gleam | 80 --------- aoc2023/test/day2/day2_test.gleam | 57 ------- aoc2023/test/day20/day20_test.gleam | 56 ------ aoc2023/test/day21/day21_test.gleam | 38 ----- aoc2023/test/day22/day22_test.gleam | 60 ------- aoc2023/test/day23/day23_test.gleam | 92 ---------- aoc2023/test/day3/day3_test.gleam | 66 ------- aoc2023/test/day4/day4_test.gleam | 58 ------- aoc2023/test/day5/day5_test.gleam | 112 ------------ aoc2023/test/day6/day6_test.gleam | 50 ------ aoc2023/test/day7/day7_test.gleam | 56 ------ aoc2023/test/day8/day8_test.gleam | 61 ------- aoc2023/test/day9/day9_test.gleam | 52 ------ 217 files changed, 6423 insertions(+), 6435 deletions(-) create mode 100644 aoc2023-gleam/.DS_Store create mode 100644 aoc2023-gleam/.github/workflows/test.yml create mode 100644 aoc2023-gleam/.gitignore create mode 100644 aoc2023-gleam/README.md create mode 100644 aoc2023-gleam/gleam.toml create mode 100644 aoc2023-gleam/manifest.toml create mode 100644 aoc2023-gleam/src/.gitignore create mode 100644 aoc2023-gleam/src/day1/.gitignore create mode 100644 aoc2023-gleam/src/day1/solve.gleam create mode 100644 aoc2023-gleam/src/day10/.gitignore create mode 100644 aoc2023-gleam/src/day10/solve.gleam create mode 100644 aoc2023-gleam/src/day11/.gitignore create mode 100644 aoc2023-gleam/src/day11/solve.gleam create mode 100644 aoc2023-gleam/src/day12/.gitignore create mode 100644 aoc2023-gleam/src/day12/solve.gleam create mode 100644 aoc2023-gleam/src/day13/.gitignore create mode 100644 aoc2023-gleam/src/day13/solve.gleam create mode 100644 aoc2023-gleam/src/day14/.gitignore create mode 100644 aoc2023-gleam/src/day14/solve.gleam create mode 100644 aoc2023-gleam/src/day15/.gitignore create mode 100644 aoc2023-gleam/src/day15/solve.gleam create mode 100644 aoc2023-gleam/src/day16/.gitignore create mode 100644 aoc2023-gleam/src/day16/solve.gleam create mode 100644 aoc2023-gleam/src/day17/.gitignore create mode 100644 aoc2023-gleam/src/day17/solve.gleam create mode 100644 aoc2023-gleam/src/day18/.gitignore create mode 100644 aoc2023-gleam/src/day18/solve.gleam create mode 100644 aoc2023-gleam/src/day19/.gitignore create mode 100644 aoc2023-gleam/src/day19/solve.gleam create mode 100644 aoc2023-gleam/src/day2/.gitignore create mode 100644 aoc2023-gleam/src/day2/solve.gleam create mode 100644 aoc2023-gleam/src/day20/.gitignore create mode 100644 aoc2023-gleam/src/day20/solve.gleam create mode 100644 aoc2023-gleam/src/day21/.gitignore create mode 100644 aoc2023-gleam/src/day21/solve.gleam create mode 100644 aoc2023-gleam/src/day22/.gitignore create mode 100644 aoc2023-gleam/src/day22/solve.gleam create mode 100644 aoc2023-gleam/src/day23/.gitignore create mode 100644 aoc2023-gleam/src/day23/solve.gleam create mode 100644 aoc2023-gleam/src/day3/.gitignore create mode 100644 aoc2023-gleam/src/day3/solve.gleam create mode 100644 aoc2023-gleam/src/day4/.gitignore create mode 100644 aoc2023-gleam/src/day4/solve.gleam create mode 100644 aoc2023-gleam/src/day5/.gitignore create mode 100644 aoc2023-gleam/src/day5/solve.gleam create mode 100644 aoc2023-gleam/src/day6/.gitignore create mode 100644 aoc2023-gleam/src/day6/solve.gleam create mode 100644 aoc2023-gleam/src/day7/.gitignore create mode 100644 aoc2023-gleam/src/day7/solve.gleam create mode 100644 aoc2023-gleam/src/day8/.gitignore create mode 100644 aoc2023-gleam/src/day8/solve.gleam create mode 100644 aoc2023-gleam/src/day9/.gitignore create mode 100644 aoc2023-gleam/src/day9/solve.gleam create mode 100644 aoc2023-gleam/src/utilities/array2d.gleam create mode 100644 aoc2023-gleam/src/utilities/memo.gleam create mode 100644 aoc2023-gleam/src/utilities/prioqueue.gleam create mode 100644 aoc2023-gleam/test/aoc2023_test.gleam create mode 100644 aoc2023-gleam/test/day1/day1_test.gleam create mode 100644 aoc2023-gleam/test/day10/day10_test.gleam create mode 100644 aoc2023-gleam/test/day11/day11_test.gleam create mode 100644 aoc2023-gleam/test/day12/day12_test.gleam create mode 100644 aoc2023-gleam/test/day13/day13_test.gleam create mode 100644 aoc2023-gleam/test/day14/day14_test.gleam create mode 100644 aoc2023-gleam/test/day15/day15_test.gleam create mode 100644 aoc2023-gleam/test/day16/day16_test.gleam create mode 100644 aoc2023-gleam/test/day17/day17_test.gleam create mode 100644 aoc2023-gleam/test/day18/day18_test.gleam create mode 100644 aoc2023-gleam/test/day19/day19_test.gleam create mode 100644 aoc2023-gleam/test/day2/day2_test.gleam create mode 100644 aoc2023-gleam/test/day20/day20_test.gleam create mode 100644 aoc2023-gleam/test/day21/day21_test.gleam create mode 100644 aoc2023-gleam/test/day22/day22_test.gleam create mode 100644 aoc2023-gleam/test/day23/day23_test.gleam create mode 100644 aoc2023-gleam/test/day3/day3_test.gleam create mode 100644 aoc2023-gleam/test/day4/day4_test.gleam create mode 100644 aoc2023-gleam/test/day5/day5_test.gleam create mode 100644 aoc2023-gleam/test/day6/day6_test.gleam create mode 100644 aoc2023-gleam/test/day7/day7_test.gleam create mode 100644 aoc2023-gleam/test/day8/day8_test.gleam create mode 100644 aoc2023-gleam/test/day9/day9_test.gleam delete mode 100644 aoc2023-other/day-01/day-01.rkt delete mode 100644 aoc2023-other/day-02/day-02-parser.rkt delete mode 100644 aoc2023-other/day-02/day-02.rkt delete mode 100644 aoc2023-other/day-03/day-03.rkt delete mode 100644 aoc2023-other/day-04/day-04.rkt delete mode 100644 aoc2023-other/day-05/day-05.rkt delete mode 100644 aoc2023-other/day-06/day-06.rkt delete mode 100644 aoc2023-other/day-07/day-07.rkt delete mode 100644 aoc2023-other/day-08/day-08.rkt delete mode 100644 aoc2023-other/day-09/day-09-polynomial.rkt delete mode 100644 aoc2023-other/day-09/day-09.rkt delete mode 100644 aoc2023-other/day-10/day-10.rkt delete mode 100644 aoc2023-other/day-11/day-11.rkt delete mode 100644 aoc2023-other/day-12/day-12.rkt delete mode 100644 aoc2023-other/day-13/day-13.rkt delete mode 100644 aoc2023-other/day-14/day-14.rkt delete mode 100644 aoc2023-other/day-15/day-15.rkt delete mode 100644 aoc2023-other/day-16/day-16.rkt delete mode 100644 aoc2023-other/day-17/day-17.rkt delete mode 100644 aoc2023-other/day-18/day-18.rkt delete mode 100644 aoc2023-other/day-19/day-19.rkt delete mode 100644 aoc2023-other/day-20/day-20.rkt delete mode 100644 aoc2023-other/day-21/day-21.rkt delete mode 100644 aoc2023-other/day-22/day-22.rkt delete mode 100644 aoc2023-other/day-23/day-23.rkt delete mode 100644 aoc2023-other/day-24/day-24a.rkt delete mode 100644 aoc2023-other/day-24/day-24b.rkt delete mode 100644 aoc2023-other/day-25/day-25.rkt create mode 100644 aoc2023-racket/day-01/day-01.rkt create mode 100644 aoc2023-racket/day-02/day-02-parser.rkt create mode 100644 aoc2023-racket/day-02/day-02.rkt create mode 100644 aoc2023-racket/day-03/day-03.rkt create mode 100644 aoc2023-racket/day-04/day-04.rkt create mode 100644 aoc2023-racket/day-05/day-05.rkt create mode 100644 aoc2023-racket/day-06/day-06.rkt create mode 100644 aoc2023-racket/day-07/day-07.rkt create mode 100644 aoc2023-racket/day-08/day-08.rkt create mode 100644 aoc2023-racket/day-09/day-09-polynomial.rkt create mode 100644 aoc2023-racket/day-09/day-09.rkt create mode 100644 aoc2023-racket/day-10/day-10.rkt create mode 100644 aoc2023-racket/day-11/day-11.rkt create mode 100644 aoc2023-racket/day-12/day-12.rkt create mode 100644 aoc2023-racket/day-13/day-13.rkt create mode 100644 aoc2023-racket/day-14/day-14.rkt create mode 100644 aoc2023-racket/day-15/day-15.rkt create mode 100644 aoc2023-racket/day-16/day-16.rkt create mode 100644 aoc2023-racket/day-17/day-17.rkt create mode 100644 aoc2023-racket/day-18/day-18.rkt create mode 100644 aoc2023-racket/day-19/day-19.rkt create mode 100644 aoc2023-racket/day-20/day-20.rkt create mode 100644 aoc2023-racket/day-21/day-21.rkt create mode 100644 aoc2023-racket/day-22/day-22.rkt create mode 100644 aoc2023-racket/day-23/day-23.rkt create mode 100644 aoc2023-racket/day-24/day-24a.rkt create mode 100644 aoc2023-racket/day-24/day-24b.rkt create mode 100644 aoc2023-racket/day-25/day-25.rkt delete mode 100644 aoc2023/.DS_Store delete mode 100644 aoc2023/.github/workflows/test.yml delete mode 100644 aoc2023/.gitignore delete mode 100644 aoc2023/README.md delete mode 100644 aoc2023/gleam.toml delete mode 100644 aoc2023/manifest.toml delete mode 100644 aoc2023/src/.gitignore delete mode 100644 aoc2023/src/aoc2023.gleam delete mode 100644 aoc2023/src/day1/.gitignore delete mode 100644 aoc2023/src/day1/solve.gleam delete mode 100644 aoc2023/src/day10/.gitignore delete mode 100644 aoc2023/src/day10/solve.gleam delete mode 100644 aoc2023/src/day11/.gitignore delete mode 100644 aoc2023/src/day11/solve.gleam delete mode 100644 aoc2023/src/day12/.gitignore delete mode 100644 aoc2023/src/day12/solve.gleam delete mode 100644 aoc2023/src/day13/.gitignore delete mode 100644 aoc2023/src/day13/solve.gleam delete mode 100644 aoc2023/src/day14/.gitignore delete mode 100644 aoc2023/src/day14/solve.gleam delete mode 100644 aoc2023/src/day15/.gitignore delete mode 100644 aoc2023/src/day15/solve.gleam delete mode 100644 aoc2023/src/day16/.gitignore delete mode 100644 aoc2023/src/day16/solve.gleam delete mode 100644 aoc2023/src/day17/.gitignore delete mode 100644 aoc2023/src/day17/solve.gleam delete mode 100644 aoc2023/src/day18/.gitignore delete mode 100644 aoc2023/src/day18/solve.gleam delete mode 100644 aoc2023/src/day19/.gitignore delete mode 100644 aoc2023/src/day19/solve.gleam delete mode 100644 aoc2023/src/day2/.gitignore delete mode 100644 aoc2023/src/day2/solve.gleam delete mode 100644 aoc2023/src/day20/.gitignore delete mode 100644 aoc2023/src/day20/solve.gleam delete mode 100644 aoc2023/src/day21/.gitignore delete mode 100644 aoc2023/src/day21/solve.gleam delete mode 100644 aoc2023/src/day22/.gitignore delete mode 100644 aoc2023/src/day22/solve.gleam delete mode 100644 aoc2023/src/day23/.gitignore delete mode 100644 aoc2023/src/day23/solve.gleam delete mode 100644 aoc2023/src/day3/.gitignore delete mode 100644 aoc2023/src/day3/solve.gleam delete mode 100644 aoc2023/src/day4/.gitignore delete mode 100644 aoc2023/src/day4/solve.gleam delete mode 100644 aoc2023/src/day5/.gitignore delete mode 100644 aoc2023/src/day5/solve.gleam delete mode 100644 aoc2023/src/day6/.gitignore delete mode 100644 aoc2023/src/day6/solve.gleam delete mode 100644 aoc2023/src/day7/.gitignore delete mode 100644 aoc2023/src/day7/solve.gleam delete mode 100644 aoc2023/src/day8/.gitignore delete mode 100644 aoc2023/src/day8/solve.gleam delete mode 100644 aoc2023/src/day9/.gitignore delete mode 100644 aoc2023/src/day9/solve.gleam delete mode 100644 aoc2023/src/utilities/array2d.gleam delete mode 100644 aoc2023/src/utilities/memo.gleam delete mode 100644 aoc2023/src/utilities/prioqueue.gleam delete mode 100644 aoc2023/test/aoc2023_test.gleam delete mode 100644 aoc2023/test/day1/day1_test.gleam delete mode 100644 aoc2023/test/day10/day10_test.gleam delete mode 100644 aoc2023/test/day11/day11_test.gleam delete mode 100644 aoc2023/test/day12/day12_test.gleam delete mode 100644 aoc2023/test/day13/day13_test.gleam delete mode 100644 aoc2023/test/day14/day14_test.gleam delete mode 100644 aoc2023/test/day15/day15_test.gleam delete mode 100644 aoc2023/test/day16/day16_test.gleam delete mode 100644 aoc2023/test/day17/day17_test.gleam delete mode 100644 aoc2023/test/day18/day18_test.gleam delete mode 100644 aoc2023/test/day19/day19_test.gleam delete mode 100644 aoc2023/test/day2/day2_test.gleam delete mode 100644 aoc2023/test/day20/day20_test.gleam delete mode 100644 aoc2023/test/day21/day21_test.gleam delete mode 100644 aoc2023/test/day22/day22_test.gleam delete mode 100644 aoc2023/test/day23/day23_test.gleam delete mode 100644 aoc2023/test/day3/day3_test.gleam delete mode 100644 aoc2023/test/day4/day4_test.gleam delete mode 100644 aoc2023/test/day5/day5_test.gleam delete mode 100644 aoc2023/test/day6/day6_test.gleam delete mode 100644 aoc2023/test/day7/day7_test.gleam delete mode 100644 aoc2023/test/day8/day8_test.gleam delete mode 100644 aoc2023/test/day9/day9_test.gleam diff --git a/aoc2023-gleam/.DS_Store b/aoc2023-gleam/.DS_Store new file mode 100644 index 0000000..5172429 Binary files /dev/null and b/aoc2023-gleam/.DS_Store differ diff --git a/aoc2023-gleam/.github/workflows/test.yml b/aoc2023-gleam/.github/workflows/test.yml new file mode 100644 index 0000000..cf2096e --- /dev/null +++ b/aoc2023-gleam/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: erlef/setup-beam@v1 + with: + otp-version: "26.0.2" + gleam-version: "0.32.4" + rebar3-version: "3" + # elixir-version: "1.15.4" + - run: gleam deps download + - run: gleam test + - run: gleam format --check src test diff --git a/aoc2023-gleam/.gitignore b/aoc2023-gleam/.gitignore new file mode 100644 index 0000000..8248306 --- /dev/null +++ b/aoc2023-gleam/.gitignore @@ -0,0 +1,6 @@ +*.beam +*.ez +build +erl_crash.dump + +aoc.toml \ No newline at end of file diff --git a/aoc2023-gleam/README.md b/aoc2023-gleam/README.md new file mode 100644 index 0000000..3f534e8 --- /dev/null +++ b/aoc2023-gleam/README.md @@ -0,0 +1,28 @@ +# aoc2023 + +[![Package Version](https://img.shields.io/hexpm/v/aoc2023)](https://hex.pm/packages/aoc2023) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/aoc2023/) + +## Quick start + +```sh +gleam run # Run the project +gleam test # Run the tests +gleam shell # Run an Erlang shell +``` + +## Installation + +If available on Hex this package can be added to your Gleam project: + +```sh +gleam add aoc2023 +``` + +and its documentation can be found at . + +## Use + +* Set up a solution: `gleam run -m adglent/day ` +* Check against examples: `gleam test -- --modules=day/day_test` +* Get final answer: `gleam run -m day/solve

` \ No newline at end of file diff --git a/aoc2023-gleam/gleam.toml b/aoc2023-gleam/gleam.toml new file mode 100644 index 0000000..8190aef --- /dev/null +++ b/aoc2023-gleam/gleam.toml @@ -0,0 +1,22 @@ +name = "aoc2023" +version = "0.1.0" +gleam = ">= 0.33.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "username", repo = "project" } +# links = [{ title = "Website", href = "https://gleam.run" }] + +[dependencies] +gleam_stdlib = "~> 0.33" +simplifile = "~> 1.0" +gleam_erlang = "~> 0.23" +gleam_community_maths = "~> 1.0" +gleam_otp = "~> 0.8" +pqueue = "~> 2.0" + +[dev-dependencies] +adglent = "~> 1.2" diff --git a/aoc2023-gleam/manifest.toml b/aoc2023-gleam/manifest.toml new file mode 100644 index 0000000..900b5c0 --- /dev/null +++ b/aoc2023-gleam/manifest.toml @@ -0,0 +1,29 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "adglent", version = "1.2.0", build_tools = ["gleam"], requirements = ["glint", "gleam_http", "gleam_stdlib", "simplifile", "gleam_erlang", "gap", "gleam_httpc", "snag", "gleam_community_ansi", "tom", "gleam_otp"], otp_app = "adglent", source = "hex", outer_checksum = "A20D35001061F8AD602E3B92FB3AC0E1E4EEC642AD2AAE0ACEAD3A85F37DA7F0" }, + { name = "gap", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "5E369751DB547BFBDA7735878DC04DA31FCA3112193D61D5D7566010C7C8BA98" }, + { name = "gleam_community_ansi", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8B5A9677BC5A2738712BBAF2BA289B1D8195FDF962BBC769569976AD5E9794E1" }, + { name = "gleam_community_colour", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "036C206886AFB9F153C552700A7A0B4D2864E3BC96A20C77E5F34A013C051BE3" }, + { name = "gleam_community_maths", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_maths", source = "hex", outer_checksum = "1B9DB313E94A0E4674CA84C5D29F45ECFE211BFB38ABBD8B23737395F47D08B3" }, + { name = "gleam_erlang", version = "0.23.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "C21CFB816C114784E669FFF4BBF433535EEA9960FA2F216209B8691E87156B96" }, + { name = "gleam_http", version = "3.5.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "AECDA43AFD523D07A8F09068598A6E271C505278A0CB6F9C7A2E4365EAE8D11E" }, + { name = "gleam_httpc", version = "2.1.1", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "06AC1CA52C9BAA66C9D5C0303B2BF34E39AA1546BB96AEE496E4B06D513AB8C7" }, + { name = "gleam_otp", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang"], otp_app = "gleam_otp", source = "hex", outer_checksum = "18EF8242A5E54BA92F717C7222F03B3228AEE00D1F286D4C56C3E8C18AA2588E" }, + { name = "gleam_stdlib", version = "0.33.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "539E37A2AA5EBE8E75F4B74755E4CC604BD957C3000AC8D705A2024886A2738B" }, + { name = "glint", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_community_ansi", "gleam_community_colour", "snag"], otp_app = "glint", source = "hex", outer_checksum = "46E56049CD370D61F720D319D0AB970408C9336EEB918F08B5DCB1DCE9845FA3" }, + { name = "pqueue", version = "2.0.7", build_tools = ["rebar3"], requirements = [], otp_app = "pqueue", source = "hex", outer_checksum = "8B0204BB202335890E4E7F9B99A8EC0B84DDB8513EE298EB180EE9B3BCB4C859" }, + { name = "simplifile", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0BD6F0E7DA1A7E11D18B8AD48453225CAFCA4C8CFB4513D217B372D2866C501C" }, + { name = "snag", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "8FD70D8FB3728E08AC425283BB509BB0F012BE1AE218424A597CDE001B0EE589" }, + { name = "tom", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "5C5A9B8586C547F1F39542B1A3BBD9FEE17AFEAB51CE53B32B13D0D46B421249" }, +] + +[requirements] +adglent = { version = "~> 1.2" } +gleam_community_maths = { version = "~> 1.0" } +gleam_erlang = { version = "~> 0.23" } +gleam_otp = { version = "~> 0.8" } +gleam_stdlib = { version = "~> 0.33" } +pqueue = { version = "~> 2.0" } +simplifile = { version = "~> 1.0" } diff --git a/aoc2023-gleam/src/.gitignore b/aoc2023-gleam/src/.gitignore new file mode 100644 index 0000000..bc13a69 --- /dev/null +++ b/aoc2023-gleam/src/.gitignore @@ -0,0 +1 @@ +aoc2023.gleam \ No newline at end of file diff --git a/aoc2023-gleam/src/day1/.gitignore b/aoc2023-gleam/src/day1/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day1/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day1/solve.gleam b/aoc2023-gleam/src/day1/solve.gleam new file mode 100644 index 0000000..ed14bde --- /dev/null +++ b/aoc2023-gleam/src/day1/solve.gleam @@ -0,0 +1,64 @@ +import adglent.{First, Second} +import gleam/io +import gleam/list +import gleam/string +import gleam/regex.{type Match, Match} +import gleam/int + +pub fn part1(input: String) { + let assert Ok(re) = regex.from_string("[1-9]") + + input + |> string.split("\n") + |> list.fold( + 0, + fn(acc, s) { + let matches = regex.scan(s, with: re) + + let assert Ok(Match(content: first, ..)) = list.first(matches) + let assert Ok(Match(content: last, ..)) = list.last(matches) + let assert Ok(i) = int.parse(first <> last) + acc + i + }, + ) + |> string.inspect +} + +const substitutions = [ + #("one", "o1e"), + #("two", "t2o"), + #("three", "t3e"), + #("four", "4"), + #("five", "5e"), + #("six", "6"), + #("seven", "7n"), + #("eight", "e8t"), + #("nine", "n9e"), +] + +pub fn part2(input: String) { + list.fold( + over: substitutions, + from: input, + with: fn(acc, sub) { + let #(from, to) = sub + string.replace(in: acc, each: from, with: to) + }, + ) + |> part1 +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("1") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day10/.gitignore b/aoc2023-gleam/src/day10/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day10/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day10/solve.gleam b/aoc2023-gleam/src/day10/solve.gleam new file mode 100644 index 0000000..c33634d --- /dev/null +++ b/aoc2023-gleam/src/day10/solve.gleam @@ -0,0 +1,177 @@ +import adglent.{First, Second} +import gleam/bool +import gleam/dict.{type Dict} +import gleam/list +import gleam/int +import gleam/io +import gleam/set.{type Set} +import gleam/string + +type Posn { + Posn(row: Int, col: Int) +} + +fn add_posns(p1: Posn, p2: Posn) -> Posn { + Posn(p1.row + p2.row, p1.col + p2.col) +} + +type PipeGrid = + Dict(Posn, String) + +const north = Posn(-1, 0) + +const south = Posn(1, 0) + +const east = Posn(0, 1) + +const west = Posn(0, -1) + +const initial_directions = [ + #(north, ["|", "7", "F"]), + #(south, ["|", "J", "L"]), + #(east, ["-", "J", "7"]), + #(west, ["-", "F", "L"]), +] + +fn pipe_neighbors(pipe: String) -> List(Posn) { + case pipe { + "|" -> [north, south] + "-" -> [east, west] + "L" -> [north, east] + "F" -> [south, east] + "7" -> [south, west] + "J" -> [north, west] + _ -> panic as "bad pipe" + } +} + +fn make_grid(input: String) -> PipeGrid { + { + use r, row <- list.index_map(string.split(input, "\n")) + use c, col <- list.index_map(string.to_graphemes(row)) + #(Posn(r, c), col) + } + |> list.flatten + |> dict.from_list +} + +fn valid_start_direction(grid: PipeGrid, s: Posn) { + let assert [dir, ..] = { + use d <- list.filter_map(initial_directions) + let #(delta, valids) = d + let neighbor = add_posns(s, delta) + case dict.get(grid, neighbor) { + Ok(pipe) -> + case list.contains(valids, pipe) { + True -> Ok(neighbor) + False -> Error(Nil) + } + Error(_) -> Error(Nil) + } + } + dir +} + +fn to_next_pipe(current: Posn, grid: PipeGrid, acc: List(Posn)) { + let assert [prev, ..] = acc + let assert Ok(pipe) = dict.get(grid, current) + use <- bool.guard(pipe == "S", [current, ..acc]) + let assert [next] = { + pipe + |> pipe_neighbors + |> list.filter_map(fn(p) { + case add_posns(p, current) { + neighbor if neighbor == prev -> Error(Nil) + neighbor -> Ok(neighbor) + } + }) + } + to_next_pipe(next, grid, [current, ..acc]) +} + +pub fn part1(input: String) { + let grid = + input + |> make_grid + + let assert Ok(s) = + grid + |> dict.filter(fn(_, v) { v == "S" }) + |> dict.keys + |> list.first + + grid + |> valid_start_direction(s) + |> to_next_pipe(grid, [s]) + |> list.length + |> fn(i) { { { i - 1 } / 2 } } + |> string.inspect +} + +fn trace_ray(p: Posn, loop: Set(Posn), grid: PipeGrid) -> Bool { + use <- bool.guard(set.contains(loop, p), False) + int.is_odd(count_crossings(p, loop, grid, 0, "")) +} + +fn count_crossings( + p: Posn, + loop: Set(Posn), + grid: PipeGrid, + acc: Int, + corner: String, +) { + let maybe_cell = dict.get(grid, p) + use <- bool.guard(maybe_cell == Error(Nil), acc) + let assert Ok(cell) = maybe_cell + let next = add_posns(p, east) + case set.contains(loop, p) { + False -> count_crossings(next, loop, grid, acc, corner) + True -> + case corner, cell { + _, "|" -> count_crossings(next, loop, grid, acc + 1, corner) + _, "F" | _, "L" -> count_crossings(next, loop, grid, acc, cell) + "F", "J" | "L", "7" -> count_crossings(next, loop, grid, acc + 1, "") + "F", "7" | "L", "J" -> count_crossings(next, loop, grid, acc, "") + _, _ -> count_crossings(next, loop, grid, acc, corner) + } + } +} + +pub fn part2(input: String) { + let grid = + input + |> make_grid + + let assert Ok(s) = + grid + |> dict.filter(fn(_, v) { v == "S" }) + |> dict.keys + |> list.first + + let loop_pipes = + grid + |> valid_start_direction(s) + |> to_next_pipe(grid, [s]) + |> set.from_list + + grid + |> dict.keys + |> list.filter(trace_ray(_, loop_pipes, grid)) + |> list.length() + |> string.inspect +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("10") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day11/.gitignore b/aoc2023-gleam/src/day11/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day11/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day11/solve.gleam b/aoc2023-gleam/src/day11/solve.gleam new file mode 100644 index 0000000..35464a1 --- /dev/null +++ b/aoc2023-gleam/src/day11/solve.gleam @@ -0,0 +1,84 @@ +import adglent.{First, Second} +import gleam/io +import gleam/int +import gleam/string +import gleam/list + +type Posn { + Posn(x: Int, y: Int) +} + +fn find_empty(grid: List(List(String))) { + use acc, row, r <- list.index_fold(grid, []) + case list.unique(row) { + ["."] -> [r, ..acc] + _ -> acc + } +} + +fn count_prior_empty_ranks(rank: Int, empty_ranks: List(Int)) -> Int { + empty_ranks + |> list.drop_while(fn(r_empty) { r_empty > rank }) + |> list.length +} + +fn parse_with_expansion(input: String, expansion: Int) -> List(Posn) { + let add = expansion - 1 + let grid = + input + |> string.split("\n") + |> list.map(string.to_graphemes) + + let empty_row_list = find_empty(grid) + let empty_col_list = find_empty(list.transpose(grid)) + + { + use r, row <- list.index_map(grid) + use acc, cell, c <- list.index_fold(over: row, from: []) + + let p = Posn(r, c) + let empty_r = count_prior_empty_ranks(r, empty_row_list) + let empty_c = count_prior_empty_ranks(c, empty_col_list) + case cell { + "#" -> [Posn(p.x + empty_r * add, p.y + empty_c * add), ..acc] + _empty -> acc + } + } + |> list.flatten() +} + +fn all_distances(stars: List(Posn)) -> Int { + use acc, pair <- list.fold(list.combination_pairs(stars), 0) + let #(s1, s2) = pair + acc + int.absolute_value(s1.x - s2.x) + int.absolute_value(s1.y - s2.y) +} + +fn find_distances(input: String, expand_by: Int) -> String { + input + |> parse_with_expansion(expand_by) + |> all_distances + |> string.inspect +} + +pub fn part1(input: String) { + find_distances(input, 2) +} + +pub fn part2(input: String) { + find_distances(input, 1_000_000) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("11") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day12/.gitignore b/aoc2023-gleam/src/day12/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day12/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day12/solve.gleam b/aoc2023-gleam/src/day12/solve.gleam new file mode 100644 index 0000000..06c7098 --- /dev/null +++ b/aoc2023-gleam/src/day12/solve.gleam @@ -0,0 +1,91 @@ +import adglent.{First, Second} +import gleam/io +import gleam/string +import gleam/list +import gleam/int +import gleam/result +import utilities/memo.{type Cache} + +type ParserState = + #(String, List(Int), Int, Bool) + +fn parse_folds(input: String, folds: Int) { + let records = string.split(input, "\n") + use record <- list.map(records) + let assert Ok(#(template, sets_str)) = string.split_once(record, " ") + + let template = + template + |> list.repeat(folds) + |> list.intersperse("?") + |> string.concat + let sets = + sets_str + |> string.split(",") + |> list.map(int.parse) + |> result.values + |> list.repeat(folds) + |> list.flatten() + + #(template, sets) +} + +fn do_count( + template: String, + groups: List(Int), + left: Int, + gap: Bool, + cache: Cache(ParserState, Int), +) -> Int { + use <- memo.memoize(cache, #(template, groups, left, gap)) + case template, groups, left, gap { + "", [], 0, _ -> 1 + "?" <> t_rest, [g, ..g_rest], 0, False -> + do_count(t_rest, g_rest, g - 1, g == 1, cache) + { + do_count(t_rest, groups, 0, False, cache) + } + "?" <> t_rest, [], 0, False + | "?" <> t_rest, _, 0, True + | "." <> t_rest, _, 0, _ -> do_count(t_rest, groups, 0, False, cache) + "#" <> t_rest, [g, ..g_rest], 0, False -> + do_count(t_rest, g_rest, g - 1, g == 1, cache) + "?" <> t_rest, gs, l, False | "#" <> t_rest, gs, l, False -> + do_count(t_rest, gs, l - 1, l == 1, cache) + _, _, _, _ -> 0 + } +} + +fn count_solutions(acc: Int, condition: #(String, List(Int))) -> Int { + use cache: Cache(ParserState, Int) <- memo.create() + let #(template, groups) = condition + acc + do_count(template, groups, 0, False, cache) +} + +pub fn part1(input: String) { + input + |> parse_folds(1) + |> list.fold(0, count_solutions) + |> string.inspect +} + +pub fn part2(input: String) { + input + |> parse_folds(5) + |> list.fold(0, count_solutions) + |> string.inspect +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("12") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day13/.gitignore b/aoc2023-gleam/src/day13/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day13/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day13/solve.gleam b/aoc2023-gleam/src/day13/solve.gleam new file mode 100644 index 0000000..2b3fca2 --- /dev/null +++ b/aoc2023-gleam/src/day13/solve.gleam @@ -0,0 +1,86 @@ +import adglent.{First, Second} +import gleam/io +import gleam/list +import gleam/string +import gleam/bool + +type SymmetryType { + Horizontal(Int) + Vertical(Int) +} + +fn is_symmetric(xss: List(List(a)), errs: Int) { + let assert [left, ..right] = xss + do_is_symmetric([left], right, errs) +} + +fn do_is_symmetric( + left: List(List(a)), + right: List(List(a)), + errors: Int, +) -> Result(Int, Nil) { + use <- bool.guard(list.is_empty(right), Error(Nil)) + let assert [h, ..t] = right + let found_errors = + list.zip(list.flatten(left), list.flatten(right)) + |> list.filter(fn(tup) { tup.1 != tup.0 }) + |> list.length + case found_errors == errors { + True -> Ok(list.length(left)) + False -> do_is_symmetric([h, ..left], t, errors) + } +} + +fn get_symmetry_type(xss: List(List(String)), errors: Int) { + case is_symmetric(xss, errors) { + Ok(n) -> Horizontal(n) + _ -> { + let assert Ok(n) = is_symmetric(list.transpose(xss), errors) + Vertical(n) + } + } +} + +fn summarize_notes(symmetries: List(SymmetryType)) { + use acc, note <- list.fold(symmetries, 0) + case note { + Horizontal(n) -> 100 * n + Vertical(n) -> n + } + acc +} + +fn solve(input: String, errors: Int) { + input + |> string.split("\n\n") + |> list.map(fn(strs) { + strs + |> string.split("\n") + |> list.map(string.to_graphemes) + |> get_symmetry_type(errors) + }) + |> summarize_notes + |> string.inspect +} + +pub fn part1(input: String) { + solve(input, 0) +} + +pub fn part2(input: String) { + solve(input, 1) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("13") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day14/.gitignore b/aoc2023-gleam/src/day14/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day14/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day14/solve.gleam b/aoc2023-gleam/src/day14/solve.gleam new file mode 100644 index 0000000..1ad1a18 --- /dev/null +++ b/aoc2023-gleam/src/day14/solve.gleam @@ -0,0 +1,93 @@ +import adglent.{First, Second} +import gleam/dict +import gleam/int +import gleam/io +import gleam/list +import gleam/order +import gleam/string + +fn parse(input) { + input + |> string.split("\n") + |> list.map(string.to_graphemes) + |> list.transpose() +} + +fn roll_boulders(strs: List(String)) { + { + use chunks <- list.map(list.chunk(strs, fn(c) { c == "O" || c == "." })) + list.sort(chunks, order.reverse(string.compare)) + } + |> list.flatten +} + +fn score(matrix) { + use acc, col <- list.fold(matrix, 0) + acc + { + use col_acc, char, n <- list.index_fold(list.reverse(col), 0) + case char { + "O" -> col_acc + n + 1 + _ -> col_acc + } + } +} + +pub fn part1(input: String) { + input + |> parse + |> list.map(roll_boulders) + |> score() + |> string.inspect +} + +fn rotate(matrix) { + matrix + |> list.map(list.reverse) + |> list.transpose +} + +fn spin(matrix) { + use acc, _ <- list.fold(list.range(1, 4), matrix) + acc + |> list.map(roll_boulders) + |> rotate +} + +fn spin_cycle(matrix) { + let cache = dict.new() + check_if_seen(matrix, cache, 1_000_000_000) +} + +fn check_if_seen(matrix, cache, count) { + case dict.get(cache, matrix) { + Error(Nil) -> + check_if_seen(spin(matrix), dict.insert(cache, matrix, count), count - 1) + Ok(n) -> { + let assert Ok(extra) = int.modulo(count, n - count) + list.fold(list.range(1, extra), matrix, fn(acc, _) { spin(acc) }) + |> score + } + } +} + +pub fn part2(input: String) { + input + |> parse + |> spin_cycle + |> string.inspect +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("14") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day15/.gitignore b/aoc2023-gleam/src/day15/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day15/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day15/solve.gleam b/aoc2023-gleam/src/day15/solve.gleam new file mode 100644 index 0000000..a7d250c --- /dev/null +++ b/aoc2023-gleam/src/day15/solve.gleam @@ -0,0 +1,104 @@ +import adglent.{First, Second} +import gleam/io +import gleam/string +import gleam/list +import gleam/int +import gleam/dict.{type Dict} +import gleam/option.{None, Some} + +fn split(input: String) -> List(String) { + input + |> string.split(",") +} + +fn hash_algorithm(str: String) -> Int { + let codepoints = + str + |> string.to_utf_codepoints() + |> list.map(string.utf_codepoint_to_int) + use acc, c <- list.fold(codepoints, 0) + let assert Ok(acc) = int.modulo({ acc + c } * 17, 256) + acc +} + +pub fn part1(input: String) -> String { + input + |> split + |> list.fold(0, fn(acc, str) { acc + hash_algorithm(str) }) + |> string.inspect +} + +type Instruction { + Remove(label: String) + Insert(label: String, focal: Int) +} + +fn read_instruction(str: String) -> Instruction { + case string.split(str, "=") { + [label, focal_str] -> { + let assert Ok(focal) = int.parse(focal_str) + Insert(label, focal) + } + _ -> Remove(string.drop_right(str, 1)) + } +} + +fn parse_instructions(insts: List(String)) -> Dict(Int, List(#(String, Int))) { + use acc, inst <- list.fold(insts, dict.new()) + case read_instruction(inst) { + Remove(label) -> remove_lens(acc, label) + Insert(label, focal) -> insert_lens(acc, label, focal) + } +} + +fn remove_lens(boxes, label) { + use v <- dict.update(boxes, hash_algorithm(label)) + case v { + Some(lenses) -> + case list.key_pop(lenses, label) { + Ok(#(_, updated)) -> updated + Error(Nil) -> lenses + } + None -> [] + } +} + +fn insert_lens(boxes, label, focal) { + use v <- dict.update(boxes, hash_algorithm(label)) + case v { + Some(lenses) -> list.key_set(lenses, label, focal) + None -> [#(label, focal)] + } +} + +fn focusing_power(boxes: Dict(Int, List(#(String, Int)))) -> Int { + use acc, k, v <- dict.fold(boxes, 0) + let box_acc = { + use acc, lens, i <- list.index_fold(v, 0) + acc + lens.1 * { i + 1 } + } + acc + { k + 1 } * box_acc +} + +pub fn part2(input: String) -> String { + input + |> split + |> parse_instructions + |> focusing_power + |> string.inspect +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("15") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day16/.gitignore b/aoc2023-gleam/src/day16/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day16/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day16/solve.gleam b/aoc2023-gleam/src/day16/solve.gleam new file mode 100644 index 0000000..65ce36b --- /dev/null +++ b/aoc2023-gleam/src/day16/solve.gleam @@ -0,0 +1,119 @@ +import adglent.{First, Second} +import gleam/bool +import gleam/dict.{type Dict} +import gleam/io +import gleam/list +import gleam/result +import gleam/set.{type Set} +import utilities/array2d.{type Posn, Posn} + +type Direction { + Up + Right + Down + Left +} + +type Light { + Light(posn: Posn, dir: Direction) +} + +fn move(l: Light) -> Light { + let Light(p, dir) = l + case dir { + Up -> Light(..l, posn: Posn(..p, r: p.r - 1)) + Down -> Light(..l, posn: Posn(..p, r: p.r + 1)) + Left -> Light(..l, posn: Posn(..p, c: p.c - 1)) + Right -> Light(..l, posn: Posn(..p, c: p.c + 1)) + } +} + +fn transform(l: Light, cell: Result(String, Nil)) -> List(Light) { + use <- bool.guard(result.is_error(cell), []) + let assert Ok(c) = cell + let Light(p, dir) = l + case dir, c { + // no change + _, "." | Up, "|" | Down, "|" | Left, "-" | Right, "-" -> [l] + // diagonal mirrors + Left, "/" -> [Light(p, Down)] + Down, "/" -> [Light(p, Left)] + Right, "/" -> [Light(p, Up)] + Up, "/" -> [Light(p, Right)] + Left, "\\" -> [Light(p, Up)] + Up, "\\" -> [Light(p, Left)] + Right, "\\" -> [Light(p, Down)] + Down, "\\" -> [Light(p, Right)] + // splitters + Left, "|" | Right, "|" -> [Light(p, Up), Light(p, Down)] + Up, "-" | Down, "-" -> [Light(p, Left), Light(p, Right)] + _, _ -> panic as "unrecognized cell type" + } +} + +fn energize(lights: List(Light), visited: Set(Light), grid: Dict(Posn, String)) { + let next_positions = + lights + |> list.flat_map(fn(l) { + let next = move(l) + transform(next, dict.get(grid, next.posn)) + }) + |> list.filter(fn(l) { !set.contains(visited, l) }) + let all_visited = set.union(set.from_list(next_positions), visited) + case visited == all_visited { + True -> + set.fold(visited, set.new(), fn(acc, l) { set.insert(acc, l.posn) }) + |> set.to_list + |> list.length + False -> energize(next_positions, all_visited, grid) + } +} + +pub fn part1(input: String) { + let grid = array2d.parse_grid(input) + + [Light(Posn(0, -1), Right)] + |> energize(set.new(), grid) +} + +pub fn part2(input: String) { + let grid = array2d.parse_grid(input) + + let Posn(rows, cols) = { + use acc, p <- list.fold(dict.keys(grid), Posn(0, 0)) + case acc.r + acc.c > p.r + p.c { + True -> acc + False -> p + } + } + + let all_starts = + list.concat([ + list.map(list.range(0, rows), fn(r) { Light(Posn(r, -1), Right) }), + list.map(list.range(0, rows), fn(r) { Light(Posn(r, cols + 1), Left) }), + list.map(list.range(0, cols), fn(c) { Light(Posn(-1, c), Down) }), + list.map(list.range(0, cols), fn(c) { Light(Posn(rows + 1, c), Up) }), + ]) + + use acc, p <- list.fold(all_starts, 0) + let energized = energize([p], set.new(), grid) + case acc > energized { + True -> acc + False -> energized + } +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("16") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day17/.gitignore b/aoc2023-gleam/src/day17/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day17/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day17/solve.gleam b/aoc2023-gleam/src/day17/solve.gleam new file mode 100644 index 0000000..7a01c4d --- /dev/null +++ b/aoc2023-gleam/src/day17/solve.gleam @@ -0,0 +1,143 @@ +import adglent.{First, Second} +import gleam/bool +import gleam/dict.{type Dict} +import gleam/io +import gleam/list +import gleam/result +import gleam/string +import gleam/set.{type Set} +import utilities/array2d.{type Posn, Posn} +import utilities/prioqueue.{type PriorityQueue} + +type State { + State(posn: Posn, heatloss: Int, previous: Posn, history: List(Posn)) +} + +const deltas = [Posn(-1, 0), Posn(1, 0), Posn(0, -1), Posn(0, 1)] + +fn make_key(s: State) { + #(s.posn, same_dir(s)) +} + +fn same_dir(s: State) { + case s.history { + [] -> [] + [first, ..] as deltas -> + list.take_while(deltas, fn(d) { d == first }) + |> list.take(10) + } +} + +fn is_goal(s: State, min_run: Int, goal: Posn) { + goal == s.posn && list.length(same_dir(s)) >= min_run +} + +fn find_good_neighbors(max: Int, min: Int, s: State, grid) { + deltas + |> list.filter(eliminate_bad_neighbors(_, s, max, min, grid)) + |> list.map(make_state(_, s, grid)) +} + +fn eliminate_bad_neighbors(d: Posn, s: State, max, min, grid) { + let neighbor = array2d.add_posns(d, s.posn) + + use <- bool.guard( + neighbor == s.previous || !dict.has_key(grid, neighbor), + False, + ) + case same_dir(s), list.length(same_dir(s)) { + [prev, ..], l if l == max -> d != prev + _, 0 -> True + [prev, ..], l if l < min -> d == prev + _, _ -> True + } +} + +fn make_state(d: Posn, s: State, grid) { + let neighbor = array2d.add_posns(d, s.posn) + let assert Ok(heat_lost) = dict.get(grid, neighbor) + State( + posn: neighbor, + heatloss: s.heatloss + heat_lost, + previous: s.posn, + history: [d, ..s.history], + ) +} + +fn find_path( + grid: Dict(Posn, Int), + queue: PriorityQueue(State), + seen: Set(#(Posn, List(Posn))), + get_neighbors: fn(State) -> List(State), + is_goal: fn(State) -> Bool, +) { + let assert Ok(#(state, rest)) = prioqueue.pop(queue) + let key = + make_key( + state + |> io.debug, + ) + case set.contains(seen, key) { + True -> find_path(grid, rest, seen, get_neighbors, is_goal) + False -> { + let now_seen = set.insert(seen, key) + let neighbors = get_neighbors(state) + case list.find(neighbors, is_goal) { + Ok(final) -> final.heatloss + _err -> { + let now_queue = + list.fold(neighbors, rest, fn(acc, n) { + prioqueue.insert(acc, n, n.heatloss) + }) + find_path(grid, now_queue, now_seen, get_neighbors, is_goal) + } + } + } + } +} + +pub fn part1(input: String) { + let raw_grid = + input + |> array2d.to_list_of_lists + + let grid = array2d.to_2d_intarray(raw_grid) + + let rmax = list.length(raw_grid) + let assert Ok(cmax) = + raw_grid + |> list.first + |> result.map(list.length) + + let start = State(Posn(0, 0), 0, Posn(0, 0), []) + let goal = Posn(rmax, cmax) + + find_path( + grid, + prioqueue.insert(prioqueue.new(), start, 0), + set.new(), + find_good_neighbors(0, 3, _, grid), + is_goal(_, 1, goal), + ) + |> string.inspect +} + +pub fn part2(input: String) { + input + |> string.inspect +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("17") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day18/.gitignore b/aoc2023-gleam/src/day18/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day18/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day18/solve.gleam b/aoc2023-gleam/src/day18/solve.gleam new file mode 100644 index 0000000..2c000f9 --- /dev/null +++ b/aoc2023-gleam/src/day18/solve.gleam @@ -0,0 +1,113 @@ +import adglent.{First, Second} +import gleam/io +import gleam/int +import gleam/list +import gleam/option.{Some} +import gleam/regex.{type Match, Match} +import gleam/string + +type Coord { + Coord(x: Int, y: Int) +} + +type Direction { + Up + Right + Down + Left +} + +type Dig { + Dig(dir: Direction, dist: Int) +} + +fn to_direction(c: String) { + case c { + "R" | "0" -> Right + "D" | "1" -> Down + "L" | "2" -> Left + "U" | "3" -> Up + _ -> panic + } +} + +fn parse_front(line: String) { + let assert Ok(re) = regex.from_string("(.) (.*) \\(.*\\)") + let assert [Match(submatches: [Some(dir), Some(dist)], ..)] = + regex.scan(with: re, content: line) + let assert Ok(n) = int.parse(dist) + Dig(to_direction(dir), n) +} + +fn parse_hex(line: String) { + let assert Ok(re) = regex.from_string("\\(#(.....)(.)\\)") + let assert [Match(submatches: [Some(dist), Some(dir)], ..)] = + regex.scan(with: re, content: line) + let assert Ok(n) = int.base_parse(dist, 16) + Dig(to_direction(dir), n) +} + +fn go(current: Coord, dig: Dig) { + case dig { + Dig(Up, n) -> Coord(current.x, current.y + n) + Dig(Right, n) -> Coord(current.x + n, current.y) + Dig(Down, n) -> Coord(current.x, current.y - n) + Dig(Left, n) -> Coord(current.x - n, current.y) + } +} + +fn double_triangle(c1: Coord, c2: Coord) { + { c1.x * c2.y } - { c2.x * c1.y } +} + +fn start_dig(digs: List(Dig)) { + do_next_dig(digs, Coord(0, 0), 0, 0) +} + +fn do_next_dig( + digs: List(Dig), + current: Coord, + area: Int, + perimeter: Int, +) -> Int { + case digs { + [] -> int.absolute_value(area) / 2 + { perimeter / 2 } + 1 + [dig, ..rest] -> { + let next = go(current, dig) + let area = area + double_triangle(current, next) + let perimeter = perimeter + dig.dist + do_next_dig(rest, next, area, perimeter) + } + } +} + +fn solve_with(input, f) { + input + |> string.split("\n") + |> list.map(f) + |> start_dig + |> string.inspect +} + +pub fn part1(input: String) { + solve_with(input, parse_front) +} + +pub fn part2(input: String) { + solve_with(input, parse_hex) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("18") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day19/.gitignore b/aoc2023-gleam/src/day19/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day19/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day19/solve.gleam b/aoc2023-gleam/src/day19/solve.gleam new file mode 100644 index 0000000..186e783 --- /dev/null +++ b/aoc2023-gleam/src/day19/solve.gleam @@ -0,0 +1,255 @@ +import adglent.{First, Second} +import gleam/io +import gleam/string +import gleam/dict.{type Dict} +import gleam/order.{type Order, Gt, Lt} +import gleam/regex.{type Match, Match} +import gleam/list +import gleam/option.{Some} +import gleam/int + +type Rating { + XtremelyCool + Musical + Aerodynamic + Shiny +} + +type Part { + Part(x: Int, m: Int, a: Int, s: Int) +} + +type Action { + Accept + Reject + SendTo(String) +} + +type Rule { + If(rating: Rating, comparison: Order, threshold: Int, do: Action) + Just(do: Action) +} + +type Workflow = + Dict(String, List(Rule)) + +type Interval { + Interval(min: Int, max: Int) +} + +type PartRange { + PartRange(x: Interval, m: Interval, a: Interval, s: Interval) +} + +fn parse_workflow(input: String) -> Workflow { + let assert Ok(re) = regex.from_string("(.*){(.*)}") + + use acc, line <- list.fold(string.split(input, "\n"), dict.new()) + let assert [Match(submatches: [Some(name), Some(all_rules)], ..)] = + regex.scan(re, line) + let rules = + string.split(all_rules, ",") + |> parse_rules + dict.insert(acc, name, rules) +} + +fn parse_rules(rules: List(String)) -> List(Rule) { + let assert Ok(re_rule) = regex.from_string("(.*)(>|<)(.*):(.*)") + use rule <- list.map(rules) + case regex.scan(re_rule, rule) { + [Match(submatches: [Some(r), Some(c), Some(t), Some(i)], ..)] -> + If(to_rating(r), to_comp(c), to_val(t), to_instruction(i)) + _nomatch -> Just(to_instruction(rule)) + } +} + +fn to_instruction(rule: String) { + case rule { + "A" -> Accept + "R" -> Reject + name -> SendTo(name) + } +} + +fn to_rating(rating: String) { + case rating { + "x" -> XtremelyCool + "m" -> Musical + "a" -> Aerodynamic + _s -> Shiny + } +} + +fn get_rating(part: Part, rating: Rating) -> Int { + case rating { + XtremelyCool -> part.x + Musical -> part.m + Aerodynamic -> part.a + Shiny -> part.s + } +} + +fn to_comp(comp: String) { + case comp { + "<" -> Lt + _gt -> Gt + } +} + +fn to_val(val: String) { + let assert Ok(n) = int.parse(val) + n +} + +fn parse_parts(input: String) -> List(Part) { + let assert Ok(re) = regex.from_string("{x=(.*),m=(.*),a=(.*),s=(.*)}") + + use part <- list.map(string.split(input, "\n")) + let assert [Match(submatches: [Some(x), Some(m), Some(a), Some(s)], ..)] = + regex.scan(re, part) + Part(to_val(x), to_val(m), to_val(a), to_val(s)) +} + +fn start_evaluating_workflow(part: Part, workflow: Workflow) -> Int { + evaluate_workflow(part, "in", workflow) +} + +fn evaluate_workflow(part: Part, name: String, workflow: Workflow) -> Int { + let assert Ok(rules) = dict.get(workflow, name) + case evaluate_rules(part, rules) { + Accept -> part.x + part.m + part.a + part.s + Reject -> 0 + SendTo(name) -> evaluate_workflow(part, name, workflow) + } +} + +fn evaluate_rules(part: Part, rules: List(Rule)) -> Action { + case rules { + [] -> panic + [Just(do), ..] -> do + [If(rating, comparison, threshold, do), ..rest] -> + case int.compare(get_rating(part, rating), threshold) == comparison { + True -> do + False -> evaluate_rules(part, rest) + } + } +} + +pub fn part1(input: String) { + let assert Ok(#(workflows_str, parts_str)) = string.split_once(input, "\n\n") + + let workflows = parse_workflow(workflows_str) + let parts = parse_parts(parts_str) + + list.map(parts, start_evaluating_workflow(_, workflows)) + |> int.sum + |> string.inspect +} + +fn size(interval: Interval) { + interval.max - interval.min + 1 +} + +fn all_in_range(pr: PartRange) { + size(pr.x) * size(pr.m) * size(pr.a) * size(pr.s) +} + +fn get_partrange(pr: PartRange, rating: Rating) -> Interval { + case rating { + XtremelyCool -> pr.x + Musical -> pr.m + Aerodynamic -> pr.a + Shiny -> pr.s + } +} + +fn update_partrange(pr: PartRange, rating: Rating, i: Interval) -> PartRange { + case rating { + XtremelyCool -> PartRange(..pr, x: i) + Musical -> PartRange(..pr, m: i) + Aerodynamic -> PartRange(..pr, a: i) + Shiny -> PartRange(..pr, s: i) + } +} + +pub fn part2(input: String) { + let assert Ok(#(workflows_str, _)) = string.split_once(input, "\n\n") + + let workflow = parse_workflow(workflows_str) + let start = Interval(1, 4000) + + PartRange(start, start, start, start) + |> evaluate_workflow_on_range("in", workflow) + |> string.inspect +} + +fn evaluate_workflow_on_range( + pr: PartRange, + name: String, + workflow: Workflow, +) -> Int { + let assert Ok(rules) = dict.get(workflow, name) + evaluate_rules_on_range(pr, rules, workflow) +} + +fn evaluate_rules_on_range( + pr: PartRange, + rules: List(Rule), + workflow: Workflow, +) -> Int { + case rules { + [Just(Accept), ..] -> all_in_range(pr) + [Just(Reject), ..] -> 0 + [Just(SendTo(name)), ..] -> evaluate_workflow_on_range(pr, name, workflow) + [If(rating, comparison, t, action), ..rest] -> { + let mod_i = get_partrange(pr, rating) + case comparison { + Lt -> + split_range( + keep: update_partrange(pr, rating, Interval(mod_i.min, t - 1)), + and_do: action, + pass: update_partrange(pr, rating, Interval(t, mod_i.max)), + and_eval: rest, + with: workflow, + ) + _gt -> + split_range( + keep: update_partrange(pr, rating, Interval(t + 1, mod_i.max)), + and_do: action, + pass: update_partrange(pr, rating, Interval(mod_i.min, t)), + and_eval: rest, + with: workflow, + ) + } + } + [] -> panic + } +} + +fn split_range( + keep keep: PartRange, + and_do action: Action, + pass pass: PartRange, + and_eval rest: List(Rule), + with workflow: Workflow, +) -> Int { + int.add( + evaluate_rules_on_range(keep, [Just(action)], workflow), + evaluate_rules_on_range(pass, rest, workflow), + ) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("19") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day2/.gitignore b/aoc2023-gleam/src/day2/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day2/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day2/solve.gleam b/aoc2023-gleam/src/day2/solve.gleam new file mode 100644 index 0000000..38e62d7 --- /dev/null +++ b/aoc2023-gleam/src/day2/solve.gleam @@ -0,0 +1,67 @@ +import adglent.{First, Second} +import gleam/io +import gleam/int +import gleam/string +import gleam/list + +pub type Game { + Game(red: Int, blue: Int, green: Int) +} + +fn parse(input: String) -> List(List(Game)) { + use line <- list.map(string.split(input, "\n")) + let assert [_, rounds] = string.split(line, on: ": ") + use match <- list.map(string.split(rounds, on: "; ")) + use acc, draw <- list.fold( + over: string.split(match, on: ", "), + from: Game(0, 0, 0), + ) + let assert Ok(#(n_str, color)) = string.split_once(draw, " ") + let assert Ok(n) = int.parse(n_str) + case color { + "red" -> Game(..acc, red: n) + "blue" -> Game(..acc, blue: n) + "green" -> Game(..acc, green: n) + _ -> panic as "unrecognized color" + } +} + +pub fn part1(input: String) { + use acc, game, i <- list.index_fold(parse(input), 0) + case list.any(game, fn(m) { m.red > 12 || m.green > 13 || m.blue > 14 }) { + False -> acc + i + 1 + True -> acc + } +} + +pub fn part2(input: String) { + { + use game <- list.map(parse(input)) + use acc, match <- list.fold(game, Game(0, 0, 0)) + let Game(red: red, green: green, blue: blue) = match + Game( + red: int.max(red, acc.red), + blue: int.max(blue, acc.blue), + green: int.max(green, acc.green), + ) + } + |> list.fold( + from: 0, + with: fn(acc, g: Game) { acc + g.red * g.blue * g.green }, + ) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("2") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day20/.gitignore b/aoc2023-gleam/src/day20/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day20/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day20/solve.gleam b/aoc2023-gleam/src/day20/solve.gleam new file mode 100644 index 0000000..9192dac --- /dev/null +++ b/aoc2023-gleam/src/day20/solve.gleam @@ -0,0 +1,251 @@ +import adglent.{First, Second} +import gleam/bool +import gleam/dict.{type Dict} +import gleam/io +import gleam/iterator.{type Iterator, type Step, Next} +import gleam/list +import gleam/queue.{type Queue} +import gleam/set +import gleam/string + +type Node { + Broadcaster(children: List(String)) + Flipflop(children: List(String), state: Power) + Conjunction(children: List(String), state: Dict(String, TonePitch)) + Ground +} + +type Tone { + Tone(from: String, to: String, pitch: TonePitch) +} + +type Power { + On + Off +} + +type TonePitch { + Low + High +} + +type State { + State( + nodes: Dict(String, Node), + low: Int, + high: Int, + cycle: Int, + sentry_nodes: Dict(String, Int), + ) +} + +fn flip_power(p: Power) -> Power { + case p { + On -> Off + Off -> On + } +} + +fn flip_flop_pitch(p: Power) -> TonePitch { + case p { + Off -> High + On -> Low + } +} + +fn combinator_pitch(state) { + case list.unique(dict.values(state)) { + [High] -> Low + _ -> High + } +} + +fn get_children(node) { + case node { + Flipflop(children: cs, ..) -> cs + Conjunction(children: cs, ..) -> cs + Broadcaster(children: cs) -> cs + Ground -> [] + } +} + +fn parse_node(input: String) -> #(String, Node) { + let assert [full_name, children_str] = string.split(input, on: " -> ") + let children = string.split(children_str, on: ", ") + + case full_name { + "%" <> name -> #(name, Flipflop(children: children, state: Off)) + "&" <> name -> #(name, Conjunction(children: children, state: dict.new())) + "broadcaster" -> #("broadcaster", Broadcaster(children: children)) + name -> #(name, Ground) + } +} + +fn to_initial_state(nodes: List(#(String, Node))) -> Dict(String, Node) { + let node_dict = dict.from_list(nodes) + let node_names = dict.keys(node_dict) + + let node_dict = + node_dict + |> dict.values + |> list.map(get_children) + |> list.concat + |> set.from_list + |> set.drop(dict.keys(node_dict)) + |> set.to_list + |> list.fold(node_dict, fn(acc, n) { dict.insert(acc, n, Ground) }) + + use name, node <- dict.map_values(node_dict) + case node { + Conjunction(state: _, children: chs) -> + node_names + |> list.filter(fn(n) { + let assert Ok(node) = dict.get(node_dict, n) + list.contains(get_children(node), any: name) + }) + |> list.map(fn(n) { #(n, Low) }) + |> dict.from_list() + |> fn(dict) { Conjunction(state: dict, children: chs) } + other -> other + } +} + +fn add_to_queue(from, children, pitch, queue) { + use acc, c <- list.fold(children, queue) + queue.push_back(acc, Tone(from: from, to: c, pitch: pitch)) +} + +fn add_tones(state: State, nodes, pitch, n) { + case pitch { + Low -> + State(..state, nodes: nodes, low: state.low + n, cycle: state.cycle + 1) + High -> + State(..state, nodes: nodes, high: state.high + n, cycle: state.cycle + 1) + } +} + +fn press_button_once(initial: State, queue: Queue(Tone)) { + let State(nodes: nodes, ..) = initial + + use <- bool.guard(queue.is_empty(queue), initial) + let assert Ok(#(Tone(from_name, to_name, pitch), rest)) = + queue.pop_front(queue) + + let assert Ok(to_node) = dict.get(nodes, to_name) + case to_node { + Broadcaster(children) -> { + let new_state = + add_tones(initial, nodes, pitch, list.length(children) + 1) + + let new_queue = add_to_queue(to_name, children, pitch, rest) + press_button_once(new_state, new_queue) + } + + Conjunction(state: state, children: children) -> { + let new_state = + state + |> dict.insert(from_name, pitch) + + let updated_nodes = + Conjunction(state: new_state, children: children) + |> dict.insert(nodes, to_name, _) + + let pitch_out = combinator_pitch(new_state) + + let new_state = + add_tones(initial, updated_nodes, pitch_out, list.length(children)) + |> check_for_interesting_node(from_name, pitch_out) + + add_to_queue(to_name, children, pitch_out, rest) + |> press_button_once(new_state, _) + } + + Flipflop(..) if pitch == High -> + press_button_once(State(..initial, cycle: initial.cycle + 1), rest) + + Flipflop(state: state, children: children) -> { + let updated_nodes = + Flipflop(state: flip_power(state), children: children) + |> dict.insert(nodes, to_name, _) + + let pitch_out = flip_flop_pitch(state) + let new_state = + add_tones(initial, updated_nodes, pitch_out, list.length(children)) + + add_to_queue(to_name, children, flip_flop_pitch(state), rest) + |> press_button_once(new_state, _) + } + + Ground(..) -> + press_button_once(State(..initial, cycle: initial.cycle + 1), rest) + } +} + +pub fn part1(input: String) { + let initial_state = + input + |> string.split(on: "\n") + |> list.map(parse_node) + |> to_initial_state() + + iterator.iterate( + from: State(initial_state, 0, 0, 1, dict.new()), + with: press_button_once(_, queue.from_list([ + Tone("button", "broadcaster", Low), + ])), + ) + |> iterator.at(1000) + |> fn(s) { + let assert Ok(State(high: high, low: low, ..)) = s + high * low + } + |> string.inspect +} + +fn check_for_interesting_node(state, name, pitch_out) { + case name, pitch_out { + "rk", High | "cd", High | "zf", High | "qx", High -> + State( + ..state, + sentry_nodes: dict.insert(state.sentry_nodes, name, state.cycle), + ) + _, _ -> state + } +} + +pub fn part2(input: String) { + let initial_state = + input + |> string.split(on: "\n") + |> list.map(parse_node) + |> to_initial_state() + + iterator.iterate( + from: State(initial_state, 0, 0, 1, dict.new()), + with: press_button_once(_, queue.from_list([ + Tone("button", "broadcaster", Low), + ])), + ) + |> iterator.drop_while(fn(s) { dict.size(s.sentry_nodes) < 4 }) + |> iterator.step + |> fn(s: Step(State, Iterator(State))) { + let assert Next(goal, _rest) = s + goal.sentry_nodes + } + |> string.inspect +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("20") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day21/.gitignore b/aoc2023-gleam/src/day21/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day21/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day21/solve.gleam b/aoc2023-gleam/src/day21/solve.gleam new file mode 100644 index 0000000..4d5c246 --- /dev/null +++ b/aoc2023-gleam/src/day21/solve.gleam @@ -0,0 +1,25 @@ +import adglent.{First, Second} +import gleam/io + +pub fn part1(input: String) { + todo as "Implement solution to part 1" +} + +pub fn part2(input: String) { + todo as "Implement solution to part 2" +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("21") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day22/.gitignore b/aoc2023-gleam/src/day22/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day22/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day22/solve.gleam b/aoc2023-gleam/src/day22/solve.gleam new file mode 100644 index 0000000..7bf2fb4 --- /dev/null +++ b/aoc2023-gleam/src/day22/solve.gleam @@ -0,0 +1,199 @@ +import adglent.{First, Second} +import gleam/bool +import gleam/dict.{type Dict} +import gleam/int +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/regex +import gleam/result +import gleam/set.{type Set} +import gleam/string + +type Point { + Point(x: Int, y: Int, z: Int) +} + +fn down_one(p: Point) -> Point { + Point(..p, z: p.z - 1) +} + +type Block { + Block(index: Int, from: Point, to: Point) +} + +fn compare_blocks(b1: Block, b2: Block) { + int.compare(b1.to.z, b2.to.z) +} + +type Space = + Dict(Point, Block) + +type AllBlocks = + Dict(Block, List(Point)) + +type BlockTree = + Dict(Int, Set(Int)) + +fn parse_block(index: Int, input: String) -> Block { + let assert Ok(re) = regex.from_string("(.*),(.*),(.*)~(.*),(.*),(.*)") + + let assert [scan] = regex.scan(with: re, content: input) + + let assert [x1, y1, z1, x2, y2, z2] = + scan.submatches + |> option.all + |> option.unwrap([]) + |> list.map(int.parse) + |> result.values + Block(index: index, from: Point(x1, y1, z1), to: Point(x2, y2, z2)) +} + +fn cross_section_at_level(b: Block, z: Int) -> List(Point) { + use x <- list.flat_map(list.range(b.from.x, b.to.x)) + use y <- list.map(list.range(b.from.y, b.to.y)) + Point(x, y, z) +} + +fn place_block(space: Space, b: Block, z: Int) -> Space { + let now_occupied = { + use x <- list.flat_map(list.range(b.from.x, b.to.x)) + use y <- list.flat_map(list.range(b.from.y, b.to.y)) + use z <- list.map(list.range(z, z + b.to.z - b.from.z)) + #(Point(x, y, z), b) + } + + dict.merge(space, dict.from_list(now_occupied)) +} + +fn find_lowest_level(space: Space, b: Block) -> Space { + do_find_lowest(space, b, b.from.z) +} + +fn do_find_lowest(space: Space, b: Block, z: Int) -> Space { + let is_intersecting = + list.any(cross_section_at_level(b, z), dict.has_key(space, _)) + + case z, is_intersecting { + 0, _ -> place_block(space, b, 1) + _, True -> place_block(space, b, z + 1) + _, False -> do_find_lowest(space, b, z - 1) + } +} + +fn to_block_positions(space: Space) -> AllBlocks { + use acc, point, index <- dict.fold(space, dict.new()) + use points <- dict.update(acc, index) + case points { + Some(ps) -> [point, ..ps] + None -> [point] + } +} + +fn above_blocks(blocks: AllBlocks) -> BlockTree { + use acc, block, points <- dict.fold(blocks, dict.new()) + use _ <- dict.update(acc, block.index) + { + use above_block, above_points <- dict.filter(blocks) + above_block.index != block.index + && list.any(above_points, fn(p) { list.contains(points, down_one(p)) }) + } + |> dict.keys + |> list.map(fn(b) { b.index }) + |> set.from_list +} + +fn below_blocks(blocktree: BlockTree) -> BlockTree { + use acc, block, _ <- dict.fold(blocktree, dict.new()) + use _ <- dict.update(acc, block) + { + use _, aboves <- dict.filter(blocktree) + set.contains(aboves, block) + } + |> dict.keys + |> set.from_list +} + +fn vulnerable_blocks(below_tree: BlockTree) -> List(Int) { + use block <- list.filter(dict.keys(below_tree)) + use bs <- list.any(dict.values(below_tree)) + !{ set.size(bs) == 0 } && { set.size(set.delete(bs, block)) == 0 } +} + +pub fn part1(input: String) { + let settled_blocks = + input + |> string.split("\n") + |> list.index_map(parse_block) + |> list.sort(compare_blocks) + |> list.fold(dict.new(), find_lowest_level) + + let block_positions = to_block_positions(settled_blocks) + let above_blocks = above_blocks(block_positions) + let below_blocks = below_blocks(above_blocks) + + let vulnerable_blocks = vulnerable_blocks(below_blocks) + + list.length(dict.keys(block_positions)) - list.length(vulnerable_blocks) +} + +fn all_falling_blocks(n: Int, above: BlockTree, below: BlockTree) { + let starting_set = set.insert(set.new(), n) + do_falling_blocks(starting_set, starting_set, above, below) +} + +fn do_falling_blocks( + fallen: Set(Int), + blocks: Set(Int), + above: BlockTree, + below: BlockTree, +) -> Int { + use <- bool.guard(set.size(blocks) == 0, set.size(fallen) - 1) + + let blocks_above = + { + use block <- list.flat_map(set.to_list(blocks)) + let assert Ok(supports) = dict.get(above, block) + use support <- list.filter(set.to_list(supports)) + let assert Ok(supportings) = dict.get(below, support) + use supporting <- list.all(set.to_list(supportings)) + set.contains(fallen, supporting) + } + |> set.from_list() + + set.union(fallen, blocks_above) + |> do_falling_blocks(blocks_above, above, below) +} + +pub fn part2(input: String) { + let settled_blocks = + input + |> string.split("\n") + |> list.index_map(parse_block) + |> list.sort(compare_blocks) + |> list.fold(dict.new(), find_lowest_level) + + let block_positions = to_block_positions(settled_blocks) + let above_blocks = above_blocks(block_positions) + let below_blocks = below_blocks(above_blocks) + + let vulnerable_blocks = vulnerable_blocks(below_blocks) + + use acc, b <- list.fold(vulnerable_blocks, 0) + acc + all_falling_blocks(b, above_blocks, below_blocks) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("22") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day23/.gitignore b/aoc2023-gleam/src/day23/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day23/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day23/solve.gleam b/aoc2023-gleam/src/day23/solve.gleam new file mode 100644 index 0000000..e1fe638 --- /dev/null +++ b/aoc2023-gleam/src/day23/solve.gleam @@ -0,0 +1,194 @@ +import adglent.{First, Second} +import gleam/int +import gleam/io +import gleam/dict.{type Dict} +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/string +import gleam/set.{type Set} +import gleam/bool +import utilities/array2d.{type Array2D, type Posn, Posn} + +type Path { + Unknown + Straight + Junction +} + +type Route { + Route(to: Posn, distance: Int) +} + +fn append_to_key(v: Option(List(a)), new: a) -> List(a) { + case v { + None -> [new] + Some(xs) -> [new, ..xs] + } +} + +fn first_parse_path(c: String) -> Result(Path, Nil) { + case c { + "#" -> Error(Nil) + _ -> Ok(Unknown) + } +} + +fn junction_neighbors(p: Posn) -> List(Posn) { + [Posn(..p, r: p.r + 1), Posn(..p, c: p.c + 1)] +} + +fn mark_junctions(trails: Array2D(Path)) -> Array2D(Path) { + use trail, _ <- dict.map_values(trails) + + let valid_neighbors = + trail + |> array2d.ortho_neighbors + |> list.filter(dict.has_key(trails, _)) + + case list.length(valid_neighbors) { + 2 -> Straight + _ -> Junction + } +} + +fn start_walking_to_next_junction( + start: Posn, + next: Posn, + trails: Array2D(Path), +) { + let seen = + set.new() + |> set.insert(start) + |> set.insert(next) + walk_to_next_junction(start, next, 1, seen, trails) +} + +fn walk_to_next_junction( + start: Posn, + current: Posn, + length: Int, + seen: Set(Posn), + trails: Array2D(Path), +) -> #(Posn, Route) { + let assert [next] = + current + |> array2d.ortho_neighbors + |> list.filter(fn(n) { dict.has_key(trails, n) && !set.contains(seen, n) }) + + case dict.get(trails, next) { + Ok(Junction) -> #(start, Route(to: next, distance: length + 1)) + _ -> { + let seen = set.insert(seen, current) + walk_to_next_junction(start, next, { length + 1 }, seen, trails) + } + } +} + +fn find_routes(junctions, trails) { + use junction <- list.flat_map(junctions) + use neighbor <- list.filter_map(junction_neighbors(junction)) + case dict.has_key(trails, neighbor) { + True -> Ok(start_walking_to_next_junction(junction, neighbor, trails)) + False -> Error(Nil) + } +} + +fn generate_routes( + junctions: List(Posn), + trails: Array2D(Path), +) -> Dict(Posn, List(Route)) { + use acc, #(from, route) <- list.fold( + find_routes(junctions, trails), + dict.new(), + ) + dict.update(acc, from, append_to_key(_, route)) +} + +fn generate_2way_routes( + junctions: List(Posn), + trails: Array2D(Path), +) -> Dict(Posn, List(Route)) { + use acc, #(from, route) <- list.fold( + find_routes(junctions, trails), + dict.new(), + ) + acc + |> dict.update(from, append_to_key(_, route)) + |> dict.update(route.to, append_to_key(_, Route(from, route.distance))) +} + +fn dfs(routes, from, to) { + let seen = set.insert(set.new(), from) + do_dfs(routes, from, to, 0, seen) +} + +fn do_dfs( + routes: Dict(Posn, List(Route)), + from: Posn, + to: Posn, + acc: Int, + seen: Set(Posn), +) -> Int { + use <- bool.guard(to == from, acc) + + let assert Ok(all_routes) = dict.get(routes, from) + let neighbors = list.filter(all_routes, fn(r) { !set.contains(seen, r.to) }) + + case neighbors { + [] -> 0 + neighbors -> + list.fold(neighbors, acc, fn(inner_acc, n) { + let score = + do_dfs(routes, n.to, to, acc + n.distance, set.insert(seen, n.to)) + int.max(score, inner_acc) + }) + } +} + +fn solve_using( + input: String, + using: fn(List(Posn), Dict(Posn, Path)) -> Dict(Posn, List(Route)), +) -> Int { + let min_row = 0 + let max_row = list.length(string.split(input, "\n")) - 1 + + let trails = + input + |> array2d.parse_grid_using(first_parse_path) + |> mark_junctions + + let junctions = + trails + |> dict.filter(fn(_, v) { v == Junction }) + |> dict.keys + + let assert Ok(start) = list.find(junctions, fn(j) { j.r == min_row }) + let assert Ok(end) = list.find(junctions, fn(j) { j.r == max_row }) + + let routes = using(junctions, trails) + + dfs(routes, start, end) +} + +pub fn part1(input: String) { + solve_using(input, generate_routes) +} + +pub fn part2(input: String) { + solve_using(input, generate_2way_routes) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("23") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day3/.gitignore b/aoc2023-gleam/src/day3/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day3/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day3/solve.gleam b/aoc2023-gleam/src/day3/solve.gleam new file mode 100644 index 0000000..ad975aa --- /dev/null +++ b/aoc2023-gleam/src/day3/solve.gleam @@ -0,0 +1,180 @@ +import adglent.{First, Second} +import gleam/io +import gleam/dict.{type Dict} +import gleam/string +import gleam/list +import gleam/int +import gleam/order.{type Order, Eq} + +type Coord { + Coord(x: Int, y: Int) +} + +type SymbolKind { + Gear + SomethingElse +} + +type Symbol { + Number(Int) + Symbol(SymbolKind) + Empty +} + +type Board = + Dict(Coord, Symbol) + +type Cell { + Cell(coord: Coord, symbol: Symbol) +} + +type Part { + Part(coords: List(Coord), part_number: Int) +} + +fn to_symbol(c: String) -> Symbol { + case int.parse(c), c { + Ok(n), _ -> Number(n) + _, "." -> Empty + _, "*" -> Symbol(Gear) + _, _ -> Symbol(SomethingElse) + } +} + +fn to_board(input: String) -> Board { + { + use y, r <- list.index_map(string.split(input, "\n")) + use x, c <- list.index_map(string.to_graphemes(r)) + #(Coord(x, y), to_symbol(c)) + } + |> list.flatten() + |> dict.from_list() +} + +fn cell_compare(a: Cell, b: Cell) -> Order { + case int.compare(a.coord.y, b.coord.y) { + Eq -> int.compare(a.coord.x, b.coord.x) + other -> other + } +} + +fn find_all_part_digits(b: Board) -> List(Cell) { + b + |> dict.filter(fn(_, v) { + case v { + Number(_) -> True + _ -> False + } + }) + |> dict.to_list() + |> list.map(fn(tup) { Cell(tup.0, tup.1) }) + |> list.sort(cell_compare) +} + +fn to_parts(cells: List(Cell)) -> List(Part) { + do_parts(cells, []) +} + +fn do_parts(cells: List(Cell), parts: List(Part)) -> List(Part) { + case cells { + [] -> parts + [Cell(next, Number(n)), ..t] -> { + case parts { + [] -> do_parts(t, [Part([next], n), ..parts]) + [Part([prev, ..] as coords, n0), ..rest_parts] -> + case { next.x - prev.x }, { next.y - prev.y } { + 1, 0 -> + do_parts(t, [Part([next, ..coords], n0 * 10 + n), ..rest_parts]) + _, _ -> do_parts(t, [Part([next], n), ..parts]) + } + _ -> panic + } + } + _ -> panic + } +} + +fn all_neighbors(c: Coord) -> List(Coord) { + use dx <- list.flat_map([-1, 0, 1]) + use dy <- list.filter_map([-1, 0, 1]) + case dx, dy { + 0, 0 -> Error(Nil) + _, _ -> Ok(Coord(c.x + dx, c.y + dy)) + } +} + +fn sum_valid_parts(acc: Int, part: Part, board: Board) -> Int { + let neighbors = + part.coords + |> list.flat_map(all_neighbors) + |> list.unique() + + let sym = [Ok(Symbol(Gear)), Ok(Symbol(SomethingElse))] + case list.any(neighbors, fn(c) { list.contains(sym, dict.get(board, c)) }) { + True -> acc + part.part_number + False -> acc + } +} + +pub fn part1(input: String) -> Int { + let board = to_board(input) + + board + |> find_all_part_digits + |> to_parts + |> list.fold(0, fn(acc, p) { sum_valid_parts(acc, p, board) }) +} + +fn to_part_with_neighbors(part: Part) -> Part { + part.coords + |> list.flat_map(all_neighbors) + |> list.unique + |> Part(part.part_number) +} + +fn find_part_numbers_near_gear(gear: Coord, parts: List(Part)) -> List(Int) { + use part <- list.filter_map(parts) + case list.contains(part.coords, gear) { + True -> Ok(part.part_number) + False -> Error(Nil) + } +} + +fn to_sum_of_gear_ratios(adjacent_parts: List(List(Int))) -> Int { + use acc, ps <- list.fold(adjacent_parts, 0) + case ps { + [p1, p2] -> acc + p1 * p2 + _ -> acc + } +} + +pub fn part2(input: String) -> Int { + let board = to_board(input) + + let parts = + board + |> find_all_part_digits + |> to_parts + |> list.map(to_part_with_neighbors) + + board + |> dict.filter(fn(_, v) { v == Symbol(Gear) }) + |> dict.keys + |> list.map(find_part_numbers_near_gear(_, parts)) + |> to_sum_of_gear_ratios +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("3") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day4/.gitignore b/aoc2023-gleam/src/day4/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day4/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day4/solve.gleam b/aoc2023-gleam/src/day4/solve.gleam new file mode 100644 index 0000000..34d6098 --- /dev/null +++ b/aoc2023-gleam/src/day4/solve.gleam @@ -0,0 +1,98 @@ +import adglent.{First, Second} +import gleam/bool +import gleam/dict.{type Dict} +import gleam/int +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import gleam/set.{type Set} +import gleam/string + +type Card { + Card(number: Int, winners: Int) +} + +fn numbers_to_set(str: String) -> Set(Int) { + str + |> string.split(" ") + |> list.map(int.parse) + |> result.values() + |> set.from_list() +} + +fn parse_card(card: String) -> Card { + let assert Ok(#("Card" <> n_str, rest)) = string.split_once(card, ": ") + let assert Ok(#(winning_str, has_str)) = string.split_once(rest, " | ") + let assert Ok(n) = int.parse(string.trim(n_str)) + + let winning = numbers_to_set(winning_str) + let has = numbers_to_set(has_str) + let winners = set.size(set.intersection(winning, has)) + + Card(number: n, winners: winners) +} + +fn win_points(n: Int) { + bool.guard(n < 2, n, fn() { 2 * win_points(n - 1) }) +} + +pub fn part1(input: String) { + use acc, c <- list.fold(string.split(input, "\n"), 0) + c + |> parse_card + |> fn(c: Card) { win_points(c.winners) } + |> int.add(acc) +} + +fn win_more_cards(cards: List(String), count: Dict(Int, Int)) { + case cards { + [] -> + count + |> dict.values + |> int.sum + [raw_card, ..rest] -> { + let card = parse_card(raw_card) + case card.winners { + 0 -> win_more_cards(rest, count) + n -> win_more_cards(rest, update_counts(n, card, count)) + } + } + } +} + +fn update_counts(n: Int, card: Card, count: Dict(Int, Int)) -> Dict(Int, Int) { + let assert Ok(bonus) = dict.get(count, card.number) + use acc, n <- list.fold(list.range(card.number + 1, card.number + n), count) + use c <- dict.update(acc, n) + case c { + Some(i) -> i + bonus + None -> panic as "won a card that doesn't exist in the card pile" + } +} + +pub fn part2(input: String) { + let cards = string.split(input, "\n") + + let count = + list.range(1, list.length(cards)) + |> list.map(fn(n) { #(n, 1) }) + |> dict.from_list() + + win_more_cards(cards, count) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("4") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day5/.gitignore b/aoc2023-gleam/src/day5/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day5/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day5/solve.gleam b/aoc2023-gleam/src/day5/solve.gleam new file mode 100644 index 0000000..58e2ae0 --- /dev/null +++ b/aoc2023-gleam/src/day5/solve.gleam @@ -0,0 +1,167 @@ +import adglent.{First, Second} +import gleam/io +import gleam/string +import gleam/result +import gleam/list.{Continue, Stop} +import gleam/int +import gleam/function + +// Types ------------------------------------------------------------------------------------------- + +pub type Almanac { + Almanac(seeds: List(Int), mappers: List(Mapper)) +} + +pub type MappingRange { + MRange(start: Int, end: Int, offset: Int) +} + +pub type SeedRange { + SRange(start: Int, end: Int) +} + +type Mapper = + List(MappingRange) + +// Parsing ----------------------------------------------------------------------------------------- + +fn parse_input(input: String) { + let assert ["seeds: " <> raw_seeds, ..raw_mappers] = + string.split(input, on: "\n\n") + + let seeds = string_to_int_list(raw_seeds) + let mappers = + list.map( + raw_mappers, + function.compose(string.split(_, on: "\n"), parse_mapper), + ) + Almanac(seeds, mappers) +} + +fn string_to_int_list(str: String) { + str + |> string.split(on: " ") + |> list.map(int.parse) + |> result.values +} + +fn parse_mapper(strs: List(String)) -> Mapper { + let assert [_, ..raw_ranges] = strs + list.map(raw_ranges, parse_mrange) + |> list.sort(fn(a, b) { int.compare(a.start, b.start) }) +} + +fn parse_mrange(str: String) -> MappingRange { + let assert [destination, source, range_width] = string_to_int_list(str) + MRange(source, source + range_width - 1, destination - source) +} + +// Part 1 ------------------------------------------------------------------------------------------ + +pub fn part1(input: String) { + let Almanac(seeds, mappers) = parse_input(input) + + list.map(seeds, list.fold(over: mappers, from: _, with: correspond)) + |> list.reduce(int.min) + |> result.unwrap(0) + |> string.inspect +} + +fn correspond(n: Int, mapper: Mapper) { + use acc, mrange <- list.fold_until(over: mapper, from: n) + case mrange.start <= acc && acc <= mrange.end { + True -> Stop(acc + mrange.offset) + False -> Continue(acc) + } +} + +// Part 2 ------------------------------------------------------------------------------------------ + +pub fn part2(input: String) { + let Almanac(seeds, mappers) = parse_input(input) + + let assert [SRange(answer, _), ..] = + seeds + |> list.sized_chunk(into: 2) + |> list.map(fn(chunk) { + let assert [start, length] = chunk + [SRange(start, start + length - 1)] + |> remap_all_seed_ranges(mappers) + }) + |> list.flatten() + |> list.sort(fn(a, b) { int.compare(a.start, b.start) }) + + string.inspect(answer) +} + +fn remap_all_seed_ranges(srs: List(SeedRange), mappers: List(Mapper)) { + case mappers { + [] -> srs + [mapper, ..rest] -> + list.flat_map(srs, remap_range(_, mapper)) + |> remap_all_seed_ranges(rest) + } +} + +fn remap_range(r: SeedRange, mapper: Mapper) -> List(SeedRange) { + do_remap_range(r, mapper, []) +} + +fn transform_range(r: SeedRange, mapper: MappingRange) -> SeedRange { + SRange(r.start + mapper.offset, r.end + mapper.offset) +} + +fn do_remap_range(r: SeedRange, mapper: Mapper, acc: List(SeedRange)) { + case mapper { + // no more mappings -> no mapping covers this range + [] -> [r, ..acc] + // range is to the left of current mapping -> no mapping covers this range + [m, ..] if r.end < m.start -> [r, ..acc] + // range is to the right of current mapping -> move to next mapping + [m, ..ms] if r.start > m.end -> do_remap_range(r, ms, acc) + // range is fully inside mapping -> range is transformed + [m, ..] if r.start >= m.start && r.end <= m.end -> [ + transform_range(r, m), + ..acc + ] + // range overlaps start but not end -> left side not transformed, right side transformed + [m, ..] if r.start < m.start && r.end <= m.end -> [ + SRange(r.start, m.start - 1), + transform_range(SRange(m.start, r.end), m), + ..acc + ] + // range overlaps end but not start -> left side transformed, right side moves to next mapping + [m, ..ms] if r.start >= m.start && r.end > m.end -> + do_remap_range( + SRange(m.end + 1, r.end), + ms, + [transform_range(SRange(r.start, m.end), m), ..acc], + ) + // mapping is fully inside range -> left not transformed, middle transformed, right to next + [m, ..ms] -> + do_remap_range( + SRange(m.end + 1, r.end), + ms, + [ + SRange(r.start, m.start - 1), + transform_range(SRange(m.start, m.end), m), + ..acc + ], + ) + } +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("5") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day6/.gitignore b/aoc2023-gleam/src/day6/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day6/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day6/solve.gleam b/aoc2023-gleam/src/day6/solve.gleam new file mode 100644 index 0000000..88044c4 --- /dev/null +++ b/aoc2023-gleam/src/day6/solve.gleam @@ -0,0 +1,85 @@ +import adglent.{First, Second} +import gleam/io +import gleam/string +import gleam/int +import gleam/list +import gleam/result + +type Race { + Race(time: Int, distance: Int) +} + +fn parse_with_bad_kerning(input: String) { + input + |> string.split("\n") + |> list.map(fn(str) { + str + |> string.split(" ") + |> list.map(int.parse) + |> result.values + }) + |> list.transpose + |> list.map(fn(ns) { + let assert [t, d] = ns + Race(t, d) + }) +} + +fn find_bound(race: Race, button_time: Int, step: Int) { + let travel_time = race.time - button_time + case button_time * travel_time > race.distance { + True -> button_time + False -> find_bound(race, button_time + step, step) + } +} + +fn lower_bound(race: Race) { + find_bound(race, 1, 1) +} + +fn upper_bound(race: Race) { + find_bound(race, race.time, -1) +} + +pub fn part1(input: String) { + { + use acc, race <- list.fold(parse_with_bad_kerning(input), 1) + acc * { upper_bound(race) - lower_bound(race) + 1 } + } + |> string.inspect +} + +fn parse_properly(input: String) { + input + |> string.replace(" ", "") + |> string.split("\n") + |> list.flat_map(string.split(_, ":")) + |> list.map(int.parse) + |> result.values +} + +pub fn part2(input: String) { + let assert [time, distance] = + input + |> parse_properly + + let race = Race(time, distance) + + upper_bound(race) - lower_bound(race) + 1 + |> string.inspect +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("6") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day7/.gitignore b/aoc2023-gleam/src/day7/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day7/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day7/solve.gleam b/aoc2023-gleam/src/day7/solve.gleam new file mode 100644 index 0000000..4454883 --- /dev/null +++ b/aoc2023-gleam/src/day7/solve.gleam @@ -0,0 +1,140 @@ +import adglent.{First, Second} +import gleam/bool +import gleam/function +import gleam/int +import gleam/io +import gleam/list +import gleam/order.{type Order, Eq, Lt} +import gleam/string + +// Types ------------------------------------------------------------------------------------------- + +type Hand { + Hand(cards: List(Int), wager: Int) +} + +// Common functions -------------------------------------------------------------------------------- + +fn parse_hand(str: String) -> Hand { + let assert [cards, wager] = string.split(str, " ") + let cards = + string.to_graphemes(cards) + |> list.map(card_rank) + let assert Ok(wager) = int.parse(wager) + + Hand(cards, wager) +} + +fn classify_hand(hand: Hand) -> Int { + case list.length(list.unique(hand.cards)), card_counts(hand) { + 1, _ -> 8 + 2, [1, 4] -> 7 + 2, [2, 3] -> 6 + 3, [1, 1, 3] -> 5 + 3, [1, 2, 2] -> 4 + 4, _ -> 3 + 5, _ -> 2 + _, _ -> 1 + } +} + +fn card_counts(hand: Hand) { + hand.cards + |> list.sort(int.compare) + |> list.chunk(function.identity) + |> list.map(list.length) + |> list.sort(int.compare) +} + +fn card_rank(card: String) -> Int { + case int.parse(card), card { + Ok(n), _ -> n + _, "A" -> 14 + _, "K" -> 13 + _, "Q" -> 12 + _, "J" -> 11 + _, "T" -> 10 + _, _ -> 1 + } +} + +fn compare_hands(hand1: Hand, hand2: Hand, using: fn(Hand) -> Int) -> Order { + case int.compare(using(hand1), using(hand2)) { + Eq -> compare_top_card(hand1.cards, hand2.cards) + other -> other + } +} + +fn compare_top_card(cards1: List(Int), cards2: List(Int)) -> Order { + use <- bool.guard(cards1 == [] || cards2 == [], Eq) + let assert [c1, ..rest1] = cards1 + let assert [c2, ..rest2] = cards2 + case int.compare(c1, c2) { + Eq -> compare_top_card(rest1, rest2) + other -> other + } +} + +fn part(input: String, comparator: fn(Hand, Hand) -> Order) { + input + |> string.split("\n") + |> list.map(parse_hand) + |> list.sort(comparator) + |> list.index_map(fn(i, h) { { i + 1 } * h.wager }) + |> int.sum + |> string.inspect +} + +// Part 1 ------------------------------------------------------------------------------------------ + +pub fn part1(input: String) { + part(input, compare_without_wilds) +} + +fn compare_without_wilds(hand1: Hand, hand2: Hand) { + compare_hands(hand1, hand2, classify_hand) +} + +// Part 2 ------------------------------------------------------------------------------------------ + +pub fn part2(input: String) { + part(string.replace(input, "J", "*"), compare_hands_considering_jokers) +} + +fn find_best_joker_substitution(hand: Hand) { + use acc, card <- list.fold(list.range(2, 14), Hand([], 0)) + let subbed_cards = { + use c <- list.map(hand.cards) + case c { + 1 -> card + other -> other + } + } + let subbed_hand = Hand(..hand, cards: subbed_cards) + case compare_hands(acc, subbed_hand, classify_hand) { + Lt -> subbed_hand + _ -> acc + } +} + +fn compare_hands_considering_jokers(hand1: Hand, hand2: Hand) -> Order { + use hand <- compare_hands(hand1, hand2) + hand + |> find_best_joker_substitution + |> classify_hand +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("7") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day8/.gitignore b/aoc2023-gleam/src/day8/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day8/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day8/solve.gleam b/aoc2023-gleam/src/day8/solve.gleam new file mode 100644 index 0000000..6b36e2d --- /dev/null +++ b/aoc2023-gleam/src/day8/solve.gleam @@ -0,0 +1,91 @@ +import adglent.{First, Second} +import gleam/bool +import gleam/dict.{type Dict} +import gleam/io +import gleam/iterator.{type Iterator, Next} +import gleam/list +import gleam/option.{Some} +import gleam/string +import gleam/regex.{type Match, Match} +import gleam_community/maths/arithmetics + +type Paths { + Paths(to_left: String, to_right: String) +} + +type Maze = + Dict(String, Paths) + +fn parse(input: String) -> #(Iterator(String), Dict(String, Paths)) { + let assert [directions_str, maze_str] = string.split(input, "\n\n") + + let directions = + directions_str + |> string.to_graphemes() + |> iterator.from_list + |> iterator.cycle + + let assert Ok(re) = regex.from_string("(...) = \\((...), (...)\\)") + let maze = + maze_str + |> string.split("\n") + |> list.map(fn(str) { + let assert [Match(submatches: [Some(name), Some(left), Some(right)], ..)] = + regex.scan(re, str) + #(name, Paths(left, right)) + }) + |> dict.from_list + + #(directions, maze) +} + +fn to_next_step( + current: String, + stop_at: String, + count: Int, + directions: Iterator(String), + maze: Maze, +) -> Int { + use <- bool.guard(string.ends_with(current, stop_at), count) + let assert Next(next_direction, rest_directions) = iterator.step(directions) + let assert Ok(paths) = dict.get(maze, current) + case next_direction { + "L" -> paths.to_left + "R" -> paths.to_right + _ -> panic as "bad direction" + } + |> to_next_step(stop_at, count + 1, rest_directions, maze) +} + +pub fn part1(input: String) -> Int { + let #(directions, maze) = parse(input) + + to_next_step("AAA", "ZZZ", 0, directions, maze) +} + +pub fn part2(input: String) -> Int { + let #(directions, maze) = parse(input) + + use acc, name <- list.fold(dict.keys(maze), 1) + case string.ends_with(name, "A") { + False -> acc + True -> + to_next_step(name, "Z", 0, directions, maze) + |> arithmetics.lcm(acc) + } +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("8") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/day9/.gitignore b/aoc2023-gleam/src/day9/.gitignore new file mode 100644 index 0000000..ae40cea --- /dev/null +++ b/aoc2023-gleam/src/day9/.gitignore @@ -0,0 +1 @@ +input.txt \ No newline at end of file diff --git a/aoc2023-gleam/src/day9/solve.gleam b/aoc2023-gleam/src/day9/solve.gleam new file mode 100644 index 0000000..a2cc7ae --- /dev/null +++ b/aoc2023-gleam/src/day9/solve.gleam @@ -0,0 +1,70 @@ +import adglent.{First, Second} +import gleam/io +import gleam/list +import gleam/string +import gleam/int + +fn parse(input: String, backwards backwards: Bool) -> List(List(Int)) { + use line <- list.map(string.split(input, "\n")) + use n_str <- list.map(maybe_backwards(string.split(line, " "), backwards)) + let assert Ok(n) = int.parse(n_str) + n +} + +fn maybe_backwards(xs: List(a), backwards: Bool) -> List(a) { + case backwards { + False -> list.reverse(xs) + True -> xs + } +} + +fn is_constant(ns: List(Int)) -> Bool { + case list.unique(ns) { + [_] -> True + _ -> False + } +} + +fn take_derivative(ns: List(Int)) -> List(Int) { + ns + |> list.window_by_2 + |> list.map(fn(tup) { tup.0 - tup.1 }) +} + +fn extrapolate(ns: List(Int)) { + case is_constant(ns), ns { + True, [n, ..] -> n + False, [n, ..] -> n + extrapolate(take_derivative(ns)) + _, _ -> panic as "list empty when it shouldn't be" + } +} + +fn part(input: String, backwards backwards: Bool) { + input + |> parse(backwards: backwards) + |> list.fold(0, fn(acc, ns) { extrapolate(ns) + acc }) + |> string.inspect +} + +pub fn part1(input: String) { + part(input, backwards: False) +} + +pub fn part2(input: String) { + part(input, backwards: True) +} + +pub fn main() { + let assert Ok(part) = adglent.get_part() + let assert Ok(input) = adglent.get_input("9") + case part { + First -> + part1(input) + |> adglent.inspect + |> io.println + Second -> + part2(input) + |> adglent.inspect + |> io.println + } +} diff --git a/aoc2023-gleam/src/utilities/array2d.gleam b/aoc2023-gleam/src/utilities/array2d.gleam new file mode 100644 index 0000000..8538129 --- /dev/null +++ b/aoc2023-gleam/src/utilities/array2d.gleam @@ -0,0 +1,74 @@ +import gleam/list +import gleam/dict.{type Dict} +import gleam/string +import gleam/int +import gleam/result + +pub type Posn { + Posn(r: Int, c: Int) +} + +pub type Array2D(a) = + Dict(Posn, a) + +pub fn add_posns(p1: Posn, p2: Posn) -> Posn { + case p1, p2 { + Posn(r1, c1), Posn(r2, c2) -> Posn(r1 + r2, c1 + c2) + } +} + +pub fn ortho_neighbors(p: Posn) -> List(Posn) { + let Posn(r, c) = p + [Posn(r + 1, c), Posn(r - 1, c), Posn(r, c + 1), Posn(r, c - 1)] +} + +pub fn to_2d_array(xss: List(List(a))) -> Array2D(a) { + to_2d_array_using(xss, fn(x) { Ok(x) }) +} + +pub fn to_2d_array_using( + xss: List(List(a)), + f: fn(a) -> Result(b, Nil), +) -> Array2D(b) { + { + use r, row <- list.index_map(xss) + use c, cell <- list.index_map(row) + case f(cell) { + Ok(contents) -> Ok(#(Posn(r, c), contents)) + Error(Nil) -> Error(Nil) + } + } + |> list.flatten + |> result.values + |> dict.from_list +} + +pub fn to_2d_intarray(xss: List(List(String))) -> Array2D(Int) { + { + use r, row <- list.index_map(xss) + use c, cell <- list.index_map(row) + let assert Ok(n) = int.parse(cell) + #(Posn(r, c), n) + } + |> list.flatten + |> dict.from_list +} + +pub fn to_list_of_lists(str: String) -> List(List(String)) { + str + |> string.split("\n") + |> list.map(string.to_graphemes) +} + +pub fn parse_grid(str: String) -> Array2D(String) { + parse_grid_using(str, fn(x) { Ok(x) }) +} + +pub fn parse_grid_using( + str: String, + f: fn(String) -> Result(a, Nil), +) -> Array2D(a) { + str + |> to_list_of_lists + |> to_2d_array_using(f) +} diff --git a/aoc2023-gleam/src/utilities/memo.gleam b/aoc2023-gleam/src/utilities/memo.gleam new file mode 100644 index 0000000..b06d8fd --- /dev/null +++ b/aoc2023-gleam/src/utilities/memo.gleam @@ -0,0 +1,57 @@ +import gleam/dict.{type Dict} +import gleam/otp/actor.{type Next, Continue, Stop} +import gleam/erlang/process.{type Subject, Normal} +import gleam/option.{None} + +const timeout = 1000 + +type Message(k, v) { + Shutdown + Get(key: k, client: Subject(Result(v, Nil))) + Set(key: k, value: v) +} + +type Server(k, v) = + Subject(Message(k, v)) + +pub opaque type Cache(k, v) { + Cache(server: Server(k, v)) +} + +fn handle_message( + message: Message(k, v), + dict: Dict(k, v), +) -> Next(Message(k, v), Dict(k, v)) { + case message { + Shutdown -> Stop(Normal) + Get(key, client) -> { + process.send(client, dict.get(dict, key)) + Continue(dict, None) + } + Set(key, value) -> Continue(dict.insert(dict, key, value), None) + } +} + +pub fn create(apply fun: fn(Cache(k, v)) -> t) -> t { + let assert Ok(server) = actor.start(dict.new(), handle_message) + let result = fun(Cache(server)) + process.send(server, Shutdown) + result +} + +pub fn set(in cache: Cache(k, v), for key: k, insert value: v) -> Nil { + process.send(cache.server, Set(key, value)) +} + +pub fn get(from cache: Cache(k, v), fetch key: k) -> Result(v, Nil) { + process.call(cache.server, fn(c) { Get(key, c) }, timeout) +} + +pub fn memoize(with cache: Cache(k, v), this key: k, apply fun: fn() -> v) -> v { + let result = case get(from: cache, fetch: key) { + Ok(value) -> value + Error(Nil) -> fun() + } + set(in: cache, for: key, insert: result) + result +} diff --git a/aoc2023-gleam/src/utilities/prioqueue.gleam b/aoc2023-gleam/src/utilities/prioqueue.gleam new file mode 100644 index 0000000..640748b --- /dev/null +++ b/aoc2023-gleam/src/utilities/prioqueue.gleam @@ -0,0 +1,62 @@ +//adapted from https://github.com/byronanderson/adventofcode2021/blob/main/gleam_advent/src/priority_queue.gleam + +import gleam/dict.{type Dict} + +type Ref + +@external(erlang, "erlang", "make_ref") +fn make_ref() -> Ref + +type PQueue(a) + +pub opaque type PriorityQueue(a) { + PriorityQueue(queue: PQueue(#(a, Ref)), refs: Dict(a, Ref)) +} + +type OutResult(a) { + Empty + Value(a, Int) +} + +@external(erlang, "pqueue2", "new") +fn new_() -> PQueue(a) + +@external(erlang, "pqueue2", "in") +fn insert_(item: a, prio: Int, queue: PQueue(a)) -> PQueue(a) + +@external(erlang, "pqueue2", "pout") +fn pop_(queue: PQueue(a)) -> #(OutResult(a), PQueue(a)) + +pub fn new() -> PriorityQueue(a) { + PriorityQueue(queue: new_(), refs: dict.new()) +} + +pub fn insert( + queue: PriorityQueue(a), + value: a, + priority: Int, +) -> PriorityQueue(a) { + let ref = make_ref() + + let refs = + queue.refs + |> dict.insert(value, ref) + + PriorityQueue(refs: refs, queue: insert_(#(value, ref), priority, queue.queue), + ) +} + +pub fn pop(queue: PriorityQueue(a)) -> Result(#(a, PriorityQueue(a)), Nil) { + case pop_(queue.queue) { + #(Value(#(value, ref), _priority), pqueue) -> { + let assert Ok(recently_enqueued_ref) = dict.get(queue.refs, value) + case recently_enqueued_ref == ref { + True -> Ok(#(value, PriorityQueue(refs: queue.refs, queue: pqueue))) + False -> pop(PriorityQueue(refs: queue.refs, queue: pqueue)) + } + } + #(Empty, _pqueue) -> { + Error(Nil) + } + } +} diff --git a/aoc2023-gleam/test/aoc2023_test.gleam b/aoc2023-gleam/test/aoc2023_test.gleam new file mode 100644 index 0000000..2b696a4 --- /dev/null +++ b/aoc2023-gleam/test/aoc2023_test.gleam @@ -0,0 +1,5 @@ +import showtime + +pub fn main() { + showtime.main() +} diff --git a/aoc2023-gleam/test/day1/day1_test.gleam b/aoc2023-gleam/test/day1/day1_test.gleam new file mode 100644 index 0000000..374653c --- /dev/null +++ b/aoc2023-gleam/test/day1/day1_test.gleam @@ -0,0 +1,57 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day1/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "1abc2 +pqr3stu8vwx +a1b2c3d4e5f +treb7uchet", + "142", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "two1nine +eightwothree +abcone2threexyz +xtwone3four +4nineeightseven2 +zoneight234 +7pqrstsixteen", + "281", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day10/day10_test.gleam b/aoc2023-gleam/test/day10/day10_test.gleam new file mode 100644 index 0000000..be9d82e --- /dev/null +++ b/aoc2023-gleam/test/day10/day10_test.gleam @@ -0,0 +1,60 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day10/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "7-F7- +.FJ|7 +SJLL7 +|F--J +LJ.LJ", + "8", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "........... +.S-------7. +.|F-----7|. +.||OOOOO||. +.||OOOOO||. +.|L-7OF-J|. +.|II|O|II|. +.L--JOL--J. +.....O.....", + "4", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day11/day11_test.gleam b/aoc2023-gleam/test/day11/day11_test.gleam new file mode 100644 index 0000000..8bb8c06 --- /dev/null +++ b/aoc2023-gleam/test/day11/day11_test.gleam @@ -0,0 +1,66 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day11/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#.....", + "374", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#.....", + "8410", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day12/day12_test.gleam b/aoc2023-gleam/test/day12/day12_test.gleam new file mode 100644 index 0000000..3daf0e9 --- /dev/null +++ b/aoc2023-gleam/test/day12/day12_test.gleam @@ -0,0 +1,48 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day12/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1", + "21", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day13/day13_test.gleam b/aoc2023-gleam/test/day13/day13_test.gleam new file mode 100644 index 0000000..7c65bed --- /dev/null +++ b/aoc2023-gleam/test/day13/day13_test.gleam @@ -0,0 +1,76 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day13/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..#", + "405", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..#", + "400", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day14/day14_test.gleam b/aoc2023-gleam/test/day14/day14_test.gleam new file mode 100644 index 0000000..8efa74e --- /dev/null +++ b/aoc2023-gleam/test/day14/day14_test.gleam @@ -0,0 +1,66 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day14/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "O....#.... +O.OO#....# +.....##... +OO.#O....O +.O.....O#. +O.#..O.#.# +..O..#O..O +.......O.. +#....###.. +#OO..#....", + "136", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "O....#.... +O.OO#....# +.....##... +OO.#O....O +.O.....O#. +O.#..O.#.# +..O..#O..O +.......O.. +#....###.. +#OO..#....", + "64", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day15/day15_test.gleam b/aoc2023-gleam/test/day15/day15_test.gleam new file mode 100644 index 0000000..0ecaecc --- /dev/null +++ b/aoc2023-gleam/test/day15/day15_test.gleam @@ -0,0 +1,42 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day15/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example("rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7", "1320"), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example("rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7", "145"), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day16/day16_test.gleam b/aoc2023-gleam/test/day16/day16_test.gleam new file mode 100644 index 0000000..036504e --- /dev/null +++ b/aoc2023-gleam/test/day16/day16_test.gleam @@ -0,0 +1,66 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day16/solve + +type Problem1AnswerType = + Int + +type Problem2AnswerType = + Int + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + ".|...\\.... +|.-.\\..... +.....|-... +........|. +.......... +.........\\ +..../.\\\\.. +.-.-/..|.. +.|....-|.\\ +..//.|....", + 46, + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + ".|...\\.... +|.-.\\..... +.....|-... +........|. +.......... +.........\\ +..../.\\\\.. +.-.-/..|.. +.|....-|.\\ +..//.|....", + 51, + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day17/day17_test.gleam b/aoc2023-gleam/test/day17/day17_test.gleam new file mode 100644 index 0000000..c1ebd22 --- /dev/null +++ b/aoc2023-gleam/test/day17/day17_test.gleam @@ -0,0 +1,55 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day17/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533", + "102", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day18/day18_test.gleam b/aoc2023-gleam/test/day18/day18_test.gleam new file mode 100644 index 0000000..7b510c8 --- /dev/null +++ b/aoc2023-gleam/test/day18/day18_test.gleam @@ -0,0 +1,74 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day18/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "R 6 (#70c710) +D 5 (#0dc571) +L 2 (#5713f0) +D 2 (#d2c081) +R 2 (#59c680) +D 2 (#411b91) +L 5 (#8ceee2) +U 2 (#caa173) +L 1 (#1b58a2) +U 2 (#caa171) +R 2 (#7807d2) +U 3 (#a77fa3) +L 2 (#015232) +U 2 (#7a21e3)", + "62", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "R 6 (#70c710) +D 5 (#0dc571) +L 2 (#5713f0) +D 2 (#d2c081) +R 2 (#59c680) +D 2 (#411b91) +L 5 (#8ceee2) +U 2 (#caa173) +L 1 (#1b58a2) +U 2 (#caa171) +R 2 (#7807d2) +U 3 (#a77fa3) +L 2 (#015232) +U 2 (#7a21e3)", + "952408144115", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day19/day19_test.gleam b/aoc2023-gleam/test/day19/day19_test.gleam new file mode 100644 index 0000000..c911de5 --- /dev/null +++ b/aoc2023-gleam/test/day19/day19_test.gleam @@ -0,0 +1,80 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day19/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "px{a<2006:qkq,m>2090:A,rfg} +pv{a>1716:R,A} +lnx{m>1548:A,A} +rfg{s<537:gd,x>2440:R,A} +qs{s>3448:A,lnx} +qkq{x<1416:A,crn} +crn{x>2662:A,R} +in{s<1351:px,qqz} +qqz{s>2770:qs,m<1801:hdj,R} +gd{a>3333:R,R} +hdj{m>838:A,pv} + +{x=787,m=2655,a=1222,s=2876} +{x=1679,m=44,a=2067,s=496} +{x=2036,m=264,a=79,s=2244} +{x=2461,m=1339,a=466,s=291} +{x=2127,m=1623,a=2188,s=1013}", + "19114", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "px{a<2006:qkq,m>2090:A,rfg} +pv{a>1716:R,A} +lnx{m>1548:A,A} +rfg{s<537:gd,x>2440:R,A} +qs{s>3448:A,lnx} +qkq{x<1416:A,crn} +crn{x>2662:A,R} +in{s<1351:px,qqz} +qqz{s>2770:qs,m<1801:hdj,R} +gd{a>3333:R,R} +hdj{m>838:A,pv} + +{x=787,m=2655,a=1222,s=2876} +{x=1679,m=44,a=2067,s=496} +{x=2036,m=264,a=79,s=2244} +{x=2461,m=1339,a=466,s=291} +{x=2127,m=1623,a=2188,s=1013}", + "167409079868000", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day2/day2_test.gleam b/aoc2023-gleam/test/day2/day2_test.gleam new file mode 100644 index 0000000..28a65da --- /dev/null +++ b/aoc2023-gleam/test/day2/day2_test.gleam @@ -0,0 +1,57 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day2/solve + +type Problem1AnswerType = + Int + +type Problem2AnswerType = + Int + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green +Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue +Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red +Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red +Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", + 8, + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green +Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue +Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red +Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red +Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", + 2286, + ), + Example("Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", 48), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day20/day20_test.gleam b/aoc2023-gleam/test/day20/day20_test.gleam new file mode 100644 index 0000000..92e8afb --- /dev/null +++ b/aoc2023-gleam/test/day20/day20_test.gleam @@ -0,0 +1,56 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day20/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a", + "32000000", + ), + Example( + "broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output +output -> ", + "11687500", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day21/day21_test.gleam b/aoc2023-gleam/test/day21/day21_test.gleam new file mode 100644 index 0000000..5f46808 --- /dev/null +++ b/aoc2023-gleam/test/day21/day21_test.gleam @@ -0,0 +1,38 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day21/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day22/day22_test.gleam b/aoc2023-gleam/test/day22/day22_test.gleam new file mode 100644 index 0000000..3f8c0ca --- /dev/null +++ b/aoc2023-gleam/test/day22/day22_test.gleam @@ -0,0 +1,60 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day22/solve + +type Problem1AnswerType = + Int + +type Problem2AnswerType = + Int + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "1,0,1~1,2,1 +0,0,2~2,0,2 +0,2,3~2,2,3 +0,0,4~0,2,4 +2,0,5~2,2,5 +0,1,6~2,1,6 +1,1,8~1,1,9", + 5, + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "1,0,1~1,2,1 +0,0,2~2,0,2 +0,2,3~2,2,3 +0,0,4~0,2,4 +2,0,5~2,2,5 +0,1,6~2,1,6 +1,1,8~1,1,9", + 7, + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day23/day23_test.gleam b/aoc2023-gleam/test/day23/day23_test.gleam new file mode 100644 index 0000000..206571c --- /dev/null +++ b/aoc2023-gleam/test/day23/day23_test.gleam @@ -0,0 +1,92 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day23/solve + +type Problem1AnswerType = + Int + +type Problem2AnswerType = + Int + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "#.##################### +#.......#########...### +#######.#########.#.### +###.....#.>.>.###.#.### +###v#####.#v#.###.#.### +###.>...#.#.#.....#...# +###v###.#.#.#########.# +###...#.#.#.......#...# +#####.#.#.#######.#.### +#.....#.#.#.......#...# +#.#####.#.#.#########v# +#.#...#...#...###...>.# +#.#.#v#######v###.###v# +#...#.>.#...>.>.#.###.# +#####v#.#.###v#.#.###.# +#.....#...#...#.#.#...# +#.#########.###.#.#.### +#...###...#...#...#.### +###.###.#.###v#####v### +#...#...#.#.>.>.#.>.### +#.###.###.#.###.#.#v### +#.....###...###...#...# +#####################.#", + 94, + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "#.##################### +#.......#########...### +#######.#########.#.### +###.....#.>.>.###.#.### +###v#####.#v#.###.#.### +###.>...#.#.#.....#...# +###v###.#.#.#########.# +###...#.#.#.......#...# +#####.#.#.#######.#.### +#.....#.#.#.......#...# +#.#####.#.#.#########v# +#.#...#...#...###...>.# +#.#.#v#######v###.###v# +#...#.>.#...>.>.#.###.# +#####v#.#.###v#.#.###.# +#.....#...#...#.#.#...# +#.#########.###.#.#.### +#...###...#...#...#.### +###.###.#.###v#####v### +#...#...#.#.>.>.#.>.### +#.###.###.#.###.#.#v### +#.....###...###...#...# +#####################.#", + 154, + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day3/day3_test.gleam b/aoc2023-gleam/test/day3/day3_test.gleam new file mode 100644 index 0000000..30e17a9 --- /dev/null +++ b/aoc2023-gleam/test/day3/day3_test.gleam @@ -0,0 +1,66 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day3/solve + +type Problem1AnswerType = + Int + +type Problem2AnswerType = + Int + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598..", + 4361, + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598..", + 467_835, + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day4/day4_test.gleam b/aoc2023-gleam/test/day4/day4_test.gleam new file mode 100644 index 0000000..324fe36 --- /dev/null +++ b/aoc2023-gleam/test/day4/day4_test.gleam @@ -0,0 +1,58 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day4/solve + +type Problem1AnswerType = + Int + +type Problem2AnswerType = + Int + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 +Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 +Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 +Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 +Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 +Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11", + 13, + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 +Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 +Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 +Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 +Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 +Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11", + 30, + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day5/day5_test.gleam b/aoc2023-gleam/test/day5/day5_test.gleam new file mode 100644 index 0000000..86a8692 --- /dev/null +++ b/aoc2023-gleam/test/day5/day5_test.gleam @@ -0,0 +1,112 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day5/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "seeds: 79 14 55 13 + +seed-to-soil map: +50 98 2 +52 50 48 + +soil-to-fertilizer map: +0 15 37 +37 52 2 +39 0 15 + +fertilizer-to-water map: +49 53 8 +0 11 42 +42 0 7 +57 7 4 + +water-to-light map: +88 18 7 +18 25 70 + +light-to-temperature map: +45 77 23 +81 45 19 +68 64 13 + +temperature-to-humidity map: +0 69 1 +1 0 69 + +humidity-to-location map: +60 56 37 +56 93 4", + "35", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "seeds: 79 14 55 13 + +seed-to-soil map: +50 98 2 +52 50 48 + +soil-to-fertilizer map: +0 15 37 +37 52 2 +39 0 15 + +fertilizer-to-water map: +49 53 8 +0 11 42 +42 0 7 +57 7 4 + +water-to-light map: +88 18 7 +18 25 70 + +light-to-temperature map: +45 77 23 +81 45 19 +68 64 13 + +temperature-to-humidity map: +0 69 1 +1 0 69 + +humidity-to-location map: +60 56 37 +56 93 4", + "46", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day6/day6_test.gleam b/aoc2023-gleam/test/day6/day6_test.gleam new file mode 100644 index 0000000..c551993 --- /dev/null +++ b/aoc2023-gleam/test/day6/day6_test.gleam @@ -0,0 +1,50 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day6/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "Time: 7 15 30 +Distance: 9 40 200", + "288", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "Time: 7 15 30 +Distance: 9 40 200", + "71503", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day7/day7_test.gleam b/aoc2023-gleam/test/day7/day7_test.gleam new file mode 100644 index 0000000..f7f8454 --- /dev/null +++ b/aoc2023-gleam/test/day7/day7_test.gleam @@ -0,0 +1,56 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day7/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483", + "6440", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483", + "5905", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day8/day8_test.gleam b/aoc2023-gleam/test/day8/day8_test.gleam new file mode 100644 index 0000000..2cd499a --- /dev/null +++ b/aoc2023-gleam/test/day8/day8_test.gleam @@ -0,0 +1,61 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day8/solve + +type Problem1AnswerType = + Int + +type Problem2AnswerType = + Int + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "LLR + +AAA = (BBB, BBB) +BBB = (AAA, ZZZ) +ZZZ = (ZZZ, ZZZ)", + 6, + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "LR + +11A = (11B, XXX) +11B = (XXX, 11Z) +11Z = (11B, XXX) +22A = (22B, XXX) +22B = (22C, 22C) +22C = (22Z, 22Z) +22Z = (22B, 22B) +XXX = (XXX, XXX)", + 6, + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-gleam/test/day9/day9_test.gleam b/aoc2023-gleam/test/day9/day9_test.gleam new file mode 100644 index 0000000..84fd3ba --- /dev/null +++ b/aoc2023-gleam/test/day9/day9_test.gleam @@ -0,0 +1,52 @@ +import gleam/list +import showtime/tests/should +import adglent.{type Example, Example} +import day9/solve + +type Problem1AnswerType = + String + +type Problem2AnswerType = + String + +/// Add examples for part 1 here: +/// ```gleam +///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] +/// ``` +const part1_examples: List(Example(Problem1AnswerType)) = [ + Example( + "0 3 6 9 12 15 +1 3 6 10 15 21 +10 13 16 21 30 45", + "114", + ), +] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +const part2_examples: List(Example(Problem2AnswerType)) = [ + Example( + "0 3 6 9 12 15 +1 3 6 10 15 21 +10 13 16 21 30 45", + "2", + ), +] + +pub fn part1_test() { + part1_examples + |> should.not_equal([]) + use example <- list.map(part1_examples) + solve.part1(example.input) + |> should.equal(example.answer) +} + +pub fn part2_test() { + part2_examples + |> should.not_equal([]) + use example <- list.map(part2_examples) + solve.part2(example.input) + |> should.equal(example.answer) +} diff --git a/aoc2023-other/day-01/day-01.rkt b/aoc2023-other/day-01/day-01.rkt deleted file mode 100644 index b720f79..0000000 --- a/aoc2023-other/day-01/day-01.rkt +++ /dev/null @@ -1,38 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(define calibration-values (fetch-aoc-input (find-session) 2023 1)) - -(define (to-number str) - (match (string->number str) - [#false (hash-ref word-to-digit str)] - [n n])) - -(define (parse-calibration-value v valid) - (for/fold ([acc '()] [value v] #:result (+ (to-number (first acc)) (* 10 (to-number (last acc))))) - ([_ (in-naturals)]) - #:break (equal? value "") - (let ([possible-prefix (findf (curry string-prefix? value) valid)]) - (if possible-prefix - (values (cons possible-prefix acc) (substring value 1)) - (values acc (substring value 1)))))) - -(define (total-calibration input valid) - (~> input - (string-trim) - (string-split "\n") - (map (λ~> (parse-calibration-value valid)) _) - (apply + _))) - -;; part 1 - -(define valid-for-part-1 (~> (range 1 10) (map ~a _))) -(total-calibration calibration-values valid-for-part-1) - -;; part 2 -(define word-to-digit - (hash "one" 1 "two" 2 "three" 3 "four" 4 "five" 5 "six" 6 "seven" 7 "eight" 8 "nine" 9)) -(define valid-for-part-2 (append valid-for-part-1 (hash-keys word-to-digit))) -(total-calibration calibration-values valid-for-part-2) diff --git a/aoc2023-other/day-02/day-02-parser.rkt b/aoc2023-other/day-02/day-02-parser.rkt deleted file mode 100644 index 76cc24f..0000000 --- a/aoc2023-other/day-02/day-02-parser.rkt +++ /dev/null @@ -1,55 +0,0 @@ -#lang racket - -(require racket/hash - advent-of-code - data/applicative - data/either - data/monad - megaparsack - megaparsack/text - threading) - -(struct game (id r g b)) - -(define cube/p - (do [n <- integer/p] - space/p - [c <- (or/p (string/p "red") - (string/p "blue") - (string/p "green"))] - (pure (cons c n)))) - -(define draw/p - (do [xs <- (many/p cube/p #:min 1 #:max 3 #:sep (string/p ", "))] - (pure (apply hash (flatten xs))))) - -(define all-draws/p - (do (string/p "Game ") - [id <- integer/p] - (string/p ": ") - [all-draws <- (many/p draw/p #:min 1 #:sep (string/p "; "))] - (define maxima - (foldl (curry hash-union #:combine max) - (hash "red" 0 "green" 0 "blue" 0) - all-draws)) - (pure (game id - (hash-ref maxima "red") - (hash-ref maxima "green") - (hash-ref maxima "blue"))))) - -(define game-maxima - (~>> (open-aoc-input (find-session) 2023 2) - port->lines - (map (λ~>> (parse-string all-draws/p) - from-either)))) - -;; part 1 -(for/sum ([m (in-list game-maxima)] - #:unless (or (> (game-r m) 12) - (> (game-g m) 13) - (> (game-b m) 14))) - (game-id m)) - -;; part 2 -(for/sum ([m (in-list game-maxima)]) - (* (game-r m) (game-g m) (game-b m))) diff --git a/aoc2023-other/day-02/day-02.rkt b/aoc2023-other/day-02/day-02.rkt deleted file mode 100644 index 973d20c..0000000 --- a/aoc2023-other/day-02/day-02.rkt +++ /dev/null @@ -1,35 +0,0 @@ -#lang racket - -(require advent-of-code) - -(struct roll (id red green blue)) - -(define all-games - (for/list ([raw-game (in-list (port->lines (open-aoc-input (find-session) 2023 2)))] - #:do [(define game (string-trim raw-game "Game ")) - (match-define (list id trials) (string-split game ": "))]) - (for/list ([trial (in-list (string-split trials "; "))]) - (for/fold ([acc (roll (string->number id) 0 0 0)]) - ([color (in-list (string-split trial ", "))]) - (match (string-split color) - [(list (app string->number n) "red") (struct-copy roll acc [red n])] - [(list (app string->number n) "green") (struct-copy roll acc [green n])] - [(list (app string->number n) "blue") (struct-copy roll acc [blue n])]))))) - -;; part 1 -(for/sum ([game (in-list all-games)] - #:when (andmap (λ (g) (and ((roll-red g) . <= . 12) - ((roll-green g) . <= . 13) - ((roll-blue g) . <= . 14))) - game)) - (roll-id (first game))) - -;; part 2 -(for/sum ([game (in-list all-games)]) - (define max-cubes - (for/fold ([acc (roll #f 0 0 0)]) ([r (in-list game)]) - (roll #f - (max (roll-red acc) (roll-red r)) - (max (roll-green acc) (roll-green r)) - (max (roll-blue acc) (roll-blue r))))) - (* (roll-red max-cubes) (roll-green max-cubes) (roll-blue max-cubes))) diff --git a/aoc2023-other/day-03/day-03.rkt b/aoc2023-other/day-03/day-03.rkt deleted file mode 100644 index 60e81a6..0000000 --- a/aoc2023-other/day-03/day-03.rkt +++ /dev/null @@ -1,72 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(struct posn (x y) #:transparent) -(struct part (n posns) #:transparent) - -(define (make-board port) - (for*/hash ([(row y) (in-indexed (port->lines port))] - [(col x) (in-indexed (string->list row))] - #:unless (equal? col #\.)) - (define v - (cond - [(string->number (string col))] - [(equal? col #\*) 'gear] - [else 'other])) - (values (posn x y) v))) - -(define board (~> (open-aoc-input (find-session) 2023 3 #:cache #true) make-board)) - -(define (posn (for/hash ([(k v) (in-hash b)] #:when (f v)) - (values k v)) - hash->list - (sort posn> part-posns (append-map neighbors) remove-duplicates)) -(define (symbol-in-neighbors b pt acc) - (~>> pt - to-neighbors - (ormap (λ (p) (let ([lookup (hash-ref b p #f)]) - (or (equal? lookup 'gear) (equal? lookup 'other))))) - ((λ (bool) (if bool (+ acc (part-n pt)) acc))))) - -;; part 1 -(define parts (~>> board (find-cells integer?) group-into-parts)) -(foldl (curry symbol-in-neighbors board) 0 parts) - -;; part 2 -(define gears (~>> board (find-cells (curry equal? 'gear)) (map car))) -(define parts-with-neighbors (map (λ (pt) (struct-copy part pt [posns (to-neighbors pt)])) parts)) - -(define (find-parts-near-gear pts gear) - (filter-map (λ (pt) (and (findf (curry equal? gear) (part-posns pt)) (part-n pt))) pts)) - -(~>> gears - (filter-map (λ~>> (find-parts-near-gear parts-with-neighbors) - ((λ (ns) (if (= (length ns) 2) (* (first ns) (second ns)) #f))))) - (apply +)) diff --git a/aoc2023-other/day-04/day-04.rkt b/aoc2023-other/day-04/day-04.rkt deleted file mode 100644 index 7a357c5..0000000 --- a/aoc2023-other/day-04/day-04.rkt +++ /dev/null @@ -1,40 +0,0 @@ -#lang racket - -(require advent-of-code - data/applicative - data/either - data/monad - megaparsack - megaparsack/text - threading) - -(struct card (n wins)) - -(define card/p - (do (string/p "Card") - (many/p space/p) - [n <- integer/p] - (string/p ":") - (many/p space/p) - [winners <- (many-until/p integer/p #:sep (many/p space/p) #:end (try/p (string/p " | ")))] - (many/p space/p) - [has <- (many+/p integer/p #:sep (many/p space/p))] - (pure (card n (set-count (set-intersect (first winners) has)))))) - -(define raw-cards (~> (open-aoc-input (find-session) 2023 4 #:cache #true) port->lines)) - -;; part 1 -(for/sum ([raw-card (in-list raw-cards)] - #:do [(match-define (success (card _ wins)) (parse-string card/p raw-card))] - #:unless (= wins 0)) - (expt 2 (sub1 wins))) - -;; part 2 -(for/fold ([counts (for/hash ([n (in-inclusive-range 1 (length raw-cards))]) - (values n 1))] - #:result (apply + (hash-values counts))) - ([raw-card (in-list raw-cards)] - #:do [(match-define (success (card n wins)) (parse-string card/p raw-card))]) - (define bonus-range (inclusive-range (+ n 1) (+ n wins))) - (define won-cards (hash-ref counts n)) - (foldl (λ (n acc) (hash-update acc n (curry + won-cards))) counts bonus-range)) diff --git a/aoc2023-other/day-05/day-05.rkt b/aoc2023-other/day-05/day-05.rkt deleted file mode 100644 index 5b9aa52..0000000 --- a/aoc2023-other/day-05/day-05.rkt +++ /dev/null @@ -1,91 +0,0 @@ -#lang racket - -(require advent-of-code - algorithms - threading) - -(struct map-range (start end offset)) -(struct seed-range (start end)) - -(define input (fetch-aoc-input (find-session) 2023 5 #:cache #true)) - -(match-define (list* raw-seeds raw-mappings) (string-split input "\n\n")) - -(define seeds-naive (~> raw-seeds (string-split " ") rest (map string->number _))) -(define mappers - (for/list ([raw-mapping (in-list raw-mappings)]) - (for/lists (map-ranges #:result (sort map-ranges < #:key map-range-start)) - ([raw-map-range (in-list (rest (string-split raw-mapping "\n")))] - #:do [(match-define (list dest source width) - (~> raw-map-range (string-split _ " ") (map string->number _)))]) - (map-range source (+ source width -1) (- dest source))))) - -;; part 1 -(define (in-map-range? n mr) - (<= (map-range-start mr) n (map-range-end mr))) - -(define (transform-value mapper n) - (match mapper - ['() n] - [(list* mr _) - #:when (in-map-range? n mr) - (+ n (map-range-offset mr))] - [(list* _ rest-mapper) (transform-value rest-mapper n)])) - -(for/lists (transforms #:result (apply min transforms)) - ([seed (in-list seeds-naive)]) - (foldl transform-value seed mappers)) - -;; part 2 -(define (remap-range r mapper [acc '()]) - (match-define (seed-range r-start r-end) r) - (match mapper - ; mapper exhausted - ['() (cons r acc)] - ; range to the left - not covered by this mapping, so keep as-is - [(list* (map-range m-start _ _) _) - #:when (< r-end m-start) - (cons r acc)] - ; range to the right - move to next map-range - [(list* (map-range _ m-end _) ms) - #:when (< m-end r-start) - (remap-range r ms acc)] - ; range is inside map-range - transform whole range - [(list* (map-range m-start m-end offset) _) - #:when (and (<= m-start r-start) (<= r-end m-end)) - (cons (seed-range (+ r-start offset) (+ r-end offset)) acc)] - ; range overlaps start only - keep left side, transform right side - [(list* (map-range m-start m-end offset) ms) - #:when (and (< r-start m-start) (<= r-end m-end)) - (remap-range (seed-range (add1 m-end) r-end) - ms - (cons (seed-range (+ m-start offset) (+ r-end offset)) acc))] - ; range overlaps end - transform left side, pass right side - [(list* (map-range m-start m-end offset) ms) - #:when (and (< m-start r-start) (<= m-end r-end)) - (remap-range (seed-range (add1 m-end) r-end) - ms - (cons (seed-range (+ r-start offset) (+ m-end offset)) acc))] - ; range overlaps whole map-range - keep left side, transform middle, pass right side - [(list* (map-range m-start m-end offset) ms) - (remap-range (seed-range (add1 m-end) r-end) - ms - (cons (seed-range (+ m-start offset) (+ m-end offset)) - (cons (seed-range (add1 m-end) r-end) acc)))])) - -(define (remap-ranges rs mappers) - (cond - [(empty? mappers) rs] - [else - (~>> rs (append-map (curryr remap-range (first mappers))) (remap-ranges _ (rest mappers)))])) - -(~> seeds-naive - (chunks-of 2) - (map (λ (xs) - (match-define (list start width) xs) - (~> (list (seed-range start (+ start width -1))) - (remap-ranges mappers) - (argmin seed-range-start _))) - _) - (argmin seed-range-start _) - seed-range-start) diff --git a/aoc2023-other/day-06/day-06.rkt b/aoc2023-other/day-06/day-06.rkt deleted file mode 100644 index 53ca9ee..0000000 --- a/aoc2023-other/day-06/day-06.rkt +++ /dev/null @@ -1,32 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(match-define (list times distances) - (~> (open-aoc-input (find-session) 2023 6 #:cache #true) port->lines)) - -;; part 1 -(define get-numbers (λ~> string-split (map string->number _) rest)) -(define (find-bound race-time dist button-time step) - (if (< dist (* button-time (- race-time button-time))) - button-time - (find-bound race-time dist (+ step button-time) step))) - -(define (lower-bound rtime dist) - (find-bound rtime dist 1 1)) -(define (upper-bound rtime dist) - (find-bound rtime dist rtime -1)) - -(for/fold ([acc 1]) - ([race-time (in-list (get-numbers times))] - [distance (in-list (get-numbers distances))]) - (* acc (add1 (- (upper-bound race-time distance) (lower-bound race-time distance))))) - -;; part 2 - -(define unkern (λ~>> get-numbers (apply ~a) string->number)) -(define big-time (unkern times)) -(define big-distance (unkern distances)) - -(add1 (- (upper-bound big-time big-distance) (lower-bound big-time big-distance))) diff --git a/aoc2023-other/day-07/day-07.rkt b/aoc2023-other/day-07/day-07.rkt deleted file mode 100644 index 30e629b..0000000 --- a/aoc2023-other/day-07/day-07.rkt +++ /dev/null @@ -1,82 +0,0 @@ -#lang racket - -(require advent-of-code - threading - memo) - -(struct hand (cards wager)) - -(define/match (card->int card) - [((? char-numeric?)) (~> card string string->number)] - [(#\A) 14] - [(#\K) 13] - [(#\Q) 12] - [(#\J) 11] - [(#\T) 10] - [(#\*) 1]) - -(define (parse-hand str #:jokers [jokers? #f]) - (match-define (list card-str wager-str) (string-split str)) - (define cards - (~> card-str - ((λ (str) (if jokers? (string-replace str "J" "*") str))) - string->list - (map card->int _))) - (define wager (~> wager-str string->number)) - (hand cards wager)) - -(define input (~> (open-aoc-input (find-session) 2023 7 #:cache #true) port->lines)) - -(define/memoize (identify-hand h) - (define freqs (~> h hand-cards (sort <) (group-by identity _) (map length _))) - (match freqs - [(list-no-order 5) 8] - [(list-no-order 1 4) 7] - [(list-no-order 2 3) 6] - [(list-no-order 1 1 3) 5] - [(list-no-order 1 2 2) 4] - [(list-no-order 1 1 1 2) 3] - [(list-no-order 1 1 1 1 1) 2] - [_ 1])) - -(define (compare-first-card cs1 cs2) - (if (= (first cs1) (first cs2)) - (compare-first-card (rest cs1) (rest cs2)) - (< (first cs1) (first cs2)))) - -(define (compare-hands with h1 h2) - (define rank1 (with h1)) - (define rank2 (with h2)) - (if (= rank1 rank2) (compare-first-card (hand-cards h1) (hand-cards h2)) (< rank1 rank2))) - -;; part 1 - -(define (compare-hands-no-wilds h1 h2) - (compare-hands identify-hand h1 h2)) - -(define (total-score in #:jokers [jokers? #false]) - (define sorted-hands - (~> in - (map (curry parse-hand #:jokers jokers?) _) - (sort (if jokers? compare-hands-no-wilds compare-hands-with-wilds)))) - (for/sum ([(h i) - (in-indexed sorted-hands)]) - (* (add1 i) (hand-wager h)))) - -(total-score input) - -;; part 2 - -(define/memoize (find-best-joker-substitution h) - (for/fold ([best-hand (hand '() 0)]) - ([wild (in-inclusive-range 2 14)]) - (define trial-hand - (hand (map (λ (c) (if (= c 1) wild c)) (hand-cards h)) (hand-wager h))) - (if (> (identify-hand trial-hand) (identify-hand best-hand)) - trial-hand - best-hand))) - -(define (compare-hands-with-wilds h1 h2) - (compare-hands (λ~> find-best-joker-substitution identify-hand) h1 h2)) - -(total-score input #:jokers #true) diff --git a/aoc2023-other/day-08/day-08.rkt b/aoc2023-other/day-08/day-08.rkt deleted file mode 100644 index 06daafa..0000000 --- a/aoc2023-other/day-08/day-08.rkt +++ /dev/null @@ -1,36 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(struct exits (left right) #:transparent) - -(match-define (list raw-directions raw-maze) - (~> (fetch-aoc-input (find-session) 2023 8 #:cache #true) (string-split "\n\n"))) - -(define directions (string->list raw-directions)) - -(define maze - (for/hash ([line (in-list (string-split raw-maze "\n"))]) - (match (regexp-match #rx"(...) = \\((...), (...)\\)" line) - [(list _ name left right) (values name (exits left right))]))) - -(define (to-next-node start end dirs maze) - (for/fold ([current start] - [acc 0] - #:result acc) - ([dir (in-cycle dirs)]) - #:break (string-suffix? current end) - (define node (hash-ref maze current)) - (case dir - [(#\L) (values (exits-left node) (add1 acc))] - [(#\R) (values (exits-right node) (add1 acc))]))) - -;; part 1 -(to-next-node "AAA" "ZZZ" directions maze) - -;; part 2 -(for/lists (ns #:result (apply lcm ns)) - ([start (in-list (hash-keys maze))] - #:when (string-suffix? start "A")) - (to-next-node start "Z" directions maze)) diff --git a/aoc2023-other/day-09/day-09-polynomial.rkt b/aoc2023-other/day-09/day-09-polynomial.rkt deleted file mode 100644 index 5bacb1f..0000000 --- a/aoc2023-other/day-09/day-09-polynomial.rkt +++ /dev/null @@ -1,17 +0,0 @@ -#lang racket - -(require advent-of-code - threading - simple-polynomial/tools) - -(define histories - (for/list ([raw-history (in-lines (open-aoc-input (find-session) 2023 9 #:cache #true))]) - (~>> raw-history - string-split - (map string->number)))) - -(for/lists (left right #:result (cons (apply + left) (apply + right))) - ([history (in-list histories)]) - (define f (interpolate-at-integer-points history)) - (values (f -1) - (f (length history)))) diff --git a/aoc2023-other/day-09/day-09.rkt b/aoc2023-other/day-09/day-09.rkt deleted file mode 100644 index 5eda1eb..0000000 --- a/aoc2023-other/day-09/day-09.rkt +++ /dev/null @@ -1,32 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(define histories - (for/list ([raw-history (in-lines (open-aoc-input (find-session) 2023 9 #:cache #true))]) - (~>> raw-history - string-split - (map string->number)))) - -(define (constant? xs) - (= 1 (length (remove-duplicates xs)))) - -(define/match (derivative xs) - [((list a b)) (list (- b a))] - [((list* a b _)) (cons (- b a) (derivative (rest xs)))]) - -(define (extrapolate xs) - (if (constant? xs) - (car xs) - (+ (last xs) (extrapolate (derivative xs))))) - -;; part 1 -(~>> histories - (map extrapolate) - (apply +)) - -;; part 2 -(~>> histories - (map (λ~> reverse extrapolate)) - (apply +)) diff --git a/aoc2023-other/day-10/day-10.rkt b/aoc2023-other/day-10/day-10.rkt deleted file mode 100644 index 64d8727..0000000 --- a/aoc2023-other/day-10/day-10.rkt +++ /dev/null @@ -1,97 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(struct posn (r c) #:transparent) - -(define/match (add-posns _p1 _p2) - [((posn x1 y1) (posn x2 y2)) (posn (+ x1 x2) (+ y1 y2))]) - -(define go-north (posn -1 0)) -(define go-south (posn 1 0)) -(define go-east (posn 0 1)) -(define go-west (posn 0 -1)) - -(define initial-directions - (list (cons go-north '(#\| #\7 #\F)) - (cons go-south '(#\| #\J #\L)) - (cons go-east '(#\- #\J #\7)) - (cons go-west '(#\- #\F #\L)))) - -(define/match (pipe-neighbors _pipe) - [(#\|) (list go-north go-south)] - [(#\-) (list go-east go-west)] - [(#\L) (list go-north go-east)] - [(#\F) (list go-south go-east)] - [(#\7) (list go-south go-west)] - [(#\J) (list go-north go-west)]) - -(define (make-pipe-grid in) - (for*/hash ([(row r) (in-indexed (string-split in "\n"))] - [(ch c) (in-indexed (string->list row))]) - (values (posn (add1 r) (add1 c)) ch))) - -(define (get-valid-S-neighbors S grid) - (for/list ([dir (in-list initial-directions)] - #:do [(match-define (cons d valid) dir)] - #:do [(define neighbor (add-posns d S))] - #:when (member (hash-ref grid neighbor 'none) valid)) - neighbor)) - -(define (to-next-pipe current previous grid [acc '()]) - (cond - [(equal? (hash-ref grid current #f) #\S) acc] - [else - (define next - (for/first ([d (in-list (pipe-neighbors (hash-ref grid current)))] - #:do [(define neighbor (add-posns d current))] - #:unless (equal? neighbor previous)) - neighbor)) - (~> next (to-next-pipe _ current grid (cons current acc)))])) - -;; part 1 -(define input (fetch-aoc-input (find-session) 2023 10 #:cache #true)) - -(define pipe-grid (make-pipe-grid input)) - -(define S-posn - (for/first ([(k v) (in-hash pipe-grid)] #:when (equal? v #\S)) - k)) - -(define S-neighbors (get-valid-S-neighbors S-posn pipe-grid)) - -(define pipe-loop (to-next-pipe (first S-neighbors) S-posn pipe-grid '())) - -(/ (add1 (length pipe-loop)) 2) - -;; part 2 -(define pipe-loop-set (~> (list->set pipe-loop) (set-add S-posn))) - -(define (trace-rays pt pipes grid) - (cond - [(set-member? pipes pt) #f] - [else (odd? (trace-ray pt pipes grid))])) - -(define (trace-ray pt pipes grid) - (define row (posn-r pt)) - (for/fold ([acc 0] - [corner #f] - #:result acc) - ([col (in-naturals (posn-c pt))] - #:do [(define test-pt (posn row col))] - #:break (not (hash-has-key? grid test-pt)) - #:when (set-member? pipes test-pt)) - (define pipe (hash-ref grid test-pt)) - (match* (corner pipe) - [(#f #\|) (values (add1 acc) #f)] ; vertical crossing - [(#f (or #\F #\L)) (values acc pipe)] - [(#\F #\J) (values (add1 acc) #f)] ; a ┏━┛ shape counts as a vertical crossing - [(#\L #\7) (values (add1 acc) #f)] - [(#\F #\7) (values acc #f)] ; a ┏━┓ shape doesn't count - [(#\L #\J) (values acc #f)] - [(_ _) (values acc corner)]))) - -(~> pipe-grid - hash-keys - (count (λ~> (trace-rays pipe-loop-set pipe-grid)) _)) diff --git a/aoc2023-other/day-11/day-11.rkt b/aoc2023-other/day-11/day-11.rkt deleted file mode 100644 index dba617b..0000000 --- a/aoc2023-other/day-11/day-11.rkt +++ /dev/null @@ -1,40 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(struct posn (x y) #:transparent) - -(define input - (~> (fetch-aoc-input (find-session) 2023 11 #:cache #true) - (string-split "\n") - (map string->list _))) - -(define (get-empty-ranks grid) - (for/list ([(rank n) (in-indexed grid)] #:when (equal? '(#\.) (remove-duplicates rank))) - n)) - -(define (count-prior-empty-ranks rank empty-ranks) - (~> empty-ranks - (takef (curryr < rank)) - length)) - -(define empty-rows (get-empty-ranks input)) -(define empty-columns (get-empty-ranks (apply map list input))) ;; transpose - -(define (sum-of-star-distances in expand-by) - (define stars - (for*/list ([(row x) (in-indexed in)] - [(col y) (in-indexed row)] - #:when (equal? col #\#)) - (posn (+ x (* (sub1 expand-by) (count-prior-empty-ranks x empty-rows))) - (+ y (* (sub1 expand-by) (count-prior-empty-ranks y empty-columns)))))) - (for/sum ([star-pair (in-combinations stars 2)]) - (match-define (list (posn x1 y1) (posn x2 y2)) star-pair) - (+ (abs (- x1 x2)) (abs (- y1 y2))))) - -;; part 1 -(sum-of-star-distances input 2) - -;; part 2 -(sum-of-star-distances input 1000000) diff --git a/aoc2023-other/day-12/day-12.rkt b/aoc2023-other/day-12/day-12.rkt deleted file mode 100644 index 50b14bb..0000000 --- a/aoc2023-other/day-12/day-12.rkt +++ /dev/null @@ -1,65 +0,0 @@ -#lang racket - -(require advent-of-code - threading - memo) - -(struct condition (template spring-set)) - -(define conditions - (for/list ([line (in-lines (open-aoc-input (find-session) 2023 12 #:cache #true))]) - (match (string-split line #px"[ ,]") - [(list* template spring-set) - (condition (string->list template) (map string->number spring-set))]))) - -(define/memoize (do-count template spring-group left-in-group need-gap?) - ;; template: list of spring positions - ;; spring-group: list of remaining contiguous groups of damaged springs - ;; left-in-group: springs remaining in current bad spring group being placed - ;; need-gap?: did we just finish placing a bad spring group - ;; and need at least one undamaged spring before starting the next one? - (match* (template spring-group left-in-group need-gap?) - ;; no springs left to place and no places left to place springs - ;; this is an OK arrangement, count it - [('() '() 0 _) 1] - ;; ambiguous wildcard, try both skipping this spot and starting a damaged spring group here - [((list* #\? t-rest) (list* g g-rest) 0 #f) - (+ (do-count t-rest g-rest (sub1 g) (= g 1)) - (do-count t-rest spring-group 0 #f))] - ;; definitely a place for a good spring (.), move on without consuming any spring groups - [((list* #\? t-rest) '() 0 #f) ; good spring, no more damaged springs to place - (do-count t-rest spring-group 0 #f)] - [((list* #\? t-rest) _ 0 #t) ; good spring right after finishing a group of bad springs - (do-count t-rest spring-group 0 #f)] - [((list* #\. t-rest) _ 0 _) ; known good spring - (do-count t-rest spring-group 0 #f)] - ;; start of bad spring (#) group, use the next spring group and remove 1 from it - [((list* #\# t-rest) (list* g g-rest) 0 #f) (do-count t-rest g-rest (sub1 g) (= g 1))] - ;; continuation of bad spring group, same - [((list* (or #\? #\#) t-rest) g left #f) (do-count t-rest g (sub1 left) (= left 1))] - ;; if nothing above works, this arrangement's invalid - [(_ _ _ _) 0])) - -(define (count-solutions c) - (match-define (condition template spring-set) c) - (do-count template spring-set 0 #f)) - -;; part 1 -(for/sum ([c (in-list conditions)]) - (count-solutions c)) - -;; part 2 -(define expanded-conditions - (for/list ([c (in-list conditions)]) - (condition (~> c - condition-template - (make-list 5 _) - (add-between #\?) - flatten) - (~> c - condition-spring-set - (make-list 5 _) - flatten)))) - -(for/sum ([c* (in-list expanded-conditions)]) - (count-solutions c*)) diff --git a/aoc2023-other/day-13/day-13.rkt b/aoc2023-other/day-13/day-13.rkt deleted file mode 100644 index 47718f8..0000000 --- a/aoc2023-other/day-13/day-13.rkt +++ /dev/null @@ -1,47 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(define input - (~>(fetch-aoc-input (find-session) 2023 13 #:cache #true) - (string-split "\n\n") - (map (λ~> string-split) _))) - -(define (do-symmetric? lefts rights errs) - (cond - [(empty? rights) #f] - [else - (define found-errs - (for/sum ([l (in-string (string-join lefts ""))] - [r (in-string (string-join rights ""))] - #:unless (char=? l r)) - 1)) - (if (= errs found-errs) - (length lefts) - (do-symmetric? (cons (first rights) lefts) - (rest rights) - errs))])) - -(define (symmetric? xss errs) - (do-symmetric? (list (first xss)) (rest xss) errs)) - -(define (transpose strs) - (~> strs - (map string->list _) - (apply map list _) - (map list->string _))) - -(define (find-symmetry-score xss errs) - (cond - [(symmetric? xss errs) => (curry * 100)] - [else (symmetric? (transpose xss) errs)])) - -;; part 1 -(for/sum ([note (in-list input)]) - (find-symmetry-score note 0)) - -;; part 2 -(for/sum ([note (in-list input)]) - (find-symmetry-score note 1)) - diff --git a/aoc2023-other/day-14/day-14.rkt b/aoc2023-other/day-14/day-14.rkt deleted file mode 100644 index d0b7cad..0000000 --- a/aoc2023-other/day-14/day-14.rkt +++ /dev/null @@ -1,49 +0,0 @@ -#lang racket - -(require advent-of-code - threading - "../../jj-aoc.rkt") - -(define input - (~> (fetch-aoc-input (find-session) 2023 14 #:cache #true) - string-split - (map string->list _) - transpose)) - -(define (roll-boulders board) - (for/list ([col (in-list board)]) - (~> col (chunks-by (curry equal? #\#)) (append-map (curryr sort char>?) _)))) - -(define (score board) - (for*/sum ([col (in-list board)] - [(row n) (in-indexed (reverse col))] - #:when (equal? row #\O)) - (add1 n))) - -;; part 1 -(~> input - roll-boulders - score) - -;; part 2 -(define (rotate-board xss) - (~> xss - (map reverse _) - transpose)) - -(define (full-cycle board) - (foldl (λ (_ acc) (~> acc roll-boulders rotate-board)) board (range 4))) - -(define (spin-to-win board) - (define cache (make-hash)) - (define (do-spin board n) - (match (hash-ref cache board 'not-found) - ['not-found - (hash-set! cache board n) - (do-spin (full-cycle board) (sub1 n))] - [seen - (define to-end (modulo n (- seen n))) - (score (foldl (λ (_ acc) (full-cycle acc)) board (range to-end)))])) - (do-spin board 1000000000)) - -(~> input spin-to-win) diff --git a/aoc2023-other/day-15/day-15.rkt b/aoc2023-other/day-15/day-15.rkt deleted file mode 100644 index d049565..0000000 --- a/aoc2023-other/day-15/day-15.rkt +++ /dev/null @@ -1,41 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(define input - (~> (fetch-aoc-input (find-session) 2023 15 #:cache #true) string-trim (string-split ","))) - -(define (hash-algorithm str) - (for/fold ([acc 0]) ([c (in-string str)]) - (~> c char->integer (+ acc) (* 17) (modulo _ 256)))) - -;; part 1 -(for/sum ([code (in-list input)]) (hash-algorithm code)) - -;; part 2 -(define (remove-lens boxes label) - (hash-update boxes - (hash-algorithm label) - (λ (lens-set) (remove label lens-set (λ (rem l) (equal? rem (car l))))) - '())) - -(define (insert-lens boxes label focal) - (define new-lens (cons label focal)) - (hash-update boxes - (hash-algorithm label) - (λ (lens-set) - (if (assoc label lens-set) - (map (λ (pair) (if (equal? (car pair) label) new-lens pair)) lens-set) - (append lens-set (list new-lens)))) - (list new-lens))) - -(define (focusing-power boxes) - (for*/sum ([(box-number lenses) (in-hash boxes)] [(lens order) (in-indexed lenses)]) - (* (add1 box-number) (add1 order) (cdr lens)))) - -(for/fold ([boxes (hash)] #:result (focusing-power boxes)) ([code (in-list input)]) - (match code - [(regexp #rx"(.*)=(.*)" (list _ label (app string->number focal))) - (insert-lens boxes label focal)] - [(regexp #rx"(.*)-" (list _ label)) (remove-lens boxes label)])) diff --git a/aoc2023-other/day-16/day-16.rkt b/aoc2023-other/day-16/day-16.rkt deleted file mode 100644 index 4a70de8..0000000 --- a/aoc2023-other/day-16/day-16.rkt +++ /dev/null @@ -1,70 +0,0 @@ -#lang racket - -(require advent-of-code - threading) - -(struct posn (r c) #:transparent) -(struct light (posn dir) #:transparent) - -(define input (fetch-aoc-input (find-session) 2023 16 #:cache #true)) - -(define grid - (for*/hash ([(row r) (in-indexed (string-split input "\n"))] - [(cell c) (in-indexed (in-string row))]) - (values (posn r c) cell))) - -(define/match (move _l) - [((light (posn r c) 'up)) (light (posn (sub1 r) c) 'up)] - [((light (posn r c) 'right)) (light (posn r (add1 c)) 'right)] - [((light (posn r c) 'left)) (light (posn r (sub1 c)) 'left)] - [((light (posn r c) 'down)) (light (posn (add1 r) c) 'down)]) - -(define/match (transform l _cell) - [(_ #\.) l] - [(_ 'off) '()] - - [((light _ (or 'up 'down)) #\|) l] - [((light _ (or 'left 'right)) #\-) l] - - [((light p 'left) #\/) (light p 'down)] - [((light p 'down) #\/) (light p 'left)] - [((light p 'right) #\/) (light p 'up)] - [((light p 'up) #\/) (light p 'right)] - - [((light p 'left) #\\) (light p 'up)] - [((light p 'up) #\\) (light p 'left)] - [((light p 'right) #\\) (light p 'down)] - [((light p 'down) #\\) (light p 'right)] - - [((light p (or 'left 'right)) #\|) (list (light p 'up) (light p 'down))] - [((light p (or 'up 'down)) #\-) (list (light p 'left) (light p 'right))]) - -;; part 1 -(define (get-energized start) - (for/fold ([energized (set)] - [previously-visited (set)] - [beam-tips (set start)] - #:result (set-count energized)) - ([_ (in-naturals)]) - (define next-positions - (list->set - (flatten (for/list ([b (in-set beam-tips)]) - (~> b move ((λ (b) (transform b (hash-ref grid (light-posn b) 'off))))))))) - (define all-visited (set-union previously-visited next-positions)) - (define next-energized (set-union energized (list->set (set-map next-positions light-posn)))) - #:break (equal? previously-visited all-visited) - (values next-energized all-visited (set-subtract next-positions previously-visited)))) - -(get-energized (light (posn 0 -1) 'right)) - -;; part 2 -(define rows (~> input (string-split "\n") length)) -(define cols (~> input (string-split #rx"\n.*") first string-length)) - -(define all-starting-positions - (append (map (λ (r) (light (posn r -1) 'right)) (range rows)) - (map (λ (r) (light (posn r cols) 'left)) (range rows)) - (map (λ (c) (light (posn -1 c) 'down)) (range cols)) - (map (λ (c) (light (posn rows c) 'up)) (range cols)))) - -(get-energized (argmax get-energized all-starting-positions)) \ No newline at end of file diff --git a/aoc2023-other/day-17/day-17.rkt b/aoc2023-other/day-17/day-17.rkt deleted file mode 100644 index 05709ad..0000000 --- a/aoc2023-other/day-17/day-17.rkt +++ /dev/null @@ -1,86 +0,0 @@ -#lang racket - -(require advent-of-code - threading - data/heap) - -(struct state (p heat-lost previous history)) -(struct posn (r c)) - -(define/match (add _p1 _p2) - [((posn r1 c1) (posn r2 c2)) (posn (+ r1 r2) (+ c1 c2))]) - -(define deltas (list (posn 0 1) (posn 0 -1) (posn 1 0) (posn -1 0))) - -(define input (fetch-aoc-input (find-session) 2023 17 #:cache #true)) - -(define grid - (for*/hash ([(row r) (in-indexed (in-list (string-split input "\n")))] - [(col c) (in-indexed (in-string row))]) - (values (posn r c) (~> col string string->number)))) - -(define goal-posn (~>> grid hash-keys (argmax (λ (p) (+ (posn-r p) (posn-c p)))))) - -(define (make-key s) - (cons (state-p s) (same-dir s))) - -(define (goal? n s) - (and (equal? goal-posn (state-p s)) - (>= (length (same-dir s)) n))) - -(define (same-dir s) - (define history (state-history s)) - (if (empty? history) - '() - (takef history (λ (n) (equal? n (car history)))))) - -(define (find-good-neighbors min-dist max-dist s) - (match-define (state p hl prev hist) s) - - (define (eliminate-bad-neighbors delta) - (define neighbor (add p delta)) - (cond - [(or (equal? neighbor prev) (not (hash-has-key? grid neighbor))) #false] - [else - (define same (same-dir s)) - (define l (length same)) - (cond - [(= max-dist l) (not (equal? delta (car same)))] - [(= l 0) #true] - [(< l min-dist) (equal? delta (car same))] - [else #t])])) - - (define (make-state delta) - (define neighbor (add p delta)) - (define new-loss (+ hl (hash-ref grid neighbor))) - (state neighbor new-loss p (cons delta hist))) - - (~>> deltas (filter eliminate-bad-neighbors) (map make-state))) - -(define (find-path neighbor-fn goal-fn) - (define seen (mutable-set)) - (define queue (make-heap (λ (a b) (<= (state-heat-lost a) (state-heat-lost b))))) - (heap-add! queue (state (posn 0 0) 0 'none '())) - - (let bfs () - (define s (heap-min queue)) - (heap-remove-min! queue) - (define key (make-key s)) - (cond - [(set-member? seen key) (bfs)] - [else - (set-add! seen key) - (define neighbors (neighbor-fn s)) - (define final (findf goal-fn neighbors)) - (if final - (state-heat-lost final) - (begin - (for ([n (in-list neighbors)]) - (heap-add! queue n)) - (bfs)))]))) - -;; part 1 -(find-path (curry find-good-neighbors 0 3) (curry goal? 1)) - -;; part 2 -(find-path (curry find-good-neighbors 4 10) (curry goal? 4)) \ No newline at end of file diff --git a/aoc2023-other/day-18/day-18.rkt b/aoc2023-other/day-18/day-18.rkt deleted file mode 100644 index b589e41..0000000 --- a/aoc2023-other/day-18/day-18.rkt +++ /dev/null @@ -1,48 +0,0 @@ -#lang racket -(require advent-of-code - threading) - -(struct coord (x y)) - -(define input (~> (fetch-aoc-input (find-session) 2023 18 #:cache #true))) - -(define (go-to-next-coord c dir dist) - (match-define (coord x y) c) - (match dir - [(or "R" "0") (coord (+ x dist) y)] - [(or "D" "1") (coord x (- y dist))] - [(or "L" "2") (coord (- x dist) y)] - [(or "U" "3") (coord x (+ y dist))])) - -(define/match (triangle-area _coord1 _coord2) - [((coord x1 y1) (coord x2 y2)) (/ (- (* x1 y2) (* x2 y1)) 2)]) - -(define (find-area-using parser) - (for/fold ([area 0] - [perimeter 0] - [current-coord (coord 0 0)] - #:result (+ 1 (abs area) (/ perimeter 2))) - ([dig (in-list (string-split input "\n"))]) - (define-values (dir dist) (parser dig)) - (define next-coord (go-to-next-coord current-coord dir dist)) - (values (+ area (triangle-area current-coord next-coord)) - (+ perimeter dist) next-coord))) - -;; part 1 -(define (parse-front dig) - (match-define (regexp #rx"(.) (.*) \\((.*)\\)" - (list _ dir (app string->number dist) _hex)) - dig) - (values dir dist)) - -(find-area-using parse-front) - -;; part 2 - -(define (parse-hex dig) - (match-define (regexp #rx".*\\(#(.....)(.)\\)" - (list _ (app (curryr string->number 16) dist) dir)) - dig) - (values dir dist)) - -(find-area-using parse-hex) diff --git a/aoc2023-other/day-19/day-19.rkt b/aoc2023-other/day-19/day-19.rkt deleted file mode 100644 index f7561f6..0000000 --- a/aoc2023-other/day-19/day-19.rkt +++ /dev/null @@ -1,134 +0,0 @@ -#lang racket - -(require advent-of-code - threading - data/applicative - data/monad - megaparsack - megaparsack/text - racket/struct) - -(struct part (x m a s)) -(struct rule (rating comparison threshold action)) -(struct just (action)) -(struct interval (from to)) - -(match-define (list raw-workflows raw-parts) - (~> (fetch-aoc-input (find-session) 2023 19 #:cache #true) - (string-split "\n\n") - (map (curryr string-split "\n") _))) - -(define/match (to-getter _) - [(#\x) part-x] - [(#\m) part-m] - [(#\a) part-a] - [(#\s) part-s]) - -(define/match (to-comp _) - [(#\>) >] - [(#\<) <]) - -(define/match (to-action _) - [('(#\R)) 'reject] - [('(#\A)) 'accept] - [(name) (apply string name)]) - -(define rule/p - (do (or/p - (try/p (do [rating <- (char-in/p "xmas")] - [comparison <- (char-in/p "<>")] - [threshold <- integer/p] - (char/p #\:) - [action <- (many+/p letter/p)] - (pure (rule (to-getter rating) - (to-comp comparison) - threshold - (to-action action))))) - (do [name <- (many+/p letter/p)] (pure (just (to-action name))))))) -(define rules/p - (do [name <- (many+/p letter/p)] - (char/p #\{) - [rules <- (many+/p rule/p #:sep (char/p #\,))] - (char/p #\}) - (pure (cons (list->string name) rules)))) - -(define rating/p (do letter/p (char/p #\=) integer/p)) -(define parts/p - (do (string/p "{") - [ratings <- (many/p rating/p #:sep (char/p #\,) #:min 4 #:max 4)] - (string/p "}") - (pure (apply part ratings)))) - -(define workflows - (~>> raw-workflows - (map (λ~>> (parse-string rules/p) parse-result!)) - make-immutable-hash)) -(define parts (map (λ~>> (parse-string parts/p) parse-result!) raw-parts)) - -;; part 1 - -(define (evaluate-workflow p [workflow-name "in"]) - (define rules (hash-ref workflows workflow-name)) - (match (evaluate-rules p rules) - ['accept (~> p struct->list (apply + _))] - ['reject 0] - [name (evaluate-workflow p name)])) - -(define (evaluate-rules p rules) - (match rules - [(list* (just action) _) action] - [(list* (rule rating comparison threshold action) _) - #:when (comparison (rating p) threshold) - action] - [(list* _ tail) (evaluate-rules p tail)])) - -(for/sum ([p (in-list parts)]) (evaluate-workflow p)) - -;; part 2 - -(define (part-update-range pr rating i) - (match rating - [(== part-x) (struct-copy part pr (x i))] - [(== part-m) (struct-copy part pr (m i))] - [(== part-a) (struct-copy part pr (a i))] - [(== part-s) (struct-copy part pr (s i))])) - -(define (evaluate-workflow-on-range pr [workflow-name "in"]) - (define rules (hash-ref workflows workflow-name)) - (evaluate-rules-on-range pr rules)) - -(define (evaluate-rules-on-range pr rules) - (match rules - [(list* (just 'accept) _) - (~> pr struct->list - (map (λ (i) (add1 (- (interval-to i) (interval-from i)))) _) - (apply * _))] - [(list* (just 'reject) _) 0] - [(list* (just name) _) (evaluate-workflow-on-range pr name)] - [(list* (rule rating (== <) threshold action) tail) - (match-define (interval i-min i-max) (rating pr)) - (split-range pr - rating - (interval i-min (sub1 threshold)) - action - (interval threshold i-max) - tail)] - [(list* (rule rating (== >) threshold action) tail) - (match-define (interval i-min i-max) (rating pr)) - (split-range pr - rating - (interval (add1 threshold) i-max) - action - (interval i-min threshold) - tail)])) - -(define (split-range pr rating keep action pass rules) - (+ (evaluate-rules-on-range (part-update-range pr rating keep) - (list (just action))) - (evaluate-rules-on-range (part-update-range pr rating pass) - rules))) - -(define start-interval (interval 1 4000)) - -(evaluate-workflow-on-range - (part start-interval start-interval start-interval start-interval)) diff --git a/aoc2023-other/day-20/day-20.rkt b/aoc2023-other/day-20/day-20.rkt deleted file mode 100644 index 2e3852d..0000000 --- a/aoc2023-other/day-20/day-20.rkt +++ /dev/null @@ -1,144 +0,0 @@ -#lang racket - -(require advent-of-code - threading - data/applicative - data/monad - megaparsack - megaparsack/text) - -(struct broadcaster ()) -(struct flipflop (state received)) -(struct conjunction (recieved)) -(struct cable (type dests)) -(struct nothing ()) - -(define charlist->symbol (λ~>> (apply string) string->symbol)) - -(define input (fetch-aoc-input (find-session) 2023 20 #:cache true)) - -(define module/p - (do (or/p (do (char/p #\%) - [name <- (many+/p letter/p)] - (pure (cons (charlist->symbol name) (flipflop 'off '())))) - (do (char/p #\&) - [name <- (many+/p letter/p)] - (pure (cons (charlist->symbol name) (conjunction (hash))))) - (do [name <- (many+/p letter/p)] - (pure (cons (charlist->symbol name) (broadcaster))))))) - -(define cable/p - (do [mod <- module/p] - (string/p " -> ") - [names <- (many/p (many+/p letter/p) #:sep (string/p ", "))] - (pure (cable mod (map charlist->symbol names))))) - -(define cables (~> input (string-split "\n") (map (λ~>> (parse-string cable/p) parse-result!) _))) - -(define destinations - (for/hash ([cable (in-list cables)]) - (values (car (cable-type cable)) (cable-dests cable)))) - -(define (set-conjunction-initial-state c) - (cond - [(conjunction? (cdr c)) - (~>> destinations - hash-keys - (filter (λ (k) (member (car c) (hash-ref destinations k)))) - (map (λ (k) (cons k 'low))) - (make-immutable-hash) - conjunction - (cons (car c)))] - [else c])) - -(define (make-initial-conditions-hash cables) - (~>> cables - (map cable-type) - (map set-conjunction-initial-state) - make-immutable-hash)) - -(define (receive mod from tone) - (match mod - [(flipflop state queue) (flipflop state (append queue (list tone)))] - [(conjunction received) (conjunction (hash-set received from tone))] - [(nothing) (nothing)])) - -; needed for part 2 -(define to-rx '(rk cd zf qx)) -(define sentry-tones (make-hash (for/list ([node to-rx]) (cons node 0)))) - -(define (press-button-once current-state this-round) - (for/fold ([queue '(broadcaster)] - [all-cables-state current-state] - [high 0] - [low 0] - #:result (values all-cables-state high low)) - ([_i (in-naturals)] #:break (empty? queue)) - (match-define (list* hd tl) queue) - (define to (hash-ref destinations hd (nothing))) - (match (hash-ref all-cables-state hd) - [(broadcaster) - (define state* - (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) - all-cables-state - to)) - (values (hash-ref destinations 'broadcaster) state* high (+ (length to) (add1 low)))] - [(flipflop 'off (list* 'low q)) - (define state* - (~> all-cables-state - (foldl (λ (r acc) - (when (member r to-rx) - (println (~a r " received high tone at " this-round))) - (hash-update acc r (λ~> (receive hd 'high)) (nothing))) - _ - to) - (hash-set _ hd (flipflop 'on q)))) - (values (append tl to) state* (+ (length to) high) low)] - [(flipflop 'on (list* 'low q)) - (define state* - (~> all-cables-state - (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) _ to) - (hash-set _ hd (flipflop 'off q)))) - (values (append tl to) state* high (+ (length to) low))] - [(flipflop on-or-off (list* 'high q)) - (define state* (~> all-cables-state (hash-set _ hd (flipflop on-or-off q)))) - (values tl state* high low)] - [(conjunction received) - #:when (or (empty? (hash-values received)) (member 'low (hash-values received))) - - (when (member hd to-rx) - (hash-set! sentry-tones hd this-round)) - (define state* - (foldl (λ (r acc) - (hash-update acc r (λ~> (receive hd 'high)) (nothing))) - all-cables-state - to)) - (values (append to tl) state* (+ (length to) high) low)] - [(conjunction _) - (define state* - (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) - all-cables-state - to)) - (values (append tl to) state* high (+ (length to) low))] - [(nothing) (values tl all-cables-state high low)]))) - -;; part 1 -(for/fold ([starting-state (make-initial-conditions-hash cables)] - [high 0] - [low 0] - #:result (* high low)) - ([i (in-range 1000)]) - (define-values (next-state this-high this-low) (press-button-once starting-state i)) - (values next-state (+ high this-high) (+ low this-low))) - -;; part 2 -;; rx receives a tone from gh, which receives four tones itself -;; those tones arrive on regular synced cycles so it's just the LCM of those cycle lengths -;; and since those cycle lengths are prime, it reduces to the product of the lengths -;; this is a really hacky mutable solution, I'm sure there's better ways of flagging these cycles - -(for/fold ([starting-state (make-initial-conditions-hash cables)] - #:result (apply * (hash-values sentry-tones))) - ([i (in-range 1 5000)]) - (define-values (next-state _high _low) (press-button-once starting-state i)) - (values next-state)) \ No newline at end of file diff --git a/aoc2023-other/day-21/day-21.rkt b/aoc2023-other/day-21/day-21.rkt deleted file mode 100644 index b5478eb..0000000 --- a/aoc2023-other/day-21/day-21.rkt +++ /dev/null @@ -1,69 +0,0 @@ -#lang racket - -(require advent-of-code - threading - simple-polynomial - racket/hash) - -(define input (fetch-aoc-input (find-session) 2023 21 #:cache #true)) - -(define initial-garden - (~> (for*/list ([(row r) (in-indexed (string-split input "\n"))] - [(col c) (in-indexed row)] - #:unless (equal? col #\#)) - (cons (cons r c) (if (equal? col #\S) 'on 'off))) - make-hash)) - -(define (neighbors p) - (match-define (cons r c) p) - (list (cons (add1 r) c) (cons (sub1 r) c) (cons r (add1 c)) (cons r (sub1 c)))) - -(define (make-n-steps garden n) - (define g (hash-copy garden)) - (define (make-one-step) - (define update (make-hash)) - (for ([(cons state) (in-hash g)] #:when (equal? state 'on)) - (hash-set! update cons 'off) - (for ([neighbor (in-list (neighbors cons))] #:when (hash-has-key? g neighbor)) - (hash-set! update neighbor 'on))) - (hash-union! g update #:combine/key (λ (_k _v v) v))) - (for/fold ([_ void] - #:result (~>> g hash-values (count (curry equal? 'on)))) - ([i (in-range n)]) - (displayln i) - (make-one-step))) - -;; part 1 -(make-n-steps initial-garden 64) - -;; part 2 -;; the growth of the steps pattern is regular and quadratic -;; the rock pattern has aisles in it that allow the steps pattern to expand freely -;; such that it will cross from one side to another in X steps -;; where X is the height/width of the repeated section - -(define grid-size (~> input (string-split "\n") length)) -(define half-size (/ (sub1 grid-size) 2)) - -(define expanded-garden - (~> (for*/list (#:do [(define rows (string-split input "\n"))] - #:do [(define height (length rows))] - [(row r) (in-indexed rows)] - #:do [(define width (string-length row))] - [(col c) (in-indexed row)] - #:unless (equal? col #\#) - [n (in-inclusive-range -3 3)] - [m (in-inclusive-range -3 3)]) - - (cons (cons (+ r (* n height)) (+ c (* m width))) - (if (and (= n m 0) (equal? col #\S)) 'on 'off))) - make-hash)) - -(define fitting-points - (for/list ([n (in-range 3)] #:do [(define size (+ half-size (* n grid-size)))]) - (cons n (make-n-steps expanded-garden size)))) - -(define end-cycle 26501365) -(define x (/ (- end-cycle half-size) grid-size)) - -((points->polynomial fitting-points) x) \ No newline at end of file diff --git a/aoc2023-other/day-22/day-22.rkt b/aoc2023-other/day-22/day-22.rkt deleted file mode 100644 index 53668c0..0000000 --- a/aoc2023-other/day-22/day-22.rkt +++ /dev/null @@ -1,109 +0,0 @@ -#lang racket - -(require advent-of-code - threading - data/applicative - data/monad - megaparsack - megaparsack/text - racket/hash) - -(struct posn (x y z)) -(struct block (n from to)) - -(define input (fetch-aoc-input (find-session) 2023 22 #:cache #true)) - -(define coordinate/p - (do [coords <- (many/p integer/p #:sep (char/p #\,) #:min 3 #:max 3)] - (pure (apply posn coords)))) - -(define block/p - (do [from <- coordinate/p] - (char/p #\~) - [to <- coordinate/p] - (pure (cons from to)))) - -(define starting-blocks - (~> (for/list ([line (in-list (string-split input "\n"))] - [n (in-naturals)]) - (match-define (cons from to) (parse-result! (parse-string block/p line))) - (block n from to)) - (sort < #:key (λ~> block-from posn-z)))) - -(define (all-in-cross-section-at-level b z) - (match-define (block _ (posn x1 y1 _) (posn x2 y2 _)) b) - (for*/list ([x (in-inclusive-range x1 x2)] - [y (in-inclusive-range y1 y2)]) - (posn x y z))) - -(define (place-block-at-level b h dz) - (match-define (block n (posn x1 y1 z1) (posn x2 y2 z2)) b) - (define now-occupied - (for*/hash ([x (in-inclusive-range x1 x2)] - [y (in-inclusive-range y1 y2)] - [z (in-inclusive-range dz (+ dz (- z2 z1)))]) - (values (posn x y z) n))) - (hash-union h now-occupied)) - -(define (find-lowest-level b h [z (~> b block-from posn-z)]) - (cond - [(= z 0) - (place-block-at-level b h 1)] - [(findf (curry hash-has-key? h) (all-in-cross-section-at-level b z)) - (place-block-at-level b h (add1 z))] - [else - (find-lowest-level b h (sub1 z))])) - -(define blocks-in-space (foldl find-lowest-level (hash) starting-blocks)) -(define block-positions - (for/fold ([placed-blocks (hash)]) - ([(p n) (in-hash blocks-in-space)]) - (hash-update placed-blocks n (curryr set-add p) (set)))) - -(define (down-one p) - (match p - [(posn x y z) (posn x y (sub1 z))])) - -(define supporting-blocks - (for/hash ([(n-id n-posns) (in-hash block-positions)]) - (values n-id - (for*/set ([(m-id m-posns) (in-hash block-positions)] - #:unless (= n-id m-id) - [m-posn (in-set m-posns)] - #:when (set-member? n-posns (down-one m-posn))) - m-id)))) - -(define supported-by-blocks - (for/hash ([n-id (in-hash-keys supporting-blocks)]) - (define supporters - (~> (for*/set - ([(m-id m-supporting) (in-hash supporting-blocks)] - #:unless (= n-id m-id) - #:when (set-member? m-supporting n-id)) - m-id) - ((λ (s) (if (set-empty? s) (set 'ground) s))))) - (values n-id supporters))) - -;; part 1 -(define vulnerable-blocks - (for/list ([n-id (in-range (length starting-blocks))] - #:when (for/or ([m-supported-by (in-hash-values supported-by-blocks)]) - (set-empty? (set-remove m-supported-by n-id)))) - n-id)) -(- (length starting-blocks) (length vulnerable-blocks)) - -;; part 2 -(for/sum ([n (in-list vulnerable-blocks)]) - (for/fold ([fallen (set n)] - [bricks (set n)] - #:result (~> fallen set-count sub1)) - ([_ (in-naturals)]) - #:break (set-empty? bricks) - (define bricks-above - (for*/set - ([brick (in-set bricks)] - [supporting (in-set (hash-ref supporting-blocks brick))] - #:when (for/and ([supports (in-set (hash-ref supported-by-blocks supporting))]) - (set-member? fallen supports))) - supporting)) - (values (set-union fallen bricks-above) bricks-above))) \ No newline at end of file diff --git a/aoc2023-other/day-23/day-23.rkt b/aoc2023-other/day-23/day-23.rkt deleted file mode 100644 index c048013..0000000 --- a/aoc2023-other/day-23/day-23.rkt +++ /dev/null @@ -1,89 +0,0 @@ -#lang racket - -(require advent-of-code - threading - graph) - -(define input (fetch-aoc-input (find-session) 2023 23 #:cache #true)) -(define trails - (for*/hash ([(row r) (in-indexed (string-split input "\n"))] - [(col c) (in-indexed row)] - #:when (not (equal? col #\#))) - ; for now, we don't actually need to detect paths and slopes, just not-rocks - ; in part 1, all forks in the road go right or down, so we can just infer the path - ; direction from the shape of the junction and form a network of junctions from there - ; in part 2, the slopes are removed anyway - (values (cons (add1 r) (add1 c)) col))) - -(define max-row (~> input (string-split "\n") length)) - -(define start (findf (λ (p) (= (car p) 1)) (hash-keys trails))) -(define end (findf (λ (p) (= (car p) max-row)) (hash-keys trails))) - -(define (get-neighbors posn type) - (match-define (cons r c) posn) - (match type - ['junction - (~> (set (cons (add1 r) c) (cons r (add1 c))))] - [_ - (~> (list (cons (add1 r) c) (cons (sub1 r) c) (cons r (add1 c)) (cons r (sub1 c))) - (filter (curry hash-has-key? trails) _) - list->set)])) - -(define junction-points - (for/set ([(k v) (in-hash trails)] - #:when (not (= (set-count (get-neighbors k v)) 2))) - k)) - -(define trails-with-junctions - (for/hash ([k (in-hash-keys trails)]) - (cond - [(set-member? junction-points k) (values k 'junction)] - [else (values k 'trail)]))) - -(define (walk-to-next-junction start current [length 1] [seen (set start)]) - (define next (~> current - (get-neighbors _ 'trail) - (set-subtract seen) set-first)) - (cond - [(equal? (hash-ref trails-with-junctions next) 'junction) - (list (- (add1 length)) start next)] ; weird format is due to graph library - [else - (walk-to-next-junction start next (add1 length) (set-add seen current))])) - -(define routes-to-junctions - (for*/list ([j (in-set junction-points)] - [neighbor (in-set (get-neighbors j 'junction))] - #:when (hash-has-key? trails neighbor)) - (walk-to-next-junction j neighbor))) - -;; part 1 -- using graph library for Bellman-Ford on negative weighted graph -;; Bellman-Ford finds the shortest path, but negating all the path weights -;; will give us the longest path instead -;; -;; unlike Dijkstra which can't handle negative path lengths, Bellman-Ford -;; works as long as the graph is currently directed and acyclic -(define slippery-trail (weighted-graph/directed routes-to-junctions)) -(match-define-values (distances _) (bellman-ford slippery-trail start)) -(- (hash-ref distances end)) - -;; part 2 -- rolling my own DFS that can reject seen junctions and dead ends -(define routes-to-junctions-with-traction - (for/fold ([trails (hash)]) ([route (in-list routes-to-junctions)]) - (match-define (list (app - weight) from to) route) - (~> trails - (hash-update _ from (curry cons (cons to weight)) '()) - (hash-update _ to (curry cons (cons from weight)) '())))) - -(define (dfs g from to [acc 0] [seen (set from)]) - (cond - [(equal? from to) acc] - [else - (define choices (filter (λ (path) (not (set-member? seen (car path)))) (hash-ref g from))) - (if (empty? choices) 0 ; long dead-ends don't count - (for/fold ([best acc]) - ([path (in-list choices)] - #:do [(match-define (cons next dist) path)]) - (max best (dfs g next to (+ acc dist) (set-add seen next)))))])) - -(dfs routes-to-junctions-with-traction start end) \ No newline at end of file diff --git a/aoc2023-other/day-24/day-24a.rkt b/aoc2023-other/day-24/day-24a.rkt deleted file mode 100644 index 31f526d..0000000 --- a/aoc2023-other/day-24/day-24a.rkt +++ /dev/null @@ -1,51 +0,0 @@ -#lang rosette - -(require advent-of-code - threading) - -(struct hail (posn vel) #:transparent) -(struct posn (x y z) #:transparent) -(struct vel (x y z) #:transparent) - -(define input (fetch-aoc-input (find-session) 2023 24 #:cache #true)) - -(define LOWER-BOUND 200000000000000) -(define UPPER-BOUND 400000000000000) - -(define (->struct f str) - (~> str (string-split _ ",") (map (λ~> string-trim string->number) _) (apply f _))) - -(define (parse-hail-record str) - (match-define (list p v) (string-split str " @ ")) - (hail (->struct posn p) - (->struct vel v))) - -(define hail-paths - (for/list ([hail (in-list (string-split input "\n"))]) - (parse-hail-record hail))) - -;; part 1 -(define (valid-intersection? h1 h2) - (match-define (hail (posn x1 y1 _) (vel vx1 vy1 _)) h1) - (match-define (hail (posn x2 y2 _) (vel vx2 vy2 _)) h2) - (cond - [(= (* vy1 vx2) (* vx1 vy2)) #f] - [else - (define t1 (/ (- (* vy2 (- x1 x2)) (* vx2 (- y1 y2))) - (- (* vy1 vx2) (* vx1 vy2)))) - (define t2 (/ (- (* vy1 (- x2 x1)) (* vx1 (- y2 y1))) - (- (* vy2 vx1) (* vx2 vy1)))) - - (define x (+ x1 (* t1 vx1))) - (define y (+ y1 (* t1 vy1))) - - (and (<= LOWER-BOUND x UPPER-BOUND) - (<= LOWER-BOUND y UPPER-BOUND) - (<= 0 t1) - (<= 0 t2))])) - -(for/sum ([(trial-paths) (in-combinations hail-paths 2)] ; - #:when (apply valid-intersection? trial-paths)) - 1) - -;; part 2 - see day-24b.rkt diff --git a/aoc2023-other/day-24/day-24b.rkt b/aoc2023-other/day-24/day-24b.rkt deleted file mode 100644 index b106b30..0000000 --- a/aoc2023-other/day-24/day-24b.rkt +++ /dev/null @@ -1,37 +0,0 @@ -#lang rosette - -(require advent-of-code - threading) - -(struct hail (posn vel)) -(struct posn (x y z)) -(struct vel (x y z)) - -(define input (fetch-aoc-input (find-session) 2023 24 #:cache #true)) - -(define (->struct f str) - (~> str (string-split _ ",") (map (λ~> string-trim string->number) _) (apply f _))) - -(define (parse-hail-record str) - (match-define (list p v) (string-split str " @ ")) - (hail (->struct posn p) (->struct vel v))) - -(define hail-paths - (for/list ([hail (in-list (string-split input "\n"))] ; - [_ (in-range 3)]) - (parse-hail-record hail))) - -;; part 1 - see day-24a.rkt -;; part 2 - -(define-symbolic px py pz vx vy vz integer?) - -(define sol - (solve ; - (for ([path (in-list hail-paths)]) - (define-symbolic* t integer?) - (assert (= (+ px (* vx t)) (+ (~> path hail-posn posn-x) (* (~> path hail-vel vel-x) t)))) - (assert (= (+ py (* vy t)) (+ (~> path hail-posn posn-y) (* (~> path hail-vel vel-y) t)))) - (assert (= (+ pz (* vz t)) (+ (~> path hail-posn posn-z) (* (~> path hail-vel vel-z) t))))))) - -(evaluate (+ px py pz) sol) diff --git a/aoc2023-other/day-25/day-25.rkt b/aoc2023-other/day-25/day-25.rkt deleted file mode 100644 index aa32e43..0000000 --- a/aoc2023-other/day-25/day-25.rkt +++ /dev/null @@ -1,43 +0,0 @@ -#lang racket - -(require advent-of-code - threading - graph) - -(define input - (~> (fetch-aoc-input (find-session) 2023 25 #:cache #true) - (string-split "\n") - (map (curryr string-split ": ") _))) - -(define all-wires - (for*/list ([wire-diagram (in-list input)] [devices (in-list (string-split (second wire-diagram)))]) - (list (car wire-diagram) devices))) - -;; instead of trying to solve the minimum cut problem, I generated the graph and -;; rendered it in graphviz: - -; (define out (open-output-file "graphviz")) -; (~> all-wires -; unweighted-graph/undirected -; graphviz -; (display out)) -; (close-output-port out) - -;; the bottleneck is very obvious on the graph -- -;; there's two large clusters of nodes, connected by just three edges -;; -;; from the graphviz output, the three critical wires are -;; cpq-hlx -;; hqp-spk -;; chr-zlx - -(define remove-these-three '(("cpq" "hlx") ("hqp" "spk") ("chr" "zlx"))) -(define cut-wires - (for/list ([wire (in-list all-wires)] #:unless (member (sort wire string cut-wires - unweighted-graph/undirected - scc - (map length _) - (apply * _)) \ No newline at end of file diff --git a/aoc2023-racket/day-01/day-01.rkt b/aoc2023-racket/day-01/day-01.rkt new file mode 100644 index 0000000..b720f79 --- /dev/null +++ b/aoc2023-racket/day-01/day-01.rkt @@ -0,0 +1,38 @@ +#lang racket + +(require advent-of-code + threading) + +(define calibration-values (fetch-aoc-input (find-session) 2023 1)) + +(define (to-number str) + (match (string->number str) + [#false (hash-ref word-to-digit str)] + [n n])) + +(define (parse-calibration-value v valid) + (for/fold ([acc '()] [value v] #:result (+ (to-number (first acc)) (* 10 (to-number (last acc))))) + ([_ (in-naturals)]) + #:break (equal? value "") + (let ([possible-prefix (findf (curry string-prefix? value) valid)]) + (if possible-prefix + (values (cons possible-prefix acc) (substring value 1)) + (values acc (substring value 1)))))) + +(define (total-calibration input valid) + (~> input + (string-trim) + (string-split "\n") + (map (λ~> (parse-calibration-value valid)) _) + (apply + _))) + +;; part 1 + +(define valid-for-part-1 (~> (range 1 10) (map ~a _))) +(total-calibration calibration-values valid-for-part-1) + +;; part 2 +(define word-to-digit + (hash "one" 1 "two" 2 "three" 3 "four" 4 "five" 5 "six" 6 "seven" 7 "eight" 8 "nine" 9)) +(define valid-for-part-2 (append valid-for-part-1 (hash-keys word-to-digit))) +(total-calibration calibration-values valid-for-part-2) diff --git a/aoc2023-racket/day-02/day-02-parser.rkt b/aoc2023-racket/day-02/day-02-parser.rkt new file mode 100644 index 0000000..76cc24f --- /dev/null +++ b/aoc2023-racket/day-02/day-02-parser.rkt @@ -0,0 +1,55 @@ +#lang racket + +(require racket/hash + advent-of-code + data/applicative + data/either + data/monad + megaparsack + megaparsack/text + threading) + +(struct game (id r g b)) + +(define cube/p + (do [n <- integer/p] + space/p + [c <- (or/p (string/p "red") + (string/p "blue") + (string/p "green"))] + (pure (cons c n)))) + +(define draw/p + (do [xs <- (many/p cube/p #:min 1 #:max 3 #:sep (string/p ", "))] + (pure (apply hash (flatten xs))))) + +(define all-draws/p + (do (string/p "Game ") + [id <- integer/p] + (string/p ": ") + [all-draws <- (many/p draw/p #:min 1 #:sep (string/p "; "))] + (define maxima + (foldl (curry hash-union #:combine max) + (hash "red" 0 "green" 0 "blue" 0) + all-draws)) + (pure (game id + (hash-ref maxima "red") + (hash-ref maxima "green") + (hash-ref maxima "blue"))))) + +(define game-maxima + (~>> (open-aoc-input (find-session) 2023 2) + port->lines + (map (λ~>> (parse-string all-draws/p) + from-either)))) + +;; part 1 +(for/sum ([m (in-list game-maxima)] + #:unless (or (> (game-r m) 12) + (> (game-g m) 13) + (> (game-b m) 14))) + (game-id m)) + +;; part 2 +(for/sum ([m (in-list game-maxima)]) + (* (game-r m) (game-g m) (game-b m))) diff --git a/aoc2023-racket/day-02/day-02.rkt b/aoc2023-racket/day-02/day-02.rkt new file mode 100644 index 0000000..973d20c --- /dev/null +++ b/aoc2023-racket/day-02/day-02.rkt @@ -0,0 +1,35 @@ +#lang racket + +(require advent-of-code) + +(struct roll (id red green blue)) + +(define all-games + (for/list ([raw-game (in-list (port->lines (open-aoc-input (find-session) 2023 2)))] + #:do [(define game (string-trim raw-game "Game ")) + (match-define (list id trials) (string-split game ": "))]) + (for/list ([trial (in-list (string-split trials "; "))]) + (for/fold ([acc (roll (string->number id) 0 0 0)]) + ([color (in-list (string-split trial ", "))]) + (match (string-split color) + [(list (app string->number n) "red") (struct-copy roll acc [red n])] + [(list (app string->number n) "green") (struct-copy roll acc [green n])] + [(list (app string->number n) "blue") (struct-copy roll acc [blue n])]))))) + +;; part 1 +(for/sum ([game (in-list all-games)] + #:when (andmap (λ (g) (and ((roll-red g) . <= . 12) + ((roll-green g) . <= . 13) + ((roll-blue g) . <= . 14))) + game)) + (roll-id (first game))) + +;; part 2 +(for/sum ([game (in-list all-games)]) + (define max-cubes + (for/fold ([acc (roll #f 0 0 0)]) ([r (in-list game)]) + (roll #f + (max (roll-red acc) (roll-red r)) + (max (roll-green acc) (roll-green r)) + (max (roll-blue acc) (roll-blue r))))) + (* (roll-red max-cubes) (roll-green max-cubes) (roll-blue max-cubes))) diff --git a/aoc2023-racket/day-03/day-03.rkt b/aoc2023-racket/day-03/day-03.rkt new file mode 100644 index 0000000..60e81a6 --- /dev/null +++ b/aoc2023-racket/day-03/day-03.rkt @@ -0,0 +1,72 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (x y) #:transparent) +(struct part (n posns) #:transparent) + +(define (make-board port) + (for*/hash ([(row y) (in-indexed (port->lines port))] + [(col x) (in-indexed (string->list row))] + #:unless (equal? col #\.)) + (define v + (cond + [(string->number (string col))] + [(equal? col #\*) 'gear] + [else 'other])) + (values (posn x y) v))) + +(define board (~> (open-aoc-input (find-session) 2023 3 #:cache #true) make-board)) + +(define (posn (for/hash ([(k v) (in-hash b)] #:when (f v)) + (values k v)) + hash->list + (sort posn> part-posns (append-map neighbors) remove-duplicates)) +(define (symbol-in-neighbors b pt acc) + (~>> pt + to-neighbors + (ormap (λ (p) (let ([lookup (hash-ref b p #f)]) + (or (equal? lookup 'gear) (equal? lookup 'other))))) + ((λ (bool) (if bool (+ acc (part-n pt)) acc))))) + +;; part 1 +(define parts (~>> board (find-cells integer?) group-into-parts)) +(foldl (curry symbol-in-neighbors board) 0 parts) + +;; part 2 +(define gears (~>> board (find-cells (curry equal? 'gear)) (map car))) +(define parts-with-neighbors (map (λ (pt) (struct-copy part pt [posns (to-neighbors pt)])) parts)) + +(define (find-parts-near-gear pts gear) + (filter-map (λ (pt) (and (findf (curry equal? gear) (part-posns pt)) (part-n pt))) pts)) + +(~>> gears + (filter-map (λ~>> (find-parts-near-gear parts-with-neighbors) + ((λ (ns) (if (= (length ns) 2) (* (first ns) (second ns)) #f))))) + (apply +)) diff --git a/aoc2023-racket/day-04/day-04.rkt b/aoc2023-racket/day-04/day-04.rkt new file mode 100644 index 0000000..7a357c5 --- /dev/null +++ b/aoc2023-racket/day-04/day-04.rkt @@ -0,0 +1,40 @@ +#lang racket + +(require advent-of-code + data/applicative + data/either + data/monad + megaparsack + megaparsack/text + threading) + +(struct card (n wins)) + +(define card/p + (do (string/p "Card") + (many/p space/p) + [n <- integer/p] + (string/p ":") + (many/p space/p) + [winners <- (many-until/p integer/p #:sep (many/p space/p) #:end (try/p (string/p " | ")))] + (many/p space/p) + [has <- (many+/p integer/p #:sep (many/p space/p))] + (pure (card n (set-count (set-intersect (first winners) has)))))) + +(define raw-cards (~> (open-aoc-input (find-session) 2023 4 #:cache #true) port->lines)) + +;; part 1 +(for/sum ([raw-card (in-list raw-cards)] + #:do [(match-define (success (card _ wins)) (parse-string card/p raw-card))] + #:unless (= wins 0)) + (expt 2 (sub1 wins))) + +;; part 2 +(for/fold ([counts (for/hash ([n (in-inclusive-range 1 (length raw-cards))]) + (values n 1))] + #:result (apply + (hash-values counts))) + ([raw-card (in-list raw-cards)] + #:do [(match-define (success (card n wins)) (parse-string card/p raw-card))]) + (define bonus-range (inclusive-range (+ n 1) (+ n wins))) + (define won-cards (hash-ref counts n)) + (foldl (λ (n acc) (hash-update acc n (curry + won-cards))) counts bonus-range)) diff --git a/aoc2023-racket/day-05/day-05.rkt b/aoc2023-racket/day-05/day-05.rkt new file mode 100644 index 0000000..5b9aa52 --- /dev/null +++ b/aoc2023-racket/day-05/day-05.rkt @@ -0,0 +1,91 @@ +#lang racket + +(require advent-of-code + algorithms + threading) + +(struct map-range (start end offset)) +(struct seed-range (start end)) + +(define input (fetch-aoc-input (find-session) 2023 5 #:cache #true)) + +(match-define (list* raw-seeds raw-mappings) (string-split input "\n\n")) + +(define seeds-naive (~> raw-seeds (string-split " ") rest (map string->number _))) +(define mappers + (for/list ([raw-mapping (in-list raw-mappings)]) + (for/lists (map-ranges #:result (sort map-ranges < #:key map-range-start)) + ([raw-map-range (in-list (rest (string-split raw-mapping "\n")))] + #:do [(match-define (list dest source width) + (~> raw-map-range (string-split _ " ") (map string->number _)))]) + (map-range source (+ source width -1) (- dest source))))) + +;; part 1 +(define (in-map-range? n mr) + (<= (map-range-start mr) n (map-range-end mr))) + +(define (transform-value mapper n) + (match mapper + ['() n] + [(list* mr _) + #:when (in-map-range? n mr) + (+ n (map-range-offset mr))] + [(list* _ rest-mapper) (transform-value rest-mapper n)])) + +(for/lists (transforms #:result (apply min transforms)) + ([seed (in-list seeds-naive)]) + (foldl transform-value seed mappers)) + +;; part 2 +(define (remap-range r mapper [acc '()]) + (match-define (seed-range r-start r-end) r) + (match mapper + ; mapper exhausted + ['() (cons r acc)] + ; range to the left - not covered by this mapping, so keep as-is + [(list* (map-range m-start _ _) _) + #:when (< r-end m-start) + (cons r acc)] + ; range to the right - move to next map-range + [(list* (map-range _ m-end _) ms) + #:when (< m-end r-start) + (remap-range r ms acc)] + ; range is inside map-range - transform whole range + [(list* (map-range m-start m-end offset) _) + #:when (and (<= m-start r-start) (<= r-end m-end)) + (cons (seed-range (+ r-start offset) (+ r-end offset)) acc)] + ; range overlaps start only - keep left side, transform right side + [(list* (map-range m-start m-end offset) ms) + #:when (and (< r-start m-start) (<= r-end m-end)) + (remap-range (seed-range (add1 m-end) r-end) + ms + (cons (seed-range (+ m-start offset) (+ r-end offset)) acc))] + ; range overlaps end - transform left side, pass right side + [(list* (map-range m-start m-end offset) ms) + #:when (and (< m-start r-start) (<= m-end r-end)) + (remap-range (seed-range (add1 m-end) r-end) + ms + (cons (seed-range (+ r-start offset) (+ m-end offset)) acc))] + ; range overlaps whole map-range - keep left side, transform middle, pass right side + [(list* (map-range m-start m-end offset) ms) + (remap-range (seed-range (add1 m-end) r-end) + ms + (cons (seed-range (+ m-start offset) (+ m-end offset)) + (cons (seed-range (add1 m-end) r-end) acc)))])) + +(define (remap-ranges rs mappers) + (cond + [(empty? mappers) rs] + [else + (~>> rs (append-map (curryr remap-range (first mappers))) (remap-ranges _ (rest mappers)))])) + +(~> seeds-naive + (chunks-of 2) + (map (λ (xs) + (match-define (list start width) xs) + (~> (list (seed-range start (+ start width -1))) + (remap-ranges mappers) + (argmin seed-range-start _))) + _) + (argmin seed-range-start _) + seed-range-start) diff --git a/aoc2023-racket/day-06/day-06.rkt b/aoc2023-racket/day-06/day-06.rkt new file mode 100644 index 0000000..53ca9ee --- /dev/null +++ b/aoc2023-racket/day-06/day-06.rkt @@ -0,0 +1,32 @@ +#lang racket + +(require advent-of-code + threading) + +(match-define (list times distances) + (~> (open-aoc-input (find-session) 2023 6 #:cache #true) port->lines)) + +;; part 1 +(define get-numbers (λ~> string-split (map string->number _) rest)) +(define (find-bound race-time dist button-time step) + (if (< dist (* button-time (- race-time button-time))) + button-time + (find-bound race-time dist (+ step button-time) step))) + +(define (lower-bound rtime dist) + (find-bound rtime dist 1 1)) +(define (upper-bound rtime dist) + (find-bound rtime dist rtime -1)) + +(for/fold ([acc 1]) + ([race-time (in-list (get-numbers times))] + [distance (in-list (get-numbers distances))]) + (* acc (add1 (- (upper-bound race-time distance) (lower-bound race-time distance))))) + +;; part 2 + +(define unkern (λ~>> get-numbers (apply ~a) string->number)) +(define big-time (unkern times)) +(define big-distance (unkern distances)) + +(add1 (- (upper-bound big-time big-distance) (lower-bound big-time big-distance))) diff --git a/aoc2023-racket/day-07/day-07.rkt b/aoc2023-racket/day-07/day-07.rkt new file mode 100644 index 0000000..30e629b --- /dev/null +++ b/aoc2023-racket/day-07/day-07.rkt @@ -0,0 +1,82 @@ +#lang racket + +(require advent-of-code + threading + memo) + +(struct hand (cards wager)) + +(define/match (card->int card) + [((? char-numeric?)) (~> card string string->number)] + [(#\A) 14] + [(#\K) 13] + [(#\Q) 12] + [(#\J) 11] + [(#\T) 10] + [(#\*) 1]) + +(define (parse-hand str #:jokers [jokers? #f]) + (match-define (list card-str wager-str) (string-split str)) + (define cards + (~> card-str + ((λ (str) (if jokers? (string-replace str "J" "*") str))) + string->list + (map card->int _))) + (define wager (~> wager-str string->number)) + (hand cards wager)) + +(define input (~> (open-aoc-input (find-session) 2023 7 #:cache #true) port->lines)) + +(define/memoize (identify-hand h) + (define freqs (~> h hand-cards (sort <) (group-by identity _) (map length _))) + (match freqs + [(list-no-order 5) 8] + [(list-no-order 1 4) 7] + [(list-no-order 2 3) 6] + [(list-no-order 1 1 3) 5] + [(list-no-order 1 2 2) 4] + [(list-no-order 1 1 1 2) 3] + [(list-no-order 1 1 1 1 1) 2] + [_ 1])) + +(define (compare-first-card cs1 cs2) + (if (= (first cs1) (first cs2)) + (compare-first-card (rest cs1) (rest cs2)) + (< (first cs1) (first cs2)))) + +(define (compare-hands with h1 h2) + (define rank1 (with h1)) + (define rank2 (with h2)) + (if (= rank1 rank2) (compare-first-card (hand-cards h1) (hand-cards h2)) (< rank1 rank2))) + +;; part 1 + +(define (compare-hands-no-wilds h1 h2) + (compare-hands identify-hand h1 h2)) + +(define (total-score in #:jokers [jokers? #false]) + (define sorted-hands + (~> in + (map (curry parse-hand #:jokers jokers?) _) + (sort (if jokers? compare-hands-no-wilds compare-hands-with-wilds)))) + (for/sum ([(h i) + (in-indexed sorted-hands)]) + (* (add1 i) (hand-wager h)))) + +(total-score input) + +;; part 2 + +(define/memoize (find-best-joker-substitution h) + (for/fold ([best-hand (hand '() 0)]) + ([wild (in-inclusive-range 2 14)]) + (define trial-hand + (hand (map (λ (c) (if (= c 1) wild c)) (hand-cards h)) (hand-wager h))) + (if (> (identify-hand trial-hand) (identify-hand best-hand)) + trial-hand + best-hand))) + +(define (compare-hands-with-wilds h1 h2) + (compare-hands (λ~> find-best-joker-substitution identify-hand) h1 h2)) + +(total-score input #:jokers #true) diff --git a/aoc2023-racket/day-08/day-08.rkt b/aoc2023-racket/day-08/day-08.rkt new file mode 100644 index 0000000..06daafa --- /dev/null +++ b/aoc2023-racket/day-08/day-08.rkt @@ -0,0 +1,36 @@ +#lang racket + +(require advent-of-code + threading) + +(struct exits (left right) #:transparent) + +(match-define (list raw-directions raw-maze) + (~> (fetch-aoc-input (find-session) 2023 8 #:cache #true) (string-split "\n\n"))) + +(define directions (string->list raw-directions)) + +(define maze + (for/hash ([line (in-list (string-split raw-maze "\n"))]) + (match (regexp-match #rx"(...) = \\((...), (...)\\)" line) + [(list _ name left right) (values name (exits left right))]))) + +(define (to-next-node start end dirs maze) + (for/fold ([current start] + [acc 0] + #:result acc) + ([dir (in-cycle dirs)]) + #:break (string-suffix? current end) + (define node (hash-ref maze current)) + (case dir + [(#\L) (values (exits-left node) (add1 acc))] + [(#\R) (values (exits-right node) (add1 acc))]))) + +;; part 1 +(to-next-node "AAA" "ZZZ" directions maze) + +;; part 2 +(for/lists (ns #:result (apply lcm ns)) + ([start (in-list (hash-keys maze))] + #:when (string-suffix? start "A")) + (to-next-node start "Z" directions maze)) diff --git a/aoc2023-racket/day-09/day-09-polynomial.rkt b/aoc2023-racket/day-09/day-09-polynomial.rkt new file mode 100644 index 0000000..5bacb1f --- /dev/null +++ b/aoc2023-racket/day-09/day-09-polynomial.rkt @@ -0,0 +1,17 @@ +#lang racket + +(require advent-of-code + threading + simple-polynomial/tools) + +(define histories + (for/list ([raw-history (in-lines (open-aoc-input (find-session) 2023 9 #:cache #true))]) + (~>> raw-history + string-split + (map string->number)))) + +(for/lists (left right #:result (cons (apply + left) (apply + right))) + ([history (in-list histories)]) + (define f (interpolate-at-integer-points history)) + (values (f -1) + (f (length history)))) diff --git a/aoc2023-racket/day-09/day-09.rkt b/aoc2023-racket/day-09/day-09.rkt new file mode 100644 index 0000000..5eda1eb --- /dev/null +++ b/aoc2023-racket/day-09/day-09.rkt @@ -0,0 +1,32 @@ +#lang racket + +(require advent-of-code + threading) + +(define histories + (for/list ([raw-history (in-lines (open-aoc-input (find-session) 2023 9 #:cache #true))]) + (~>> raw-history + string-split + (map string->number)))) + +(define (constant? xs) + (= 1 (length (remove-duplicates xs)))) + +(define/match (derivative xs) + [((list a b)) (list (- b a))] + [((list* a b _)) (cons (- b a) (derivative (rest xs)))]) + +(define (extrapolate xs) + (if (constant? xs) + (car xs) + (+ (last xs) (extrapolate (derivative xs))))) + +;; part 1 +(~>> histories + (map extrapolate) + (apply +)) + +;; part 2 +(~>> histories + (map (λ~> reverse extrapolate)) + (apply +)) diff --git a/aoc2023-racket/day-10/day-10.rkt b/aoc2023-racket/day-10/day-10.rkt new file mode 100644 index 0000000..64d8727 --- /dev/null +++ b/aoc2023-racket/day-10/day-10.rkt @@ -0,0 +1,97 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (r c) #:transparent) + +(define/match (add-posns _p1 _p2) + [((posn x1 y1) (posn x2 y2)) (posn (+ x1 x2) (+ y1 y2))]) + +(define go-north (posn -1 0)) +(define go-south (posn 1 0)) +(define go-east (posn 0 1)) +(define go-west (posn 0 -1)) + +(define initial-directions + (list (cons go-north '(#\| #\7 #\F)) + (cons go-south '(#\| #\J #\L)) + (cons go-east '(#\- #\J #\7)) + (cons go-west '(#\- #\F #\L)))) + +(define/match (pipe-neighbors _pipe) + [(#\|) (list go-north go-south)] + [(#\-) (list go-east go-west)] + [(#\L) (list go-north go-east)] + [(#\F) (list go-south go-east)] + [(#\7) (list go-south go-west)] + [(#\J) (list go-north go-west)]) + +(define (make-pipe-grid in) + (for*/hash ([(row r) (in-indexed (string-split in "\n"))] + [(ch c) (in-indexed (string->list row))]) + (values (posn (add1 r) (add1 c)) ch))) + +(define (get-valid-S-neighbors S grid) + (for/list ([dir (in-list initial-directions)] + #:do [(match-define (cons d valid) dir)] + #:do [(define neighbor (add-posns d S))] + #:when (member (hash-ref grid neighbor 'none) valid)) + neighbor)) + +(define (to-next-pipe current previous grid [acc '()]) + (cond + [(equal? (hash-ref grid current #f) #\S) acc] + [else + (define next + (for/first ([d (in-list (pipe-neighbors (hash-ref grid current)))] + #:do [(define neighbor (add-posns d current))] + #:unless (equal? neighbor previous)) + neighbor)) + (~> next (to-next-pipe _ current grid (cons current acc)))])) + +;; part 1 +(define input (fetch-aoc-input (find-session) 2023 10 #:cache #true)) + +(define pipe-grid (make-pipe-grid input)) + +(define S-posn + (for/first ([(k v) (in-hash pipe-grid)] #:when (equal? v #\S)) + k)) + +(define S-neighbors (get-valid-S-neighbors S-posn pipe-grid)) + +(define pipe-loop (to-next-pipe (first S-neighbors) S-posn pipe-grid '())) + +(/ (add1 (length pipe-loop)) 2) + +;; part 2 +(define pipe-loop-set (~> (list->set pipe-loop) (set-add S-posn))) + +(define (trace-rays pt pipes grid) + (cond + [(set-member? pipes pt) #f] + [else (odd? (trace-ray pt pipes grid))])) + +(define (trace-ray pt pipes grid) + (define row (posn-r pt)) + (for/fold ([acc 0] + [corner #f] + #:result acc) + ([col (in-naturals (posn-c pt))] + #:do [(define test-pt (posn row col))] + #:break (not (hash-has-key? grid test-pt)) + #:when (set-member? pipes test-pt)) + (define pipe (hash-ref grid test-pt)) + (match* (corner pipe) + [(#f #\|) (values (add1 acc) #f)] ; vertical crossing + [(#f (or #\F #\L)) (values acc pipe)] + [(#\F #\J) (values (add1 acc) #f)] ; a ┏━┛ shape counts as a vertical crossing + [(#\L #\7) (values (add1 acc) #f)] + [(#\F #\7) (values acc #f)] ; a ┏━┓ shape doesn't count + [(#\L #\J) (values acc #f)] + [(_ _) (values acc corner)]))) + +(~> pipe-grid + hash-keys + (count (λ~> (trace-rays pipe-loop-set pipe-grid)) _)) diff --git a/aoc2023-racket/day-11/day-11.rkt b/aoc2023-racket/day-11/day-11.rkt new file mode 100644 index 0000000..dba617b --- /dev/null +++ b/aoc2023-racket/day-11/day-11.rkt @@ -0,0 +1,40 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (x y) #:transparent) + +(define input + (~> (fetch-aoc-input (find-session) 2023 11 #:cache #true) + (string-split "\n") + (map string->list _))) + +(define (get-empty-ranks grid) + (for/list ([(rank n) (in-indexed grid)] #:when (equal? '(#\.) (remove-duplicates rank))) + n)) + +(define (count-prior-empty-ranks rank empty-ranks) + (~> empty-ranks + (takef (curryr < rank)) + length)) + +(define empty-rows (get-empty-ranks input)) +(define empty-columns (get-empty-ranks (apply map list input))) ;; transpose + +(define (sum-of-star-distances in expand-by) + (define stars + (for*/list ([(row x) (in-indexed in)] + [(col y) (in-indexed row)] + #:when (equal? col #\#)) + (posn (+ x (* (sub1 expand-by) (count-prior-empty-ranks x empty-rows))) + (+ y (* (sub1 expand-by) (count-prior-empty-ranks y empty-columns)))))) + (for/sum ([star-pair (in-combinations stars 2)]) + (match-define (list (posn x1 y1) (posn x2 y2)) star-pair) + (+ (abs (- x1 x2)) (abs (- y1 y2))))) + +;; part 1 +(sum-of-star-distances input 2) + +;; part 2 +(sum-of-star-distances input 1000000) diff --git a/aoc2023-racket/day-12/day-12.rkt b/aoc2023-racket/day-12/day-12.rkt new file mode 100644 index 0000000..50b14bb --- /dev/null +++ b/aoc2023-racket/day-12/day-12.rkt @@ -0,0 +1,65 @@ +#lang racket + +(require advent-of-code + threading + memo) + +(struct condition (template spring-set)) + +(define conditions + (for/list ([line (in-lines (open-aoc-input (find-session) 2023 12 #:cache #true))]) + (match (string-split line #px"[ ,]") + [(list* template spring-set) + (condition (string->list template) (map string->number spring-set))]))) + +(define/memoize (do-count template spring-group left-in-group need-gap?) + ;; template: list of spring positions + ;; spring-group: list of remaining contiguous groups of damaged springs + ;; left-in-group: springs remaining in current bad spring group being placed + ;; need-gap?: did we just finish placing a bad spring group + ;; and need at least one undamaged spring before starting the next one? + (match* (template spring-group left-in-group need-gap?) + ;; no springs left to place and no places left to place springs + ;; this is an OK arrangement, count it + [('() '() 0 _) 1] + ;; ambiguous wildcard, try both skipping this spot and starting a damaged spring group here + [((list* #\? t-rest) (list* g g-rest) 0 #f) + (+ (do-count t-rest g-rest (sub1 g) (= g 1)) + (do-count t-rest spring-group 0 #f))] + ;; definitely a place for a good spring (.), move on without consuming any spring groups + [((list* #\? t-rest) '() 0 #f) ; good spring, no more damaged springs to place + (do-count t-rest spring-group 0 #f)] + [((list* #\? t-rest) _ 0 #t) ; good spring right after finishing a group of bad springs + (do-count t-rest spring-group 0 #f)] + [((list* #\. t-rest) _ 0 _) ; known good spring + (do-count t-rest spring-group 0 #f)] + ;; start of bad spring (#) group, use the next spring group and remove 1 from it + [((list* #\# t-rest) (list* g g-rest) 0 #f) (do-count t-rest g-rest (sub1 g) (= g 1))] + ;; continuation of bad spring group, same + [((list* (or #\? #\#) t-rest) g left #f) (do-count t-rest g (sub1 left) (= left 1))] + ;; if nothing above works, this arrangement's invalid + [(_ _ _ _) 0])) + +(define (count-solutions c) + (match-define (condition template spring-set) c) + (do-count template spring-set 0 #f)) + +;; part 1 +(for/sum ([c (in-list conditions)]) + (count-solutions c)) + +;; part 2 +(define expanded-conditions + (for/list ([c (in-list conditions)]) + (condition (~> c + condition-template + (make-list 5 _) + (add-between #\?) + flatten) + (~> c + condition-spring-set + (make-list 5 _) + flatten)))) + +(for/sum ([c* (in-list expanded-conditions)]) + (count-solutions c*)) diff --git a/aoc2023-racket/day-13/day-13.rkt b/aoc2023-racket/day-13/day-13.rkt new file mode 100644 index 0000000..47718f8 --- /dev/null +++ b/aoc2023-racket/day-13/day-13.rkt @@ -0,0 +1,47 @@ +#lang racket + +(require advent-of-code + threading) + +(define input + (~>(fetch-aoc-input (find-session) 2023 13 #:cache #true) + (string-split "\n\n") + (map (λ~> string-split) _))) + +(define (do-symmetric? lefts rights errs) + (cond + [(empty? rights) #f] + [else + (define found-errs + (for/sum ([l (in-string (string-join lefts ""))] + [r (in-string (string-join rights ""))] + #:unless (char=? l r)) + 1)) + (if (= errs found-errs) + (length lefts) + (do-symmetric? (cons (first rights) lefts) + (rest rights) + errs))])) + +(define (symmetric? xss errs) + (do-symmetric? (list (first xss)) (rest xss) errs)) + +(define (transpose strs) + (~> strs + (map string->list _) + (apply map list _) + (map list->string _))) + +(define (find-symmetry-score xss errs) + (cond + [(symmetric? xss errs) => (curry * 100)] + [else (symmetric? (transpose xss) errs)])) + +;; part 1 +(for/sum ([note (in-list input)]) + (find-symmetry-score note 0)) + +;; part 2 +(for/sum ([note (in-list input)]) + (find-symmetry-score note 1)) + diff --git a/aoc2023-racket/day-14/day-14.rkt b/aoc2023-racket/day-14/day-14.rkt new file mode 100644 index 0000000..d0b7cad --- /dev/null +++ b/aoc2023-racket/day-14/day-14.rkt @@ -0,0 +1,49 @@ +#lang racket + +(require advent-of-code + threading + "../../jj-aoc.rkt") + +(define input + (~> (fetch-aoc-input (find-session) 2023 14 #:cache #true) + string-split + (map string->list _) + transpose)) + +(define (roll-boulders board) + (for/list ([col (in-list board)]) + (~> col (chunks-by (curry equal? #\#)) (append-map (curryr sort char>?) _)))) + +(define (score board) + (for*/sum ([col (in-list board)] + [(row n) (in-indexed (reverse col))] + #:when (equal? row #\O)) + (add1 n))) + +;; part 1 +(~> input + roll-boulders + score) + +;; part 2 +(define (rotate-board xss) + (~> xss + (map reverse _) + transpose)) + +(define (full-cycle board) + (foldl (λ (_ acc) (~> acc roll-boulders rotate-board)) board (range 4))) + +(define (spin-to-win board) + (define cache (make-hash)) + (define (do-spin board n) + (match (hash-ref cache board 'not-found) + ['not-found + (hash-set! cache board n) + (do-spin (full-cycle board) (sub1 n))] + [seen + (define to-end (modulo n (- seen n))) + (score (foldl (λ (_ acc) (full-cycle acc)) board (range to-end)))])) + (do-spin board 1000000000)) + +(~> input spin-to-win) diff --git a/aoc2023-racket/day-15/day-15.rkt b/aoc2023-racket/day-15/day-15.rkt new file mode 100644 index 0000000..d049565 --- /dev/null +++ b/aoc2023-racket/day-15/day-15.rkt @@ -0,0 +1,41 @@ +#lang racket + +(require advent-of-code + threading) + +(define input + (~> (fetch-aoc-input (find-session) 2023 15 #:cache #true) string-trim (string-split ","))) + +(define (hash-algorithm str) + (for/fold ([acc 0]) ([c (in-string str)]) + (~> c char->integer (+ acc) (* 17) (modulo _ 256)))) + +;; part 1 +(for/sum ([code (in-list input)]) (hash-algorithm code)) + +;; part 2 +(define (remove-lens boxes label) + (hash-update boxes + (hash-algorithm label) + (λ (lens-set) (remove label lens-set (λ (rem l) (equal? rem (car l))))) + '())) + +(define (insert-lens boxes label focal) + (define new-lens (cons label focal)) + (hash-update boxes + (hash-algorithm label) + (λ (lens-set) + (if (assoc label lens-set) + (map (λ (pair) (if (equal? (car pair) label) new-lens pair)) lens-set) + (append lens-set (list new-lens)))) + (list new-lens))) + +(define (focusing-power boxes) + (for*/sum ([(box-number lenses) (in-hash boxes)] [(lens order) (in-indexed lenses)]) + (* (add1 box-number) (add1 order) (cdr lens)))) + +(for/fold ([boxes (hash)] #:result (focusing-power boxes)) ([code (in-list input)]) + (match code + [(regexp #rx"(.*)=(.*)" (list _ label (app string->number focal))) + (insert-lens boxes label focal)] + [(regexp #rx"(.*)-" (list _ label)) (remove-lens boxes label)])) diff --git a/aoc2023-racket/day-16/day-16.rkt b/aoc2023-racket/day-16/day-16.rkt new file mode 100644 index 0000000..4a70de8 --- /dev/null +++ b/aoc2023-racket/day-16/day-16.rkt @@ -0,0 +1,70 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (r c) #:transparent) +(struct light (posn dir) #:transparent) + +(define input (fetch-aoc-input (find-session) 2023 16 #:cache #true)) + +(define grid + (for*/hash ([(row r) (in-indexed (string-split input "\n"))] + [(cell c) (in-indexed (in-string row))]) + (values (posn r c) cell))) + +(define/match (move _l) + [((light (posn r c) 'up)) (light (posn (sub1 r) c) 'up)] + [((light (posn r c) 'right)) (light (posn r (add1 c)) 'right)] + [((light (posn r c) 'left)) (light (posn r (sub1 c)) 'left)] + [((light (posn r c) 'down)) (light (posn (add1 r) c) 'down)]) + +(define/match (transform l _cell) + [(_ #\.) l] + [(_ 'off) '()] + + [((light _ (or 'up 'down)) #\|) l] + [((light _ (or 'left 'right)) #\-) l] + + [((light p 'left) #\/) (light p 'down)] + [((light p 'down) #\/) (light p 'left)] + [((light p 'right) #\/) (light p 'up)] + [((light p 'up) #\/) (light p 'right)] + + [((light p 'left) #\\) (light p 'up)] + [((light p 'up) #\\) (light p 'left)] + [((light p 'right) #\\) (light p 'down)] + [((light p 'down) #\\) (light p 'right)] + + [((light p (or 'left 'right)) #\|) (list (light p 'up) (light p 'down))] + [((light p (or 'up 'down)) #\-) (list (light p 'left) (light p 'right))]) + +;; part 1 +(define (get-energized start) + (for/fold ([energized (set)] + [previously-visited (set)] + [beam-tips (set start)] + #:result (set-count energized)) + ([_ (in-naturals)]) + (define next-positions + (list->set + (flatten (for/list ([b (in-set beam-tips)]) + (~> b move ((λ (b) (transform b (hash-ref grid (light-posn b) 'off))))))))) + (define all-visited (set-union previously-visited next-positions)) + (define next-energized (set-union energized (list->set (set-map next-positions light-posn)))) + #:break (equal? previously-visited all-visited) + (values next-energized all-visited (set-subtract next-positions previously-visited)))) + +(get-energized (light (posn 0 -1) 'right)) + +;; part 2 +(define rows (~> input (string-split "\n") length)) +(define cols (~> input (string-split #rx"\n.*") first string-length)) + +(define all-starting-positions + (append (map (λ (r) (light (posn r -1) 'right)) (range rows)) + (map (λ (r) (light (posn r cols) 'left)) (range rows)) + (map (λ (c) (light (posn -1 c) 'down)) (range cols)) + (map (λ (c) (light (posn rows c) 'up)) (range cols)))) + +(get-energized (argmax get-energized all-starting-positions)) \ No newline at end of file diff --git a/aoc2023-racket/day-17/day-17.rkt b/aoc2023-racket/day-17/day-17.rkt new file mode 100644 index 0000000..05709ad --- /dev/null +++ b/aoc2023-racket/day-17/day-17.rkt @@ -0,0 +1,86 @@ +#lang racket + +(require advent-of-code + threading + data/heap) + +(struct state (p heat-lost previous history)) +(struct posn (r c)) + +(define/match (add _p1 _p2) + [((posn r1 c1) (posn r2 c2)) (posn (+ r1 r2) (+ c1 c2))]) + +(define deltas (list (posn 0 1) (posn 0 -1) (posn 1 0) (posn -1 0))) + +(define input (fetch-aoc-input (find-session) 2023 17 #:cache #true)) + +(define grid + (for*/hash ([(row r) (in-indexed (in-list (string-split input "\n")))] + [(col c) (in-indexed (in-string row))]) + (values (posn r c) (~> col string string->number)))) + +(define goal-posn (~>> grid hash-keys (argmax (λ (p) (+ (posn-r p) (posn-c p)))))) + +(define (make-key s) + (cons (state-p s) (same-dir s))) + +(define (goal? n s) + (and (equal? goal-posn (state-p s)) + (>= (length (same-dir s)) n))) + +(define (same-dir s) + (define history (state-history s)) + (if (empty? history) + '() + (takef history (λ (n) (equal? n (car history)))))) + +(define (find-good-neighbors min-dist max-dist s) + (match-define (state p hl prev hist) s) + + (define (eliminate-bad-neighbors delta) + (define neighbor (add p delta)) + (cond + [(or (equal? neighbor prev) (not (hash-has-key? grid neighbor))) #false] + [else + (define same (same-dir s)) + (define l (length same)) + (cond + [(= max-dist l) (not (equal? delta (car same)))] + [(= l 0) #true] + [(< l min-dist) (equal? delta (car same))] + [else #t])])) + + (define (make-state delta) + (define neighbor (add p delta)) + (define new-loss (+ hl (hash-ref grid neighbor))) + (state neighbor new-loss p (cons delta hist))) + + (~>> deltas (filter eliminate-bad-neighbors) (map make-state))) + +(define (find-path neighbor-fn goal-fn) + (define seen (mutable-set)) + (define queue (make-heap (λ (a b) (<= (state-heat-lost a) (state-heat-lost b))))) + (heap-add! queue (state (posn 0 0) 0 'none '())) + + (let bfs () + (define s (heap-min queue)) + (heap-remove-min! queue) + (define key (make-key s)) + (cond + [(set-member? seen key) (bfs)] + [else + (set-add! seen key) + (define neighbors (neighbor-fn s)) + (define final (findf goal-fn neighbors)) + (if final + (state-heat-lost final) + (begin + (for ([n (in-list neighbors)]) + (heap-add! queue n)) + (bfs)))]))) + +;; part 1 +(find-path (curry find-good-neighbors 0 3) (curry goal? 1)) + +;; part 2 +(find-path (curry find-good-neighbors 4 10) (curry goal? 4)) \ No newline at end of file diff --git a/aoc2023-racket/day-18/day-18.rkt b/aoc2023-racket/day-18/day-18.rkt new file mode 100644 index 0000000..b589e41 --- /dev/null +++ b/aoc2023-racket/day-18/day-18.rkt @@ -0,0 +1,48 @@ +#lang racket +(require advent-of-code + threading) + +(struct coord (x y)) + +(define input (~> (fetch-aoc-input (find-session) 2023 18 #:cache #true))) + +(define (go-to-next-coord c dir dist) + (match-define (coord x y) c) + (match dir + [(or "R" "0") (coord (+ x dist) y)] + [(or "D" "1") (coord x (- y dist))] + [(or "L" "2") (coord (- x dist) y)] + [(or "U" "3") (coord x (+ y dist))])) + +(define/match (triangle-area _coord1 _coord2) + [((coord x1 y1) (coord x2 y2)) (/ (- (* x1 y2) (* x2 y1)) 2)]) + +(define (find-area-using parser) + (for/fold ([area 0] + [perimeter 0] + [current-coord (coord 0 0)] + #:result (+ 1 (abs area) (/ perimeter 2))) + ([dig (in-list (string-split input "\n"))]) + (define-values (dir dist) (parser dig)) + (define next-coord (go-to-next-coord current-coord dir dist)) + (values (+ area (triangle-area current-coord next-coord)) + (+ perimeter dist) next-coord))) + +;; part 1 +(define (parse-front dig) + (match-define (regexp #rx"(.) (.*) \\((.*)\\)" + (list _ dir (app string->number dist) _hex)) + dig) + (values dir dist)) + +(find-area-using parse-front) + +;; part 2 + +(define (parse-hex dig) + (match-define (regexp #rx".*\\(#(.....)(.)\\)" + (list _ (app (curryr string->number 16) dist) dir)) + dig) + (values dir dist)) + +(find-area-using parse-hex) diff --git a/aoc2023-racket/day-19/day-19.rkt b/aoc2023-racket/day-19/day-19.rkt new file mode 100644 index 0000000..f7561f6 --- /dev/null +++ b/aoc2023-racket/day-19/day-19.rkt @@ -0,0 +1,134 @@ +#lang racket + +(require advent-of-code + threading + data/applicative + data/monad + megaparsack + megaparsack/text + racket/struct) + +(struct part (x m a s)) +(struct rule (rating comparison threshold action)) +(struct just (action)) +(struct interval (from to)) + +(match-define (list raw-workflows raw-parts) + (~> (fetch-aoc-input (find-session) 2023 19 #:cache #true) + (string-split "\n\n") + (map (curryr string-split "\n") _))) + +(define/match (to-getter _) + [(#\x) part-x] + [(#\m) part-m] + [(#\a) part-a] + [(#\s) part-s]) + +(define/match (to-comp _) + [(#\>) >] + [(#\<) <]) + +(define/match (to-action _) + [('(#\R)) 'reject] + [('(#\A)) 'accept] + [(name) (apply string name)]) + +(define rule/p + (do (or/p + (try/p (do [rating <- (char-in/p "xmas")] + [comparison <- (char-in/p "<>")] + [threshold <- integer/p] + (char/p #\:) + [action <- (many+/p letter/p)] + (pure (rule (to-getter rating) + (to-comp comparison) + threshold + (to-action action))))) + (do [name <- (many+/p letter/p)] (pure (just (to-action name))))))) +(define rules/p + (do [name <- (many+/p letter/p)] + (char/p #\{) + [rules <- (many+/p rule/p #:sep (char/p #\,))] + (char/p #\}) + (pure (cons (list->string name) rules)))) + +(define rating/p (do letter/p (char/p #\=) integer/p)) +(define parts/p + (do (string/p "{") + [ratings <- (many/p rating/p #:sep (char/p #\,) #:min 4 #:max 4)] + (string/p "}") + (pure (apply part ratings)))) + +(define workflows + (~>> raw-workflows + (map (λ~>> (parse-string rules/p) parse-result!)) + make-immutable-hash)) +(define parts (map (λ~>> (parse-string parts/p) parse-result!) raw-parts)) + +;; part 1 + +(define (evaluate-workflow p [workflow-name "in"]) + (define rules (hash-ref workflows workflow-name)) + (match (evaluate-rules p rules) + ['accept (~> p struct->list (apply + _))] + ['reject 0] + [name (evaluate-workflow p name)])) + +(define (evaluate-rules p rules) + (match rules + [(list* (just action) _) action] + [(list* (rule rating comparison threshold action) _) + #:when (comparison (rating p) threshold) + action] + [(list* _ tail) (evaluate-rules p tail)])) + +(for/sum ([p (in-list parts)]) (evaluate-workflow p)) + +;; part 2 + +(define (part-update-range pr rating i) + (match rating + [(== part-x) (struct-copy part pr (x i))] + [(== part-m) (struct-copy part pr (m i))] + [(== part-a) (struct-copy part pr (a i))] + [(== part-s) (struct-copy part pr (s i))])) + +(define (evaluate-workflow-on-range pr [workflow-name "in"]) + (define rules (hash-ref workflows workflow-name)) + (evaluate-rules-on-range pr rules)) + +(define (evaluate-rules-on-range pr rules) + (match rules + [(list* (just 'accept) _) + (~> pr struct->list + (map (λ (i) (add1 (- (interval-to i) (interval-from i)))) _) + (apply * _))] + [(list* (just 'reject) _) 0] + [(list* (just name) _) (evaluate-workflow-on-range pr name)] + [(list* (rule rating (== <) threshold action) tail) + (match-define (interval i-min i-max) (rating pr)) + (split-range pr + rating + (interval i-min (sub1 threshold)) + action + (interval threshold i-max) + tail)] + [(list* (rule rating (== >) threshold action) tail) + (match-define (interval i-min i-max) (rating pr)) + (split-range pr + rating + (interval (add1 threshold) i-max) + action + (interval i-min threshold) + tail)])) + +(define (split-range pr rating keep action pass rules) + (+ (evaluate-rules-on-range (part-update-range pr rating keep) + (list (just action))) + (evaluate-rules-on-range (part-update-range pr rating pass) + rules))) + +(define start-interval (interval 1 4000)) + +(evaluate-workflow-on-range + (part start-interval start-interval start-interval start-interval)) diff --git a/aoc2023-racket/day-20/day-20.rkt b/aoc2023-racket/day-20/day-20.rkt new file mode 100644 index 0000000..2e3852d --- /dev/null +++ b/aoc2023-racket/day-20/day-20.rkt @@ -0,0 +1,144 @@ +#lang racket + +(require advent-of-code + threading + data/applicative + data/monad + megaparsack + megaparsack/text) + +(struct broadcaster ()) +(struct flipflop (state received)) +(struct conjunction (recieved)) +(struct cable (type dests)) +(struct nothing ()) + +(define charlist->symbol (λ~>> (apply string) string->symbol)) + +(define input (fetch-aoc-input (find-session) 2023 20 #:cache true)) + +(define module/p + (do (or/p (do (char/p #\%) + [name <- (many+/p letter/p)] + (pure (cons (charlist->symbol name) (flipflop 'off '())))) + (do (char/p #\&) + [name <- (many+/p letter/p)] + (pure (cons (charlist->symbol name) (conjunction (hash))))) + (do [name <- (many+/p letter/p)] + (pure (cons (charlist->symbol name) (broadcaster))))))) + +(define cable/p + (do [mod <- module/p] + (string/p " -> ") + [names <- (many/p (many+/p letter/p) #:sep (string/p ", "))] + (pure (cable mod (map charlist->symbol names))))) + +(define cables (~> input (string-split "\n") (map (λ~>> (parse-string cable/p) parse-result!) _))) + +(define destinations + (for/hash ([cable (in-list cables)]) + (values (car (cable-type cable)) (cable-dests cable)))) + +(define (set-conjunction-initial-state c) + (cond + [(conjunction? (cdr c)) + (~>> destinations + hash-keys + (filter (λ (k) (member (car c) (hash-ref destinations k)))) + (map (λ (k) (cons k 'low))) + (make-immutable-hash) + conjunction + (cons (car c)))] + [else c])) + +(define (make-initial-conditions-hash cables) + (~>> cables + (map cable-type) + (map set-conjunction-initial-state) + make-immutable-hash)) + +(define (receive mod from tone) + (match mod + [(flipflop state queue) (flipflop state (append queue (list tone)))] + [(conjunction received) (conjunction (hash-set received from tone))] + [(nothing) (nothing)])) + +; needed for part 2 +(define to-rx '(rk cd zf qx)) +(define sentry-tones (make-hash (for/list ([node to-rx]) (cons node 0)))) + +(define (press-button-once current-state this-round) + (for/fold ([queue '(broadcaster)] + [all-cables-state current-state] + [high 0] + [low 0] + #:result (values all-cables-state high low)) + ([_i (in-naturals)] #:break (empty? queue)) + (match-define (list* hd tl) queue) + (define to (hash-ref destinations hd (nothing))) + (match (hash-ref all-cables-state hd) + [(broadcaster) + (define state* + (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) + all-cables-state + to)) + (values (hash-ref destinations 'broadcaster) state* high (+ (length to) (add1 low)))] + [(flipflop 'off (list* 'low q)) + (define state* + (~> all-cables-state + (foldl (λ (r acc) + (when (member r to-rx) + (println (~a r " received high tone at " this-round))) + (hash-update acc r (λ~> (receive hd 'high)) (nothing))) + _ + to) + (hash-set _ hd (flipflop 'on q)))) + (values (append tl to) state* (+ (length to) high) low)] + [(flipflop 'on (list* 'low q)) + (define state* + (~> all-cables-state + (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) _ to) + (hash-set _ hd (flipflop 'off q)))) + (values (append tl to) state* high (+ (length to) low))] + [(flipflop on-or-off (list* 'high q)) + (define state* (~> all-cables-state (hash-set _ hd (flipflop on-or-off q)))) + (values tl state* high low)] + [(conjunction received) + #:when (or (empty? (hash-values received)) (member 'low (hash-values received))) + + (when (member hd to-rx) + (hash-set! sentry-tones hd this-round)) + (define state* + (foldl (λ (r acc) + (hash-update acc r (λ~> (receive hd 'high)) (nothing))) + all-cables-state + to)) + (values (append to tl) state* (+ (length to) high) low)] + [(conjunction _) + (define state* + (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) + all-cables-state + to)) + (values (append tl to) state* high (+ (length to) low))] + [(nothing) (values tl all-cables-state high low)]))) + +;; part 1 +(for/fold ([starting-state (make-initial-conditions-hash cables)] + [high 0] + [low 0] + #:result (* high low)) + ([i (in-range 1000)]) + (define-values (next-state this-high this-low) (press-button-once starting-state i)) + (values next-state (+ high this-high) (+ low this-low))) + +;; part 2 +;; rx receives a tone from gh, which receives four tones itself +;; those tones arrive on regular synced cycles so it's just the LCM of those cycle lengths +;; and since those cycle lengths are prime, it reduces to the product of the lengths +;; this is a really hacky mutable solution, I'm sure there's better ways of flagging these cycles + +(for/fold ([starting-state (make-initial-conditions-hash cables)] + #:result (apply * (hash-values sentry-tones))) + ([i (in-range 1 5000)]) + (define-values (next-state _high _low) (press-button-once starting-state i)) + (values next-state)) \ No newline at end of file diff --git a/aoc2023-racket/day-21/day-21.rkt b/aoc2023-racket/day-21/day-21.rkt new file mode 100644 index 0000000..b5478eb --- /dev/null +++ b/aoc2023-racket/day-21/day-21.rkt @@ -0,0 +1,69 @@ +#lang racket + +(require advent-of-code + threading + simple-polynomial + racket/hash) + +(define input (fetch-aoc-input (find-session) 2023 21 #:cache #true)) + +(define initial-garden + (~> (for*/list ([(row r) (in-indexed (string-split input "\n"))] + [(col c) (in-indexed row)] + #:unless (equal? col #\#)) + (cons (cons r c) (if (equal? col #\S) 'on 'off))) + make-hash)) + +(define (neighbors p) + (match-define (cons r c) p) + (list (cons (add1 r) c) (cons (sub1 r) c) (cons r (add1 c)) (cons r (sub1 c)))) + +(define (make-n-steps garden n) + (define g (hash-copy garden)) + (define (make-one-step) + (define update (make-hash)) + (for ([(cons state) (in-hash g)] #:when (equal? state 'on)) + (hash-set! update cons 'off) + (for ([neighbor (in-list (neighbors cons))] #:when (hash-has-key? g neighbor)) + (hash-set! update neighbor 'on))) + (hash-union! g update #:combine/key (λ (_k _v v) v))) + (for/fold ([_ void] + #:result (~>> g hash-values (count (curry equal? 'on)))) + ([i (in-range n)]) + (displayln i) + (make-one-step))) + +;; part 1 +(make-n-steps initial-garden 64) + +;; part 2 +;; the growth of the steps pattern is regular and quadratic +;; the rock pattern has aisles in it that allow the steps pattern to expand freely +;; such that it will cross from one side to another in X steps +;; where X is the height/width of the repeated section + +(define grid-size (~> input (string-split "\n") length)) +(define half-size (/ (sub1 grid-size) 2)) + +(define expanded-garden + (~> (for*/list (#:do [(define rows (string-split input "\n"))] + #:do [(define height (length rows))] + [(row r) (in-indexed rows)] + #:do [(define width (string-length row))] + [(col c) (in-indexed row)] + #:unless (equal? col #\#) + [n (in-inclusive-range -3 3)] + [m (in-inclusive-range -3 3)]) + + (cons (cons (+ r (* n height)) (+ c (* m width))) + (if (and (= n m 0) (equal? col #\S)) 'on 'off))) + make-hash)) + +(define fitting-points + (for/list ([n (in-range 3)] #:do [(define size (+ half-size (* n grid-size)))]) + (cons n (make-n-steps expanded-garden size)))) + +(define end-cycle 26501365) +(define x (/ (- end-cycle half-size) grid-size)) + +((points->polynomial fitting-points) x) \ No newline at end of file diff --git a/aoc2023-racket/day-22/day-22.rkt b/aoc2023-racket/day-22/day-22.rkt new file mode 100644 index 0000000..53668c0 --- /dev/null +++ b/aoc2023-racket/day-22/day-22.rkt @@ -0,0 +1,109 @@ +#lang racket + +(require advent-of-code + threading + data/applicative + data/monad + megaparsack + megaparsack/text + racket/hash) + +(struct posn (x y z)) +(struct block (n from to)) + +(define input (fetch-aoc-input (find-session) 2023 22 #:cache #true)) + +(define coordinate/p + (do [coords <- (many/p integer/p #:sep (char/p #\,) #:min 3 #:max 3)] + (pure (apply posn coords)))) + +(define block/p + (do [from <- coordinate/p] + (char/p #\~) + [to <- coordinate/p] + (pure (cons from to)))) + +(define starting-blocks + (~> (for/list ([line (in-list (string-split input "\n"))] + [n (in-naturals)]) + (match-define (cons from to) (parse-result! (parse-string block/p line))) + (block n from to)) + (sort < #:key (λ~> block-from posn-z)))) + +(define (all-in-cross-section-at-level b z) + (match-define (block _ (posn x1 y1 _) (posn x2 y2 _)) b) + (for*/list ([x (in-inclusive-range x1 x2)] + [y (in-inclusive-range y1 y2)]) + (posn x y z))) + +(define (place-block-at-level b h dz) + (match-define (block n (posn x1 y1 z1) (posn x2 y2 z2)) b) + (define now-occupied + (for*/hash ([x (in-inclusive-range x1 x2)] + [y (in-inclusive-range y1 y2)] + [z (in-inclusive-range dz (+ dz (- z2 z1)))]) + (values (posn x y z) n))) + (hash-union h now-occupied)) + +(define (find-lowest-level b h [z (~> b block-from posn-z)]) + (cond + [(= z 0) + (place-block-at-level b h 1)] + [(findf (curry hash-has-key? h) (all-in-cross-section-at-level b z)) + (place-block-at-level b h (add1 z))] + [else + (find-lowest-level b h (sub1 z))])) + +(define blocks-in-space (foldl find-lowest-level (hash) starting-blocks)) +(define block-positions + (for/fold ([placed-blocks (hash)]) + ([(p n) (in-hash blocks-in-space)]) + (hash-update placed-blocks n (curryr set-add p) (set)))) + +(define (down-one p) + (match p + [(posn x y z) (posn x y (sub1 z))])) + +(define supporting-blocks + (for/hash ([(n-id n-posns) (in-hash block-positions)]) + (values n-id + (for*/set ([(m-id m-posns) (in-hash block-positions)] + #:unless (= n-id m-id) + [m-posn (in-set m-posns)] + #:when (set-member? n-posns (down-one m-posn))) + m-id)))) + +(define supported-by-blocks + (for/hash ([n-id (in-hash-keys supporting-blocks)]) + (define supporters + (~> (for*/set + ([(m-id m-supporting) (in-hash supporting-blocks)] + #:unless (= n-id m-id) + #:when (set-member? m-supporting n-id)) + m-id) + ((λ (s) (if (set-empty? s) (set 'ground) s))))) + (values n-id supporters))) + +;; part 1 +(define vulnerable-blocks + (for/list ([n-id (in-range (length starting-blocks))] + #:when (for/or ([m-supported-by (in-hash-values supported-by-blocks)]) + (set-empty? (set-remove m-supported-by n-id)))) + n-id)) +(- (length starting-blocks) (length vulnerable-blocks)) + +;; part 2 +(for/sum ([n (in-list vulnerable-blocks)]) + (for/fold ([fallen (set n)] + [bricks (set n)] + #:result (~> fallen set-count sub1)) + ([_ (in-naturals)]) + #:break (set-empty? bricks) + (define bricks-above + (for*/set + ([brick (in-set bricks)] + [supporting (in-set (hash-ref supporting-blocks brick))] + #:when (for/and ([supports (in-set (hash-ref supported-by-blocks supporting))]) + (set-member? fallen supports))) + supporting)) + (values (set-union fallen bricks-above) bricks-above))) \ No newline at end of file diff --git a/aoc2023-racket/day-23/day-23.rkt b/aoc2023-racket/day-23/day-23.rkt new file mode 100644 index 0000000..c048013 --- /dev/null +++ b/aoc2023-racket/day-23/day-23.rkt @@ -0,0 +1,89 @@ +#lang racket + +(require advent-of-code + threading + graph) + +(define input (fetch-aoc-input (find-session) 2023 23 #:cache #true)) +(define trails + (for*/hash ([(row r) (in-indexed (string-split input "\n"))] + [(col c) (in-indexed row)] + #:when (not (equal? col #\#))) + ; for now, we don't actually need to detect paths and slopes, just not-rocks + ; in part 1, all forks in the road go right or down, so we can just infer the path + ; direction from the shape of the junction and form a network of junctions from there + ; in part 2, the slopes are removed anyway + (values (cons (add1 r) (add1 c)) col))) + +(define max-row (~> input (string-split "\n") length)) + +(define start (findf (λ (p) (= (car p) 1)) (hash-keys trails))) +(define end (findf (λ (p) (= (car p) max-row)) (hash-keys trails))) + +(define (get-neighbors posn type) + (match-define (cons r c) posn) + (match type + ['junction + (~> (set (cons (add1 r) c) (cons r (add1 c))))] + [_ + (~> (list (cons (add1 r) c) (cons (sub1 r) c) (cons r (add1 c)) (cons r (sub1 c))) + (filter (curry hash-has-key? trails) _) + list->set)])) + +(define junction-points + (for/set ([(k v) (in-hash trails)] + #:when (not (= (set-count (get-neighbors k v)) 2))) + k)) + +(define trails-with-junctions + (for/hash ([k (in-hash-keys trails)]) + (cond + [(set-member? junction-points k) (values k 'junction)] + [else (values k 'trail)]))) + +(define (walk-to-next-junction start current [length 1] [seen (set start)]) + (define next (~> current + (get-neighbors _ 'trail) + (set-subtract seen) set-first)) + (cond + [(equal? (hash-ref trails-with-junctions next) 'junction) + (list (- (add1 length)) start next)] ; weird format is due to graph library + [else + (walk-to-next-junction start next (add1 length) (set-add seen current))])) + +(define routes-to-junctions + (for*/list ([j (in-set junction-points)] + [neighbor (in-set (get-neighbors j 'junction))] + #:when (hash-has-key? trails neighbor)) + (walk-to-next-junction j neighbor))) + +;; part 1 -- using graph library for Bellman-Ford on negative weighted graph +;; Bellman-Ford finds the shortest path, but negating all the path weights +;; will give us the longest path instead +;; +;; unlike Dijkstra which can't handle negative path lengths, Bellman-Ford +;; works as long as the graph is currently directed and acyclic +(define slippery-trail (weighted-graph/directed routes-to-junctions)) +(match-define-values (distances _) (bellman-ford slippery-trail start)) +(- (hash-ref distances end)) + +;; part 2 -- rolling my own DFS that can reject seen junctions and dead ends +(define routes-to-junctions-with-traction + (for/fold ([trails (hash)]) ([route (in-list routes-to-junctions)]) + (match-define (list (app - weight) from to) route) + (~> trails + (hash-update _ from (curry cons (cons to weight)) '()) + (hash-update _ to (curry cons (cons from weight)) '())))) + +(define (dfs g from to [acc 0] [seen (set from)]) + (cond + [(equal? from to) acc] + [else + (define choices (filter (λ (path) (not (set-member? seen (car path)))) (hash-ref g from))) + (if (empty? choices) 0 ; long dead-ends don't count + (for/fold ([best acc]) + ([path (in-list choices)] + #:do [(match-define (cons next dist) path)]) + (max best (dfs g next to (+ acc dist) (set-add seen next)))))])) + +(dfs routes-to-junctions-with-traction start end) \ No newline at end of file diff --git a/aoc2023-racket/day-24/day-24a.rkt b/aoc2023-racket/day-24/day-24a.rkt new file mode 100644 index 0000000..31f526d --- /dev/null +++ b/aoc2023-racket/day-24/day-24a.rkt @@ -0,0 +1,51 @@ +#lang rosette + +(require advent-of-code + threading) + +(struct hail (posn vel) #:transparent) +(struct posn (x y z) #:transparent) +(struct vel (x y z) #:transparent) + +(define input (fetch-aoc-input (find-session) 2023 24 #:cache #true)) + +(define LOWER-BOUND 200000000000000) +(define UPPER-BOUND 400000000000000) + +(define (->struct f str) + (~> str (string-split _ ",") (map (λ~> string-trim string->number) _) (apply f _))) + +(define (parse-hail-record str) + (match-define (list p v) (string-split str " @ ")) + (hail (->struct posn p) + (->struct vel v))) + +(define hail-paths + (for/list ([hail (in-list (string-split input "\n"))]) + (parse-hail-record hail))) + +;; part 1 +(define (valid-intersection? h1 h2) + (match-define (hail (posn x1 y1 _) (vel vx1 vy1 _)) h1) + (match-define (hail (posn x2 y2 _) (vel vx2 vy2 _)) h2) + (cond + [(= (* vy1 vx2) (* vx1 vy2)) #f] + [else + (define t1 (/ (- (* vy2 (- x1 x2)) (* vx2 (- y1 y2))) + (- (* vy1 vx2) (* vx1 vy2)))) + (define t2 (/ (- (* vy1 (- x2 x1)) (* vx1 (- y2 y1))) + (- (* vy2 vx1) (* vx2 vy1)))) + + (define x (+ x1 (* t1 vx1))) + (define y (+ y1 (* t1 vy1))) + + (and (<= LOWER-BOUND x UPPER-BOUND) + (<= LOWER-BOUND y UPPER-BOUND) + (<= 0 t1) + (<= 0 t2))])) + +(for/sum ([(trial-paths) (in-combinations hail-paths 2)] ; + #:when (apply valid-intersection? trial-paths)) + 1) + +;; part 2 - see day-24b.rkt diff --git a/aoc2023-racket/day-24/day-24b.rkt b/aoc2023-racket/day-24/day-24b.rkt new file mode 100644 index 0000000..b106b30 --- /dev/null +++ b/aoc2023-racket/day-24/day-24b.rkt @@ -0,0 +1,37 @@ +#lang rosette + +(require advent-of-code + threading) + +(struct hail (posn vel)) +(struct posn (x y z)) +(struct vel (x y z)) + +(define input (fetch-aoc-input (find-session) 2023 24 #:cache #true)) + +(define (->struct f str) + (~> str (string-split _ ",") (map (λ~> string-trim string->number) _) (apply f _))) + +(define (parse-hail-record str) + (match-define (list p v) (string-split str " @ ")) + (hail (->struct posn p) (->struct vel v))) + +(define hail-paths + (for/list ([hail (in-list (string-split input "\n"))] ; + [_ (in-range 3)]) + (parse-hail-record hail))) + +;; part 1 - see day-24a.rkt +;; part 2 + +(define-symbolic px py pz vx vy vz integer?) + +(define sol + (solve ; + (for ([path (in-list hail-paths)]) + (define-symbolic* t integer?) + (assert (= (+ px (* vx t)) (+ (~> path hail-posn posn-x) (* (~> path hail-vel vel-x) t)))) + (assert (= (+ py (* vy t)) (+ (~> path hail-posn posn-y) (* (~> path hail-vel vel-y) t)))) + (assert (= (+ pz (* vz t)) (+ (~> path hail-posn posn-z) (* (~> path hail-vel vel-z) t))))))) + +(evaluate (+ px py pz) sol) diff --git a/aoc2023-racket/day-25/day-25.rkt b/aoc2023-racket/day-25/day-25.rkt new file mode 100644 index 0000000..aa32e43 --- /dev/null +++ b/aoc2023-racket/day-25/day-25.rkt @@ -0,0 +1,43 @@ +#lang racket + +(require advent-of-code + threading + graph) + +(define input + (~> (fetch-aoc-input (find-session) 2023 25 #:cache #true) + (string-split "\n") + (map (curryr string-split ": ") _))) + +(define all-wires + (for*/list ([wire-diagram (in-list input)] [devices (in-list (string-split (second wire-diagram)))]) + (list (car wire-diagram) devices))) + +;; instead of trying to solve the minimum cut problem, I generated the graph and +;; rendered it in graphviz: + +; (define out (open-output-file "graphviz")) +; (~> all-wires +; unweighted-graph/undirected +; graphviz +; (display out)) +; (close-output-port out) + +;; the bottleneck is very obvious on the graph -- +;; there's two large clusters of nodes, connected by just three edges +;; +;; from the graphviz output, the three critical wires are +;; cpq-hlx +;; hqp-spk +;; chr-zlx + +(define remove-these-three '(("cpq" "hlx") ("hqp" "spk") ("chr" "zlx"))) +(define cut-wires + (for/list ([wire (in-list all-wires)] #:unless (member (sort wire string cut-wires + unweighted-graph/undirected + scc + (map length _) + (apply * _)) \ No newline at end of file diff --git a/aoc2023/.DS_Store b/aoc2023/.DS_Store deleted file mode 100644 index 5172429..0000000 Binary files a/aoc2023/.DS_Store and /dev/null differ diff --git a/aoc2023/.github/workflows/test.yml b/aoc2023/.github/workflows/test.yml deleted file mode 100644 index cf2096e..0000000 --- a/aoc2023/.github/workflows/test.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: test - -on: - push: - branches: - - master - - main - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: erlef/setup-beam@v1 - with: - otp-version: "26.0.2" - gleam-version: "0.32.4" - rebar3-version: "3" - # elixir-version: "1.15.4" - - run: gleam deps download - - run: gleam test - - run: gleam format --check src test diff --git a/aoc2023/.gitignore b/aoc2023/.gitignore deleted file mode 100644 index 8248306..0000000 --- a/aoc2023/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -*.beam -*.ez -build -erl_crash.dump - -aoc.toml \ No newline at end of file diff --git a/aoc2023/README.md b/aoc2023/README.md deleted file mode 100644 index 3f534e8..0000000 --- a/aoc2023/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# aoc2023 - -[![Package Version](https://img.shields.io/hexpm/v/aoc2023)](https://hex.pm/packages/aoc2023) -[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/aoc2023/) - -## Quick start - -```sh -gleam run # Run the project -gleam test # Run the tests -gleam shell # Run an Erlang shell -``` - -## Installation - -If available on Hex this package can be added to your Gleam project: - -```sh -gleam add aoc2023 -``` - -and its documentation can be found at . - -## Use - -* Set up a solution: `gleam run -m adglent/day ` -* Check against examples: `gleam test -- --modules=day/day_test` -* Get final answer: `gleam run -m day/solve

` \ No newline at end of file diff --git a/aoc2023/gleam.toml b/aoc2023/gleam.toml deleted file mode 100644 index 8190aef..0000000 --- a/aoc2023/gleam.toml +++ /dev/null @@ -1,22 +0,0 @@ -name = "aoc2023" -version = "0.1.0" -gleam = ">= 0.33.0" - -# Fill out these fields if you intend to generate HTML documentation or publish -# your project to the Hex package manager. -# -# description = "" -# licences = ["Apache-2.0"] -# repository = { type = "github", user = "username", repo = "project" } -# links = [{ title = "Website", href = "https://gleam.run" }] - -[dependencies] -gleam_stdlib = "~> 0.33" -simplifile = "~> 1.0" -gleam_erlang = "~> 0.23" -gleam_community_maths = "~> 1.0" -gleam_otp = "~> 0.8" -pqueue = "~> 2.0" - -[dev-dependencies] -adglent = "~> 1.2" diff --git a/aoc2023/manifest.toml b/aoc2023/manifest.toml deleted file mode 100644 index 900b5c0..0000000 --- a/aoc2023/manifest.toml +++ /dev/null @@ -1,29 +0,0 @@ -# This file was generated by Gleam -# You typically do not need to edit this file - -packages = [ - { name = "adglent", version = "1.2.0", build_tools = ["gleam"], requirements = ["glint", "gleam_http", "gleam_stdlib", "simplifile", "gleam_erlang", "gap", "gleam_httpc", "snag", "gleam_community_ansi", "tom", "gleam_otp"], otp_app = "adglent", source = "hex", outer_checksum = "A20D35001061F8AD602E3B92FB3AC0E1E4EEC642AD2AAE0ACEAD3A85F37DA7F0" }, - { name = "gap", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "5E369751DB547BFBDA7735878DC04DA31FCA3112193D61D5D7566010C7C8BA98" }, - { name = "gleam_community_ansi", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8B5A9677BC5A2738712BBAF2BA289B1D8195FDF962BBC769569976AD5E9794E1" }, - { name = "gleam_community_colour", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "036C206886AFB9F153C552700A7A0B4D2864E3BC96A20C77E5F34A013C051BE3" }, - { name = "gleam_community_maths", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_maths", source = "hex", outer_checksum = "1B9DB313E94A0E4674CA84C5D29F45ECFE211BFB38ABBD8B23737395F47D08B3" }, - { name = "gleam_erlang", version = "0.23.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "C21CFB816C114784E669FFF4BBF433535EEA9960FA2F216209B8691E87156B96" }, - { name = "gleam_http", version = "3.5.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "AECDA43AFD523D07A8F09068598A6E271C505278A0CB6F9C7A2E4365EAE8D11E" }, - { name = "gleam_httpc", version = "2.1.1", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "06AC1CA52C9BAA66C9D5C0303B2BF34E39AA1546BB96AEE496E4B06D513AB8C7" }, - { name = "gleam_otp", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang"], otp_app = "gleam_otp", source = "hex", outer_checksum = "18EF8242A5E54BA92F717C7222F03B3228AEE00D1F286D4C56C3E8C18AA2588E" }, - { name = "gleam_stdlib", version = "0.33.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "539E37A2AA5EBE8E75F4B74755E4CC604BD957C3000AC8D705A2024886A2738B" }, - { name = "glint", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_community_ansi", "gleam_community_colour", "snag"], otp_app = "glint", source = "hex", outer_checksum = "46E56049CD370D61F720D319D0AB970408C9336EEB918F08B5DCB1DCE9845FA3" }, - { name = "pqueue", version = "2.0.7", build_tools = ["rebar3"], requirements = [], otp_app = "pqueue", source = "hex", outer_checksum = "8B0204BB202335890E4E7F9B99A8EC0B84DDB8513EE298EB180EE9B3BCB4C859" }, - { name = "simplifile", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0BD6F0E7DA1A7E11D18B8AD48453225CAFCA4C8CFB4513D217B372D2866C501C" }, - { name = "snag", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "8FD70D8FB3728E08AC425283BB509BB0F012BE1AE218424A597CDE001B0EE589" }, - { name = "tom", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "5C5A9B8586C547F1F39542B1A3BBD9FEE17AFEAB51CE53B32B13D0D46B421249" }, -] - -[requirements] -adglent = { version = "~> 1.2" } -gleam_community_maths = { version = "~> 1.0" } -gleam_erlang = { version = "~> 0.23" } -gleam_otp = { version = "~> 0.8" } -gleam_stdlib = { version = "~> 0.33" } -pqueue = { version = "~> 2.0" } -simplifile = { version = "~> 1.0" } diff --git a/aoc2023/src/.gitignore b/aoc2023/src/.gitignore deleted file mode 100644 index bc13a69..0000000 --- a/aoc2023/src/.gitignore +++ /dev/null @@ -1 +0,0 @@ -aoc2023.gleam \ No newline at end of file diff --git a/aoc2023/src/aoc2023.gleam b/aoc2023/src/aoc2023.gleam deleted file mode 100644 index aab904d..0000000 --- a/aoc2023/src/aoc2023.gleam +++ /dev/null @@ -1,12 +0,0 @@ -import gleam/io -import gleam/bit_array - -const str = "abcdefgh -abcdefgh" - -pub fn main() { - let trim = 8 - let assert <<_:bytes-size(trim), "\n":utf8, rest:bytes>> = - bit_array.from_string(str) - io.debug(rest) -} diff --git a/aoc2023/src/day1/.gitignore b/aoc2023/src/day1/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day1/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day1/solve.gleam b/aoc2023/src/day1/solve.gleam deleted file mode 100644 index ed14bde..0000000 --- a/aoc2023/src/day1/solve.gleam +++ /dev/null @@ -1,64 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/list -import gleam/string -import gleam/regex.{type Match, Match} -import gleam/int - -pub fn part1(input: String) { - let assert Ok(re) = regex.from_string("[1-9]") - - input - |> string.split("\n") - |> list.fold( - 0, - fn(acc, s) { - let matches = regex.scan(s, with: re) - - let assert Ok(Match(content: first, ..)) = list.first(matches) - let assert Ok(Match(content: last, ..)) = list.last(matches) - let assert Ok(i) = int.parse(first <> last) - acc + i - }, - ) - |> string.inspect -} - -const substitutions = [ - #("one", "o1e"), - #("two", "t2o"), - #("three", "t3e"), - #("four", "4"), - #("five", "5e"), - #("six", "6"), - #("seven", "7n"), - #("eight", "e8t"), - #("nine", "n9e"), -] - -pub fn part2(input: String) { - list.fold( - over: substitutions, - from: input, - with: fn(acc, sub) { - let #(from, to) = sub - string.replace(in: acc, each: from, with: to) - }, - ) - |> part1 -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("1") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day10/.gitignore b/aoc2023/src/day10/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day10/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day10/solve.gleam b/aoc2023/src/day10/solve.gleam deleted file mode 100644 index c33634d..0000000 --- a/aoc2023/src/day10/solve.gleam +++ /dev/null @@ -1,177 +0,0 @@ -import adglent.{First, Second} -import gleam/bool -import gleam/dict.{type Dict} -import gleam/list -import gleam/int -import gleam/io -import gleam/set.{type Set} -import gleam/string - -type Posn { - Posn(row: Int, col: Int) -} - -fn add_posns(p1: Posn, p2: Posn) -> Posn { - Posn(p1.row + p2.row, p1.col + p2.col) -} - -type PipeGrid = - Dict(Posn, String) - -const north = Posn(-1, 0) - -const south = Posn(1, 0) - -const east = Posn(0, 1) - -const west = Posn(0, -1) - -const initial_directions = [ - #(north, ["|", "7", "F"]), - #(south, ["|", "J", "L"]), - #(east, ["-", "J", "7"]), - #(west, ["-", "F", "L"]), -] - -fn pipe_neighbors(pipe: String) -> List(Posn) { - case pipe { - "|" -> [north, south] - "-" -> [east, west] - "L" -> [north, east] - "F" -> [south, east] - "7" -> [south, west] - "J" -> [north, west] - _ -> panic as "bad pipe" - } -} - -fn make_grid(input: String) -> PipeGrid { - { - use r, row <- list.index_map(string.split(input, "\n")) - use c, col <- list.index_map(string.to_graphemes(row)) - #(Posn(r, c), col) - } - |> list.flatten - |> dict.from_list -} - -fn valid_start_direction(grid: PipeGrid, s: Posn) { - let assert [dir, ..] = { - use d <- list.filter_map(initial_directions) - let #(delta, valids) = d - let neighbor = add_posns(s, delta) - case dict.get(grid, neighbor) { - Ok(pipe) -> - case list.contains(valids, pipe) { - True -> Ok(neighbor) - False -> Error(Nil) - } - Error(_) -> Error(Nil) - } - } - dir -} - -fn to_next_pipe(current: Posn, grid: PipeGrid, acc: List(Posn)) { - let assert [prev, ..] = acc - let assert Ok(pipe) = dict.get(grid, current) - use <- bool.guard(pipe == "S", [current, ..acc]) - let assert [next] = { - pipe - |> pipe_neighbors - |> list.filter_map(fn(p) { - case add_posns(p, current) { - neighbor if neighbor == prev -> Error(Nil) - neighbor -> Ok(neighbor) - } - }) - } - to_next_pipe(next, grid, [current, ..acc]) -} - -pub fn part1(input: String) { - let grid = - input - |> make_grid - - let assert Ok(s) = - grid - |> dict.filter(fn(_, v) { v == "S" }) - |> dict.keys - |> list.first - - grid - |> valid_start_direction(s) - |> to_next_pipe(grid, [s]) - |> list.length - |> fn(i) { { { i - 1 } / 2 } } - |> string.inspect -} - -fn trace_ray(p: Posn, loop: Set(Posn), grid: PipeGrid) -> Bool { - use <- bool.guard(set.contains(loop, p), False) - int.is_odd(count_crossings(p, loop, grid, 0, "")) -} - -fn count_crossings( - p: Posn, - loop: Set(Posn), - grid: PipeGrid, - acc: Int, - corner: String, -) { - let maybe_cell = dict.get(grid, p) - use <- bool.guard(maybe_cell == Error(Nil), acc) - let assert Ok(cell) = maybe_cell - let next = add_posns(p, east) - case set.contains(loop, p) { - False -> count_crossings(next, loop, grid, acc, corner) - True -> - case corner, cell { - _, "|" -> count_crossings(next, loop, grid, acc + 1, corner) - _, "F" | _, "L" -> count_crossings(next, loop, grid, acc, cell) - "F", "J" | "L", "7" -> count_crossings(next, loop, grid, acc + 1, "") - "F", "7" | "L", "J" -> count_crossings(next, loop, grid, acc, "") - _, _ -> count_crossings(next, loop, grid, acc, corner) - } - } -} - -pub fn part2(input: String) { - let grid = - input - |> make_grid - - let assert Ok(s) = - grid - |> dict.filter(fn(_, v) { v == "S" }) - |> dict.keys - |> list.first - - let loop_pipes = - grid - |> valid_start_direction(s) - |> to_next_pipe(grid, [s]) - |> set.from_list - - grid - |> dict.keys - |> list.filter(trace_ray(_, loop_pipes, grid)) - |> list.length() - |> string.inspect -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("10") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day11/.gitignore b/aoc2023/src/day11/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day11/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day11/solve.gleam b/aoc2023/src/day11/solve.gleam deleted file mode 100644 index 35464a1..0000000 --- a/aoc2023/src/day11/solve.gleam +++ /dev/null @@ -1,84 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/int -import gleam/string -import gleam/list - -type Posn { - Posn(x: Int, y: Int) -} - -fn find_empty(grid: List(List(String))) { - use acc, row, r <- list.index_fold(grid, []) - case list.unique(row) { - ["."] -> [r, ..acc] - _ -> acc - } -} - -fn count_prior_empty_ranks(rank: Int, empty_ranks: List(Int)) -> Int { - empty_ranks - |> list.drop_while(fn(r_empty) { r_empty > rank }) - |> list.length -} - -fn parse_with_expansion(input: String, expansion: Int) -> List(Posn) { - let add = expansion - 1 - let grid = - input - |> string.split("\n") - |> list.map(string.to_graphemes) - - let empty_row_list = find_empty(grid) - let empty_col_list = find_empty(list.transpose(grid)) - - { - use r, row <- list.index_map(grid) - use acc, cell, c <- list.index_fold(over: row, from: []) - - let p = Posn(r, c) - let empty_r = count_prior_empty_ranks(r, empty_row_list) - let empty_c = count_prior_empty_ranks(c, empty_col_list) - case cell { - "#" -> [Posn(p.x + empty_r * add, p.y + empty_c * add), ..acc] - _empty -> acc - } - } - |> list.flatten() -} - -fn all_distances(stars: List(Posn)) -> Int { - use acc, pair <- list.fold(list.combination_pairs(stars), 0) - let #(s1, s2) = pair - acc + int.absolute_value(s1.x - s2.x) + int.absolute_value(s1.y - s2.y) -} - -fn find_distances(input: String, expand_by: Int) -> String { - input - |> parse_with_expansion(expand_by) - |> all_distances - |> string.inspect -} - -pub fn part1(input: String) { - find_distances(input, 2) -} - -pub fn part2(input: String) { - find_distances(input, 1_000_000) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("11") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day12/.gitignore b/aoc2023/src/day12/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day12/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day12/solve.gleam b/aoc2023/src/day12/solve.gleam deleted file mode 100644 index 06c7098..0000000 --- a/aoc2023/src/day12/solve.gleam +++ /dev/null @@ -1,91 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/string -import gleam/list -import gleam/int -import gleam/result -import utilities/memo.{type Cache} - -type ParserState = - #(String, List(Int), Int, Bool) - -fn parse_folds(input: String, folds: Int) { - let records = string.split(input, "\n") - use record <- list.map(records) - let assert Ok(#(template, sets_str)) = string.split_once(record, " ") - - let template = - template - |> list.repeat(folds) - |> list.intersperse("?") - |> string.concat - let sets = - sets_str - |> string.split(",") - |> list.map(int.parse) - |> result.values - |> list.repeat(folds) - |> list.flatten() - - #(template, sets) -} - -fn do_count( - template: String, - groups: List(Int), - left: Int, - gap: Bool, - cache: Cache(ParserState, Int), -) -> Int { - use <- memo.memoize(cache, #(template, groups, left, gap)) - case template, groups, left, gap { - "", [], 0, _ -> 1 - "?" <> t_rest, [g, ..g_rest], 0, False -> - do_count(t_rest, g_rest, g - 1, g == 1, cache) + { - do_count(t_rest, groups, 0, False, cache) - } - "?" <> t_rest, [], 0, False - | "?" <> t_rest, _, 0, True - | "." <> t_rest, _, 0, _ -> do_count(t_rest, groups, 0, False, cache) - "#" <> t_rest, [g, ..g_rest], 0, False -> - do_count(t_rest, g_rest, g - 1, g == 1, cache) - "?" <> t_rest, gs, l, False | "#" <> t_rest, gs, l, False -> - do_count(t_rest, gs, l - 1, l == 1, cache) - _, _, _, _ -> 0 - } -} - -fn count_solutions(acc: Int, condition: #(String, List(Int))) -> Int { - use cache: Cache(ParserState, Int) <- memo.create() - let #(template, groups) = condition - acc + do_count(template, groups, 0, False, cache) -} - -pub fn part1(input: String) { - input - |> parse_folds(1) - |> list.fold(0, count_solutions) - |> string.inspect -} - -pub fn part2(input: String) { - input - |> parse_folds(5) - |> list.fold(0, count_solutions) - |> string.inspect -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("12") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day13/.gitignore b/aoc2023/src/day13/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day13/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day13/solve.gleam b/aoc2023/src/day13/solve.gleam deleted file mode 100644 index 2b3fca2..0000000 --- a/aoc2023/src/day13/solve.gleam +++ /dev/null @@ -1,86 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/list -import gleam/string -import gleam/bool - -type SymmetryType { - Horizontal(Int) - Vertical(Int) -} - -fn is_symmetric(xss: List(List(a)), errs: Int) { - let assert [left, ..right] = xss - do_is_symmetric([left], right, errs) -} - -fn do_is_symmetric( - left: List(List(a)), - right: List(List(a)), - errors: Int, -) -> Result(Int, Nil) { - use <- bool.guard(list.is_empty(right), Error(Nil)) - let assert [h, ..t] = right - let found_errors = - list.zip(list.flatten(left), list.flatten(right)) - |> list.filter(fn(tup) { tup.1 != tup.0 }) - |> list.length - case found_errors == errors { - True -> Ok(list.length(left)) - False -> do_is_symmetric([h, ..left], t, errors) - } -} - -fn get_symmetry_type(xss: List(List(String)), errors: Int) { - case is_symmetric(xss, errors) { - Ok(n) -> Horizontal(n) - _ -> { - let assert Ok(n) = is_symmetric(list.transpose(xss), errors) - Vertical(n) - } - } -} - -fn summarize_notes(symmetries: List(SymmetryType)) { - use acc, note <- list.fold(symmetries, 0) - case note { - Horizontal(n) -> 100 * n - Vertical(n) -> n - } + acc -} - -fn solve(input: String, errors: Int) { - input - |> string.split("\n\n") - |> list.map(fn(strs) { - strs - |> string.split("\n") - |> list.map(string.to_graphemes) - |> get_symmetry_type(errors) - }) - |> summarize_notes - |> string.inspect -} - -pub fn part1(input: String) { - solve(input, 0) -} - -pub fn part2(input: String) { - solve(input, 1) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("13") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day14/.gitignore b/aoc2023/src/day14/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day14/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day14/solve.gleam b/aoc2023/src/day14/solve.gleam deleted file mode 100644 index 1ad1a18..0000000 --- a/aoc2023/src/day14/solve.gleam +++ /dev/null @@ -1,93 +0,0 @@ -import adglent.{First, Second} -import gleam/dict -import gleam/int -import gleam/io -import gleam/list -import gleam/order -import gleam/string - -fn parse(input) { - input - |> string.split("\n") - |> list.map(string.to_graphemes) - |> list.transpose() -} - -fn roll_boulders(strs: List(String)) { - { - use chunks <- list.map(list.chunk(strs, fn(c) { c == "O" || c == "." })) - list.sort(chunks, order.reverse(string.compare)) - } - |> list.flatten -} - -fn score(matrix) { - use acc, col <- list.fold(matrix, 0) - acc + { - use col_acc, char, n <- list.index_fold(list.reverse(col), 0) - case char { - "O" -> col_acc + n + 1 - _ -> col_acc - } - } -} - -pub fn part1(input: String) { - input - |> parse - |> list.map(roll_boulders) - |> score() - |> string.inspect -} - -fn rotate(matrix) { - matrix - |> list.map(list.reverse) - |> list.transpose -} - -fn spin(matrix) { - use acc, _ <- list.fold(list.range(1, 4), matrix) - acc - |> list.map(roll_boulders) - |> rotate -} - -fn spin_cycle(matrix) { - let cache = dict.new() - check_if_seen(matrix, cache, 1_000_000_000) -} - -fn check_if_seen(matrix, cache, count) { - case dict.get(cache, matrix) { - Error(Nil) -> - check_if_seen(spin(matrix), dict.insert(cache, matrix, count), count - 1) - Ok(n) -> { - let assert Ok(extra) = int.modulo(count, n - count) - list.fold(list.range(1, extra), matrix, fn(acc, _) { spin(acc) }) - |> score - } - } -} - -pub fn part2(input: String) { - input - |> parse - |> spin_cycle - |> string.inspect -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("14") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day15/.gitignore b/aoc2023/src/day15/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day15/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day15/solve.gleam b/aoc2023/src/day15/solve.gleam deleted file mode 100644 index a7d250c..0000000 --- a/aoc2023/src/day15/solve.gleam +++ /dev/null @@ -1,104 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/string -import gleam/list -import gleam/int -import gleam/dict.{type Dict} -import gleam/option.{None, Some} - -fn split(input: String) -> List(String) { - input - |> string.split(",") -} - -fn hash_algorithm(str: String) -> Int { - let codepoints = - str - |> string.to_utf_codepoints() - |> list.map(string.utf_codepoint_to_int) - use acc, c <- list.fold(codepoints, 0) - let assert Ok(acc) = int.modulo({ acc + c } * 17, 256) - acc -} - -pub fn part1(input: String) -> String { - input - |> split - |> list.fold(0, fn(acc, str) { acc + hash_algorithm(str) }) - |> string.inspect -} - -type Instruction { - Remove(label: String) - Insert(label: String, focal: Int) -} - -fn read_instruction(str: String) -> Instruction { - case string.split(str, "=") { - [label, focal_str] -> { - let assert Ok(focal) = int.parse(focal_str) - Insert(label, focal) - } - _ -> Remove(string.drop_right(str, 1)) - } -} - -fn parse_instructions(insts: List(String)) -> Dict(Int, List(#(String, Int))) { - use acc, inst <- list.fold(insts, dict.new()) - case read_instruction(inst) { - Remove(label) -> remove_lens(acc, label) - Insert(label, focal) -> insert_lens(acc, label, focal) - } -} - -fn remove_lens(boxes, label) { - use v <- dict.update(boxes, hash_algorithm(label)) - case v { - Some(lenses) -> - case list.key_pop(lenses, label) { - Ok(#(_, updated)) -> updated - Error(Nil) -> lenses - } - None -> [] - } -} - -fn insert_lens(boxes, label, focal) { - use v <- dict.update(boxes, hash_algorithm(label)) - case v { - Some(lenses) -> list.key_set(lenses, label, focal) - None -> [#(label, focal)] - } -} - -fn focusing_power(boxes: Dict(Int, List(#(String, Int)))) -> Int { - use acc, k, v <- dict.fold(boxes, 0) - let box_acc = { - use acc, lens, i <- list.index_fold(v, 0) - acc + lens.1 * { i + 1 } - } - acc + { k + 1 } * box_acc -} - -pub fn part2(input: String) -> String { - input - |> split - |> parse_instructions - |> focusing_power - |> string.inspect -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("15") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day16/.gitignore b/aoc2023/src/day16/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day16/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day16/solve.gleam b/aoc2023/src/day16/solve.gleam deleted file mode 100644 index 65ce36b..0000000 --- a/aoc2023/src/day16/solve.gleam +++ /dev/null @@ -1,119 +0,0 @@ -import adglent.{First, Second} -import gleam/bool -import gleam/dict.{type Dict} -import gleam/io -import gleam/list -import gleam/result -import gleam/set.{type Set} -import utilities/array2d.{type Posn, Posn} - -type Direction { - Up - Right - Down - Left -} - -type Light { - Light(posn: Posn, dir: Direction) -} - -fn move(l: Light) -> Light { - let Light(p, dir) = l - case dir { - Up -> Light(..l, posn: Posn(..p, r: p.r - 1)) - Down -> Light(..l, posn: Posn(..p, r: p.r + 1)) - Left -> Light(..l, posn: Posn(..p, c: p.c - 1)) - Right -> Light(..l, posn: Posn(..p, c: p.c + 1)) - } -} - -fn transform(l: Light, cell: Result(String, Nil)) -> List(Light) { - use <- bool.guard(result.is_error(cell), []) - let assert Ok(c) = cell - let Light(p, dir) = l - case dir, c { - // no change - _, "." | Up, "|" | Down, "|" | Left, "-" | Right, "-" -> [l] - // diagonal mirrors - Left, "/" -> [Light(p, Down)] - Down, "/" -> [Light(p, Left)] - Right, "/" -> [Light(p, Up)] - Up, "/" -> [Light(p, Right)] - Left, "\\" -> [Light(p, Up)] - Up, "\\" -> [Light(p, Left)] - Right, "\\" -> [Light(p, Down)] - Down, "\\" -> [Light(p, Right)] - // splitters - Left, "|" | Right, "|" -> [Light(p, Up), Light(p, Down)] - Up, "-" | Down, "-" -> [Light(p, Left), Light(p, Right)] - _, _ -> panic as "unrecognized cell type" - } -} - -fn energize(lights: List(Light), visited: Set(Light), grid: Dict(Posn, String)) { - let next_positions = - lights - |> list.flat_map(fn(l) { - let next = move(l) - transform(next, dict.get(grid, next.posn)) - }) - |> list.filter(fn(l) { !set.contains(visited, l) }) - let all_visited = set.union(set.from_list(next_positions), visited) - case visited == all_visited { - True -> - set.fold(visited, set.new(), fn(acc, l) { set.insert(acc, l.posn) }) - |> set.to_list - |> list.length - False -> energize(next_positions, all_visited, grid) - } -} - -pub fn part1(input: String) { - let grid = array2d.parse_grid(input) - - [Light(Posn(0, -1), Right)] - |> energize(set.new(), grid) -} - -pub fn part2(input: String) { - let grid = array2d.parse_grid(input) - - let Posn(rows, cols) = { - use acc, p <- list.fold(dict.keys(grid), Posn(0, 0)) - case acc.r + acc.c > p.r + p.c { - True -> acc - False -> p - } - } - - let all_starts = - list.concat([ - list.map(list.range(0, rows), fn(r) { Light(Posn(r, -1), Right) }), - list.map(list.range(0, rows), fn(r) { Light(Posn(r, cols + 1), Left) }), - list.map(list.range(0, cols), fn(c) { Light(Posn(-1, c), Down) }), - list.map(list.range(0, cols), fn(c) { Light(Posn(rows + 1, c), Up) }), - ]) - - use acc, p <- list.fold(all_starts, 0) - let energized = energize([p], set.new(), grid) - case acc > energized { - True -> acc - False -> energized - } -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("16") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day17/.gitignore b/aoc2023/src/day17/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day17/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day17/solve.gleam b/aoc2023/src/day17/solve.gleam deleted file mode 100644 index 7a01c4d..0000000 --- a/aoc2023/src/day17/solve.gleam +++ /dev/null @@ -1,143 +0,0 @@ -import adglent.{First, Second} -import gleam/bool -import gleam/dict.{type Dict} -import gleam/io -import gleam/list -import gleam/result -import gleam/string -import gleam/set.{type Set} -import utilities/array2d.{type Posn, Posn} -import utilities/prioqueue.{type PriorityQueue} - -type State { - State(posn: Posn, heatloss: Int, previous: Posn, history: List(Posn)) -} - -const deltas = [Posn(-1, 0), Posn(1, 0), Posn(0, -1), Posn(0, 1)] - -fn make_key(s: State) { - #(s.posn, same_dir(s)) -} - -fn same_dir(s: State) { - case s.history { - [] -> [] - [first, ..] as deltas -> - list.take_while(deltas, fn(d) { d == first }) - |> list.take(10) - } -} - -fn is_goal(s: State, min_run: Int, goal: Posn) { - goal == s.posn && list.length(same_dir(s)) >= min_run -} - -fn find_good_neighbors(max: Int, min: Int, s: State, grid) { - deltas - |> list.filter(eliminate_bad_neighbors(_, s, max, min, grid)) - |> list.map(make_state(_, s, grid)) -} - -fn eliminate_bad_neighbors(d: Posn, s: State, max, min, grid) { - let neighbor = array2d.add_posns(d, s.posn) - - use <- bool.guard( - neighbor == s.previous || !dict.has_key(grid, neighbor), - False, - ) - case same_dir(s), list.length(same_dir(s)) { - [prev, ..], l if l == max -> d != prev - _, 0 -> True - [prev, ..], l if l < min -> d == prev - _, _ -> True - } -} - -fn make_state(d: Posn, s: State, grid) { - let neighbor = array2d.add_posns(d, s.posn) - let assert Ok(heat_lost) = dict.get(grid, neighbor) - State( - posn: neighbor, - heatloss: s.heatloss + heat_lost, - previous: s.posn, - history: [d, ..s.history], - ) -} - -fn find_path( - grid: Dict(Posn, Int), - queue: PriorityQueue(State), - seen: Set(#(Posn, List(Posn))), - get_neighbors: fn(State) -> List(State), - is_goal: fn(State) -> Bool, -) { - let assert Ok(#(state, rest)) = prioqueue.pop(queue) - let key = - make_key( - state - |> io.debug, - ) - case set.contains(seen, key) { - True -> find_path(grid, rest, seen, get_neighbors, is_goal) - False -> { - let now_seen = set.insert(seen, key) - let neighbors = get_neighbors(state) - case list.find(neighbors, is_goal) { - Ok(final) -> final.heatloss - _err -> { - let now_queue = - list.fold(neighbors, rest, fn(acc, n) { - prioqueue.insert(acc, n, n.heatloss) - }) - find_path(grid, now_queue, now_seen, get_neighbors, is_goal) - } - } - } - } -} - -pub fn part1(input: String) { - let raw_grid = - input - |> array2d.to_list_of_lists - - let grid = array2d.to_2d_intarray(raw_grid) - - let rmax = list.length(raw_grid) - let assert Ok(cmax) = - raw_grid - |> list.first - |> result.map(list.length) - - let start = State(Posn(0, 0), 0, Posn(0, 0), []) - let goal = Posn(rmax, cmax) - - find_path( - grid, - prioqueue.insert(prioqueue.new(), start, 0), - set.new(), - find_good_neighbors(0, 3, _, grid), - is_goal(_, 1, goal), - ) - |> string.inspect -} - -pub fn part2(input: String) { - input - |> string.inspect -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("17") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day18/.gitignore b/aoc2023/src/day18/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day18/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day18/solve.gleam b/aoc2023/src/day18/solve.gleam deleted file mode 100644 index 2c000f9..0000000 --- a/aoc2023/src/day18/solve.gleam +++ /dev/null @@ -1,113 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/int -import gleam/list -import gleam/option.{Some} -import gleam/regex.{type Match, Match} -import gleam/string - -type Coord { - Coord(x: Int, y: Int) -} - -type Direction { - Up - Right - Down - Left -} - -type Dig { - Dig(dir: Direction, dist: Int) -} - -fn to_direction(c: String) { - case c { - "R" | "0" -> Right - "D" | "1" -> Down - "L" | "2" -> Left - "U" | "3" -> Up - _ -> panic - } -} - -fn parse_front(line: String) { - let assert Ok(re) = regex.from_string("(.) (.*) \\(.*\\)") - let assert [Match(submatches: [Some(dir), Some(dist)], ..)] = - regex.scan(with: re, content: line) - let assert Ok(n) = int.parse(dist) - Dig(to_direction(dir), n) -} - -fn parse_hex(line: String) { - let assert Ok(re) = regex.from_string("\\(#(.....)(.)\\)") - let assert [Match(submatches: [Some(dist), Some(dir)], ..)] = - regex.scan(with: re, content: line) - let assert Ok(n) = int.base_parse(dist, 16) - Dig(to_direction(dir), n) -} - -fn go(current: Coord, dig: Dig) { - case dig { - Dig(Up, n) -> Coord(current.x, current.y + n) - Dig(Right, n) -> Coord(current.x + n, current.y) - Dig(Down, n) -> Coord(current.x, current.y - n) - Dig(Left, n) -> Coord(current.x - n, current.y) - } -} - -fn double_triangle(c1: Coord, c2: Coord) { - { c1.x * c2.y } - { c2.x * c1.y } -} - -fn start_dig(digs: List(Dig)) { - do_next_dig(digs, Coord(0, 0), 0, 0) -} - -fn do_next_dig( - digs: List(Dig), - current: Coord, - area: Int, - perimeter: Int, -) -> Int { - case digs { - [] -> int.absolute_value(area) / 2 + { perimeter / 2 } + 1 - [dig, ..rest] -> { - let next = go(current, dig) - let area = area + double_triangle(current, next) - let perimeter = perimeter + dig.dist - do_next_dig(rest, next, area, perimeter) - } - } -} - -fn solve_with(input, f) { - input - |> string.split("\n") - |> list.map(f) - |> start_dig - |> string.inspect -} - -pub fn part1(input: String) { - solve_with(input, parse_front) -} - -pub fn part2(input: String) { - solve_with(input, parse_hex) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("18") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day19/.gitignore b/aoc2023/src/day19/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day19/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day19/solve.gleam b/aoc2023/src/day19/solve.gleam deleted file mode 100644 index 186e783..0000000 --- a/aoc2023/src/day19/solve.gleam +++ /dev/null @@ -1,255 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/string -import gleam/dict.{type Dict} -import gleam/order.{type Order, Gt, Lt} -import gleam/regex.{type Match, Match} -import gleam/list -import gleam/option.{Some} -import gleam/int - -type Rating { - XtremelyCool - Musical - Aerodynamic - Shiny -} - -type Part { - Part(x: Int, m: Int, a: Int, s: Int) -} - -type Action { - Accept - Reject - SendTo(String) -} - -type Rule { - If(rating: Rating, comparison: Order, threshold: Int, do: Action) - Just(do: Action) -} - -type Workflow = - Dict(String, List(Rule)) - -type Interval { - Interval(min: Int, max: Int) -} - -type PartRange { - PartRange(x: Interval, m: Interval, a: Interval, s: Interval) -} - -fn parse_workflow(input: String) -> Workflow { - let assert Ok(re) = regex.from_string("(.*){(.*)}") - - use acc, line <- list.fold(string.split(input, "\n"), dict.new()) - let assert [Match(submatches: [Some(name), Some(all_rules)], ..)] = - regex.scan(re, line) - let rules = - string.split(all_rules, ",") - |> parse_rules - dict.insert(acc, name, rules) -} - -fn parse_rules(rules: List(String)) -> List(Rule) { - let assert Ok(re_rule) = regex.from_string("(.*)(>|<)(.*):(.*)") - use rule <- list.map(rules) - case regex.scan(re_rule, rule) { - [Match(submatches: [Some(r), Some(c), Some(t), Some(i)], ..)] -> - If(to_rating(r), to_comp(c), to_val(t), to_instruction(i)) - _nomatch -> Just(to_instruction(rule)) - } -} - -fn to_instruction(rule: String) { - case rule { - "A" -> Accept - "R" -> Reject - name -> SendTo(name) - } -} - -fn to_rating(rating: String) { - case rating { - "x" -> XtremelyCool - "m" -> Musical - "a" -> Aerodynamic - _s -> Shiny - } -} - -fn get_rating(part: Part, rating: Rating) -> Int { - case rating { - XtremelyCool -> part.x - Musical -> part.m - Aerodynamic -> part.a - Shiny -> part.s - } -} - -fn to_comp(comp: String) { - case comp { - "<" -> Lt - _gt -> Gt - } -} - -fn to_val(val: String) { - let assert Ok(n) = int.parse(val) - n -} - -fn parse_parts(input: String) -> List(Part) { - let assert Ok(re) = regex.from_string("{x=(.*),m=(.*),a=(.*),s=(.*)}") - - use part <- list.map(string.split(input, "\n")) - let assert [Match(submatches: [Some(x), Some(m), Some(a), Some(s)], ..)] = - regex.scan(re, part) - Part(to_val(x), to_val(m), to_val(a), to_val(s)) -} - -fn start_evaluating_workflow(part: Part, workflow: Workflow) -> Int { - evaluate_workflow(part, "in", workflow) -} - -fn evaluate_workflow(part: Part, name: String, workflow: Workflow) -> Int { - let assert Ok(rules) = dict.get(workflow, name) - case evaluate_rules(part, rules) { - Accept -> part.x + part.m + part.a + part.s - Reject -> 0 - SendTo(name) -> evaluate_workflow(part, name, workflow) - } -} - -fn evaluate_rules(part: Part, rules: List(Rule)) -> Action { - case rules { - [] -> panic - [Just(do), ..] -> do - [If(rating, comparison, threshold, do), ..rest] -> - case int.compare(get_rating(part, rating), threshold) == comparison { - True -> do - False -> evaluate_rules(part, rest) - } - } -} - -pub fn part1(input: String) { - let assert Ok(#(workflows_str, parts_str)) = string.split_once(input, "\n\n") - - let workflows = parse_workflow(workflows_str) - let parts = parse_parts(parts_str) - - list.map(parts, start_evaluating_workflow(_, workflows)) - |> int.sum - |> string.inspect -} - -fn size(interval: Interval) { - interval.max - interval.min + 1 -} - -fn all_in_range(pr: PartRange) { - size(pr.x) * size(pr.m) * size(pr.a) * size(pr.s) -} - -fn get_partrange(pr: PartRange, rating: Rating) -> Interval { - case rating { - XtremelyCool -> pr.x - Musical -> pr.m - Aerodynamic -> pr.a - Shiny -> pr.s - } -} - -fn update_partrange(pr: PartRange, rating: Rating, i: Interval) -> PartRange { - case rating { - XtremelyCool -> PartRange(..pr, x: i) - Musical -> PartRange(..pr, m: i) - Aerodynamic -> PartRange(..pr, a: i) - Shiny -> PartRange(..pr, s: i) - } -} - -pub fn part2(input: String) { - let assert Ok(#(workflows_str, _)) = string.split_once(input, "\n\n") - - let workflow = parse_workflow(workflows_str) - let start = Interval(1, 4000) - - PartRange(start, start, start, start) - |> evaluate_workflow_on_range("in", workflow) - |> string.inspect -} - -fn evaluate_workflow_on_range( - pr: PartRange, - name: String, - workflow: Workflow, -) -> Int { - let assert Ok(rules) = dict.get(workflow, name) - evaluate_rules_on_range(pr, rules, workflow) -} - -fn evaluate_rules_on_range( - pr: PartRange, - rules: List(Rule), - workflow: Workflow, -) -> Int { - case rules { - [Just(Accept), ..] -> all_in_range(pr) - [Just(Reject), ..] -> 0 - [Just(SendTo(name)), ..] -> evaluate_workflow_on_range(pr, name, workflow) - [If(rating, comparison, t, action), ..rest] -> { - let mod_i = get_partrange(pr, rating) - case comparison { - Lt -> - split_range( - keep: update_partrange(pr, rating, Interval(mod_i.min, t - 1)), - and_do: action, - pass: update_partrange(pr, rating, Interval(t, mod_i.max)), - and_eval: rest, - with: workflow, - ) - _gt -> - split_range( - keep: update_partrange(pr, rating, Interval(t + 1, mod_i.max)), - and_do: action, - pass: update_partrange(pr, rating, Interval(mod_i.min, t)), - and_eval: rest, - with: workflow, - ) - } - } - [] -> panic - } -} - -fn split_range( - keep keep: PartRange, - and_do action: Action, - pass pass: PartRange, - and_eval rest: List(Rule), - with workflow: Workflow, -) -> Int { - int.add( - evaluate_rules_on_range(keep, [Just(action)], workflow), - evaluate_rules_on_range(pass, rest, workflow), - ) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("19") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day2/.gitignore b/aoc2023/src/day2/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day2/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day2/solve.gleam b/aoc2023/src/day2/solve.gleam deleted file mode 100644 index 38e62d7..0000000 --- a/aoc2023/src/day2/solve.gleam +++ /dev/null @@ -1,67 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/int -import gleam/string -import gleam/list - -pub type Game { - Game(red: Int, blue: Int, green: Int) -} - -fn parse(input: String) -> List(List(Game)) { - use line <- list.map(string.split(input, "\n")) - let assert [_, rounds] = string.split(line, on: ": ") - use match <- list.map(string.split(rounds, on: "; ")) - use acc, draw <- list.fold( - over: string.split(match, on: ", "), - from: Game(0, 0, 0), - ) - let assert Ok(#(n_str, color)) = string.split_once(draw, " ") - let assert Ok(n) = int.parse(n_str) - case color { - "red" -> Game(..acc, red: n) - "blue" -> Game(..acc, blue: n) - "green" -> Game(..acc, green: n) - _ -> panic as "unrecognized color" - } -} - -pub fn part1(input: String) { - use acc, game, i <- list.index_fold(parse(input), 0) - case list.any(game, fn(m) { m.red > 12 || m.green > 13 || m.blue > 14 }) { - False -> acc + i + 1 - True -> acc - } -} - -pub fn part2(input: String) { - { - use game <- list.map(parse(input)) - use acc, match <- list.fold(game, Game(0, 0, 0)) - let Game(red: red, green: green, blue: blue) = match - Game( - red: int.max(red, acc.red), - blue: int.max(blue, acc.blue), - green: int.max(green, acc.green), - ) - } - |> list.fold( - from: 0, - with: fn(acc, g: Game) { acc + g.red * g.blue * g.green }, - ) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("2") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day20/.gitignore b/aoc2023/src/day20/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day20/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day20/solve.gleam b/aoc2023/src/day20/solve.gleam deleted file mode 100644 index 9192dac..0000000 --- a/aoc2023/src/day20/solve.gleam +++ /dev/null @@ -1,251 +0,0 @@ -import adglent.{First, Second} -import gleam/bool -import gleam/dict.{type Dict} -import gleam/io -import gleam/iterator.{type Iterator, type Step, Next} -import gleam/list -import gleam/queue.{type Queue} -import gleam/set -import gleam/string - -type Node { - Broadcaster(children: List(String)) - Flipflop(children: List(String), state: Power) - Conjunction(children: List(String), state: Dict(String, TonePitch)) - Ground -} - -type Tone { - Tone(from: String, to: String, pitch: TonePitch) -} - -type Power { - On - Off -} - -type TonePitch { - Low - High -} - -type State { - State( - nodes: Dict(String, Node), - low: Int, - high: Int, - cycle: Int, - sentry_nodes: Dict(String, Int), - ) -} - -fn flip_power(p: Power) -> Power { - case p { - On -> Off - Off -> On - } -} - -fn flip_flop_pitch(p: Power) -> TonePitch { - case p { - Off -> High - On -> Low - } -} - -fn combinator_pitch(state) { - case list.unique(dict.values(state)) { - [High] -> Low - _ -> High - } -} - -fn get_children(node) { - case node { - Flipflop(children: cs, ..) -> cs - Conjunction(children: cs, ..) -> cs - Broadcaster(children: cs) -> cs - Ground -> [] - } -} - -fn parse_node(input: String) -> #(String, Node) { - let assert [full_name, children_str] = string.split(input, on: " -> ") - let children = string.split(children_str, on: ", ") - - case full_name { - "%" <> name -> #(name, Flipflop(children: children, state: Off)) - "&" <> name -> #(name, Conjunction(children: children, state: dict.new())) - "broadcaster" -> #("broadcaster", Broadcaster(children: children)) - name -> #(name, Ground) - } -} - -fn to_initial_state(nodes: List(#(String, Node))) -> Dict(String, Node) { - let node_dict = dict.from_list(nodes) - let node_names = dict.keys(node_dict) - - let node_dict = - node_dict - |> dict.values - |> list.map(get_children) - |> list.concat - |> set.from_list - |> set.drop(dict.keys(node_dict)) - |> set.to_list - |> list.fold(node_dict, fn(acc, n) { dict.insert(acc, n, Ground) }) - - use name, node <- dict.map_values(node_dict) - case node { - Conjunction(state: _, children: chs) -> - node_names - |> list.filter(fn(n) { - let assert Ok(node) = dict.get(node_dict, n) - list.contains(get_children(node), any: name) - }) - |> list.map(fn(n) { #(n, Low) }) - |> dict.from_list() - |> fn(dict) { Conjunction(state: dict, children: chs) } - other -> other - } -} - -fn add_to_queue(from, children, pitch, queue) { - use acc, c <- list.fold(children, queue) - queue.push_back(acc, Tone(from: from, to: c, pitch: pitch)) -} - -fn add_tones(state: State, nodes, pitch, n) { - case pitch { - Low -> - State(..state, nodes: nodes, low: state.low + n, cycle: state.cycle + 1) - High -> - State(..state, nodes: nodes, high: state.high + n, cycle: state.cycle + 1) - } -} - -fn press_button_once(initial: State, queue: Queue(Tone)) { - let State(nodes: nodes, ..) = initial - - use <- bool.guard(queue.is_empty(queue), initial) - let assert Ok(#(Tone(from_name, to_name, pitch), rest)) = - queue.pop_front(queue) - - let assert Ok(to_node) = dict.get(nodes, to_name) - case to_node { - Broadcaster(children) -> { - let new_state = - add_tones(initial, nodes, pitch, list.length(children) + 1) - - let new_queue = add_to_queue(to_name, children, pitch, rest) - press_button_once(new_state, new_queue) - } - - Conjunction(state: state, children: children) -> { - let new_state = - state - |> dict.insert(from_name, pitch) - - let updated_nodes = - Conjunction(state: new_state, children: children) - |> dict.insert(nodes, to_name, _) - - let pitch_out = combinator_pitch(new_state) - - let new_state = - add_tones(initial, updated_nodes, pitch_out, list.length(children)) - |> check_for_interesting_node(from_name, pitch_out) - - add_to_queue(to_name, children, pitch_out, rest) - |> press_button_once(new_state, _) - } - - Flipflop(..) if pitch == High -> - press_button_once(State(..initial, cycle: initial.cycle + 1), rest) - - Flipflop(state: state, children: children) -> { - let updated_nodes = - Flipflop(state: flip_power(state), children: children) - |> dict.insert(nodes, to_name, _) - - let pitch_out = flip_flop_pitch(state) - let new_state = - add_tones(initial, updated_nodes, pitch_out, list.length(children)) - - add_to_queue(to_name, children, flip_flop_pitch(state), rest) - |> press_button_once(new_state, _) - } - - Ground(..) -> - press_button_once(State(..initial, cycle: initial.cycle + 1), rest) - } -} - -pub fn part1(input: String) { - let initial_state = - input - |> string.split(on: "\n") - |> list.map(parse_node) - |> to_initial_state() - - iterator.iterate( - from: State(initial_state, 0, 0, 1, dict.new()), - with: press_button_once(_, queue.from_list([ - Tone("button", "broadcaster", Low), - ])), - ) - |> iterator.at(1000) - |> fn(s) { - let assert Ok(State(high: high, low: low, ..)) = s - high * low - } - |> string.inspect -} - -fn check_for_interesting_node(state, name, pitch_out) { - case name, pitch_out { - "rk", High | "cd", High | "zf", High | "qx", High -> - State( - ..state, - sentry_nodes: dict.insert(state.sentry_nodes, name, state.cycle), - ) - _, _ -> state - } -} - -pub fn part2(input: String) { - let initial_state = - input - |> string.split(on: "\n") - |> list.map(parse_node) - |> to_initial_state() - - iterator.iterate( - from: State(initial_state, 0, 0, 1, dict.new()), - with: press_button_once(_, queue.from_list([ - Tone("button", "broadcaster", Low), - ])), - ) - |> iterator.drop_while(fn(s) { dict.size(s.sentry_nodes) < 4 }) - |> iterator.step - |> fn(s: Step(State, Iterator(State))) { - let assert Next(goal, _rest) = s - goal.sentry_nodes - } - |> string.inspect -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("20") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day21/.gitignore b/aoc2023/src/day21/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day21/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day21/solve.gleam b/aoc2023/src/day21/solve.gleam deleted file mode 100644 index 4d5c246..0000000 --- a/aoc2023/src/day21/solve.gleam +++ /dev/null @@ -1,25 +0,0 @@ -import adglent.{First, Second} -import gleam/io - -pub fn part1(input: String) { - todo as "Implement solution to part 1" -} - -pub fn part2(input: String) { - todo as "Implement solution to part 2" -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("21") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day22/.gitignore b/aoc2023/src/day22/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day22/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day22/solve.gleam b/aoc2023/src/day22/solve.gleam deleted file mode 100644 index 7bf2fb4..0000000 --- a/aoc2023/src/day22/solve.gleam +++ /dev/null @@ -1,199 +0,0 @@ -import adglent.{First, Second} -import gleam/bool -import gleam/dict.{type Dict} -import gleam/int -import gleam/io -import gleam/list -import gleam/option.{None, Some} -import gleam/regex -import gleam/result -import gleam/set.{type Set} -import gleam/string - -type Point { - Point(x: Int, y: Int, z: Int) -} - -fn down_one(p: Point) -> Point { - Point(..p, z: p.z - 1) -} - -type Block { - Block(index: Int, from: Point, to: Point) -} - -fn compare_blocks(b1: Block, b2: Block) { - int.compare(b1.to.z, b2.to.z) -} - -type Space = - Dict(Point, Block) - -type AllBlocks = - Dict(Block, List(Point)) - -type BlockTree = - Dict(Int, Set(Int)) - -fn parse_block(index: Int, input: String) -> Block { - let assert Ok(re) = regex.from_string("(.*),(.*),(.*)~(.*),(.*),(.*)") - - let assert [scan] = regex.scan(with: re, content: input) - - let assert [x1, y1, z1, x2, y2, z2] = - scan.submatches - |> option.all - |> option.unwrap([]) - |> list.map(int.parse) - |> result.values - Block(index: index, from: Point(x1, y1, z1), to: Point(x2, y2, z2)) -} - -fn cross_section_at_level(b: Block, z: Int) -> List(Point) { - use x <- list.flat_map(list.range(b.from.x, b.to.x)) - use y <- list.map(list.range(b.from.y, b.to.y)) - Point(x, y, z) -} - -fn place_block(space: Space, b: Block, z: Int) -> Space { - let now_occupied = { - use x <- list.flat_map(list.range(b.from.x, b.to.x)) - use y <- list.flat_map(list.range(b.from.y, b.to.y)) - use z <- list.map(list.range(z, z + b.to.z - b.from.z)) - #(Point(x, y, z), b) - } - - dict.merge(space, dict.from_list(now_occupied)) -} - -fn find_lowest_level(space: Space, b: Block) -> Space { - do_find_lowest(space, b, b.from.z) -} - -fn do_find_lowest(space: Space, b: Block, z: Int) -> Space { - let is_intersecting = - list.any(cross_section_at_level(b, z), dict.has_key(space, _)) - - case z, is_intersecting { - 0, _ -> place_block(space, b, 1) - _, True -> place_block(space, b, z + 1) - _, False -> do_find_lowest(space, b, z - 1) - } -} - -fn to_block_positions(space: Space) -> AllBlocks { - use acc, point, index <- dict.fold(space, dict.new()) - use points <- dict.update(acc, index) - case points { - Some(ps) -> [point, ..ps] - None -> [point] - } -} - -fn above_blocks(blocks: AllBlocks) -> BlockTree { - use acc, block, points <- dict.fold(blocks, dict.new()) - use _ <- dict.update(acc, block.index) - { - use above_block, above_points <- dict.filter(blocks) - above_block.index != block.index - && list.any(above_points, fn(p) { list.contains(points, down_one(p)) }) - } - |> dict.keys - |> list.map(fn(b) { b.index }) - |> set.from_list -} - -fn below_blocks(blocktree: BlockTree) -> BlockTree { - use acc, block, _ <- dict.fold(blocktree, dict.new()) - use _ <- dict.update(acc, block) - { - use _, aboves <- dict.filter(blocktree) - set.contains(aboves, block) - } - |> dict.keys - |> set.from_list -} - -fn vulnerable_blocks(below_tree: BlockTree) -> List(Int) { - use block <- list.filter(dict.keys(below_tree)) - use bs <- list.any(dict.values(below_tree)) - !{ set.size(bs) == 0 } && { set.size(set.delete(bs, block)) == 0 } -} - -pub fn part1(input: String) { - let settled_blocks = - input - |> string.split("\n") - |> list.index_map(parse_block) - |> list.sort(compare_blocks) - |> list.fold(dict.new(), find_lowest_level) - - let block_positions = to_block_positions(settled_blocks) - let above_blocks = above_blocks(block_positions) - let below_blocks = below_blocks(above_blocks) - - let vulnerable_blocks = vulnerable_blocks(below_blocks) - - list.length(dict.keys(block_positions)) - list.length(vulnerable_blocks) -} - -fn all_falling_blocks(n: Int, above: BlockTree, below: BlockTree) { - let starting_set = set.insert(set.new(), n) - do_falling_blocks(starting_set, starting_set, above, below) -} - -fn do_falling_blocks( - fallen: Set(Int), - blocks: Set(Int), - above: BlockTree, - below: BlockTree, -) -> Int { - use <- bool.guard(set.size(blocks) == 0, set.size(fallen) - 1) - - let blocks_above = - { - use block <- list.flat_map(set.to_list(blocks)) - let assert Ok(supports) = dict.get(above, block) - use support <- list.filter(set.to_list(supports)) - let assert Ok(supportings) = dict.get(below, support) - use supporting <- list.all(set.to_list(supportings)) - set.contains(fallen, supporting) - } - |> set.from_list() - - set.union(fallen, blocks_above) - |> do_falling_blocks(blocks_above, above, below) -} - -pub fn part2(input: String) { - let settled_blocks = - input - |> string.split("\n") - |> list.index_map(parse_block) - |> list.sort(compare_blocks) - |> list.fold(dict.new(), find_lowest_level) - - let block_positions = to_block_positions(settled_blocks) - let above_blocks = above_blocks(block_positions) - let below_blocks = below_blocks(above_blocks) - - let vulnerable_blocks = vulnerable_blocks(below_blocks) - - use acc, b <- list.fold(vulnerable_blocks, 0) - acc + all_falling_blocks(b, above_blocks, below_blocks) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("22") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day23/.gitignore b/aoc2023/src/day23/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day23/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day23/solve.gleam b/aoc2023/src/day23/solve.gleam deleted file mode 100644 index e1fe638..0000000 --- a/aoc2023/src/day23/solve.gleam +++ /dev/null @@ -1,194 +0,0 @@ -import adglent.{First, Second} -import gleam/int -import gleam/io -import gleam/dict.{type Dict} -import gleam/list -import gleam/option.{type Option, None, Some} -import gleam/string -import gleam/set.{type Set} -import gleam/bool -import utilities/array2d.{type Array2D, type Posn, Posn} - -type Path { - Unknown - Straight - Junction -} - -type Route { - Route(to: Posn, distance: Int) -} - -fn append_to_key(v: Option(List(a)), new: a) -> List(a) { - case v { - None -> [new] - Some(xs) -> [new, ..xs] - } -} - -fn first_parse_path(c: String) -> Result(Path, Nil) { - case c { - "#" -> Error(Nil) - _ -> Ok(Unknown) - } -} - -fn junction_neighbors(p: Posn) -> List(Posn) { - [Posn(..p, r: p.r + 1), Posn(..p, c: p.c + 1)] -} - -fn mark_junctions(trails: Array2D(Path)) -> Array2D(Path) { - use trail, _ <- dict.map_values(trails) - - let valid_neighbors = - trail - |> array2d.ortho_neighbors - |> list.filter(dict.has_key(trails, _)) - - case list.length(valid_neighbors) { - 2 -> Straight - _ -> Junction - } -} - -fn start_walking_to_next_junction( - start: Posn, - next: Posn, - trails: Array2D(Path), -) { - let seen = - set.new() - |> set.insert(start) - |> set.insert(next) - walk_to_next_junction(start, next, 1, seen, trails) -} - -fn walk_to_next_junction( - start: Posn, - current: Posn, - length: Int, - seen: Set(Posn), - trails: Array2D(Path), -) -> #(Posn, Route) { - let assert [next] = - current - |> array2d.ortho_neighbors - |> list.filter(fn(n) { dict.has_key(trails, n) && !set.contains(seen, n) }) - - case dict.get(trails, next) { - Ok(Junction) -> #(start, Route(to: next, distance: length + 1)) - _ -> { - let seen = set.insert(seen, current) - walk_to_next_junction(start, next, { length + 1 }, seen, trails) - } - } -} - -fn find_routes(junctions, trails) { - use junction <- list.flat_map(junctions) - use neighbor <- list.filter_map(junction_neighbors(junction)) - case dict.has_key(trails, neighbor) { - True -> Ok(start_walking_to_next_junction(junction, neighbor, trails)) - False -> Error(Nil) - } -} - -fn generate_routes( - junctions: List(Posn), - trails: Array2D(Path), -) -> Dict(Posn, List(Route)) { - use acc, #(from, route) <- list.fold( - find_routes(junctions, trails), - dict.new(), - ) - dict.update(acc, from, append_to_key(_, route)) -} - -fn generate_2way_routes( - junctions: List(Posn), - trails: Array2D(Path), -) -> Dict(Posn, List(Route)) { - use acc, #(from, route) <- list.fold( - find_routes(junctions, trails), - dict.new(), - ) - acc - |> dict.update(from, append_to_key(_, route)) - |> dict.update(route.to, append_to_key(_, Route(from, route.distance))) -} - -fn dfs(routes, from, to) { - let seen = set.insert(set.new(), from) - do_dfs(routes, from, to, 0, seen) -} - -fn do_dfs( - routes: Dict(Posn, List(Route)), - from: Posn, - to: Posn, - acc: Int, - seen: Set(Posn), -) -> Int { - use <- bool.guard(to == from, acc) - - let assert Ok(all_routes) = dict.get(routes, from) - let neighbors = list.filter(all_routes, fn(r) { !set.contains(seen, r.to) }) - - case neighbors { - [] -> 0 - neighbors -> - list.fold(neighbors, acc, fn(inner_acc, n) { - let score = - do_dfs(routes, n.to, to, acc + n.distance, set.insert(seen, n.to)) - int.max(score, inner_acc) - }) - } -} - -fn solve_using( - input: String, - using: fn(List(Posn), Dict(Posn, Path)) -> Dict(Posn, List(Route)), -) -> Int { - let min_row = 0 - let max_row = list.length(string.split(input, "\n")) - 1 - - let trails = - input - |> array2d.parse_grid_using(first_parse_path) - |> mark_junctions - - let junctions = - trails - |> dict.filter(fn(_, v) { v == Junction }) - |> dict.keys - - let assert Ok(start) = list.find(junctions, fn(j) { j.r == min_row }) - let assert Ok(end) = list.find(junctions, fn(j) { j.r == max_row }) - - let routes = using(junctions, trails) - - dfs(routes, start, end) -} - -pub fn part1(input: String) { - solve_using(input, generate_routes) -} - -pub fn part2(input: String) { - solve_using(input, generate_2way_routes) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("23") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day3/.gitignore b/aoc2023/src/day3/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day3/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day3/solve.gleam b/aoc2023/src/day3/solve.gleam deleted file mode 100644 index ad975aa..0000000 --- a/aoc2023/src/day3/solve.gleam +++ /dev/null @@ -1,180 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/dict.{type Dict} -import gleam/string -import gleam/list -import gleam/int -import gleam/order.{type Order, Eq} - -type Coord { - Coord(x: Int, y: Int) -} - -type SymbolKind { - Gear - SomethingElse -} - -type Symbol { - Number(Int) - Symbol(SymbolKind) - Empty -} - -type Board = - Dict(Coord, Symbol) - -type Cell { - Cell(coord: Coord, symbol: Symbol) -} - -type Part { - Part(coords: List(Coord), part_number: Int) -} - -fn to_symbol(c: String) -> Symbol { - case int.parse(c), c { - Ok(n), _ -> Number(n) - _, "." -> Empty - _, "*" -> Symbol(Gear) - _, _ -> Symbol(SomethingElse) - } -} - -fn to_board(input: String) -> Board { - { - use y, r <- list.index_map(string.split(input, "\n")) - use x, c <- list.index_map(string.to_graphemes(r)) - #(Coord(x, y), to_symbol(c)) - } - |> list.flatten() - |> dict.from_list() -} - -fn cell_compare(a: Cell, b: Cell) -> Order { - case int.compare(a.coord.y, b.coord.y) { - Eq -> int.compare(a.coord.x, b.coord.x) - other -> other - } -} - -fn find_all_part_digits(b: Board) -> List(Cell) { - b - |> dict.filter(fn(_, v) { - case v { - Number(_) -> True - _ -> False - } - }) - |> dict.to_list() - |> list.map(fn(tup) { Cell(tup.0, tup.1) }) - |> list.sort(cell_compare) -} - -fn to_parts(cells: List(Cell)) -> List(Part) { - do_parts(cells, []) -} - -fn do_parts(cells: List(Cell), parts: List(Part)) -> List(Part) { - case cells { - [] -> parts - [Cell(next, Number(n)), ..t] -> { - case parts { - [] -> do_parts(t, [Part([next], n), ..parts]) - [Part([prev, ..] as coords, n0), ..rest_parts] -> - case { next.x - prev.x }, { next.y - prev.y } { - 1, 0 -> - do_parts(t, [Part([next, ..coords], n0 * 10 + n), ..rest_parts]) - _, _ -> do_parts(t, [Part([next], n), ..parts]) - } - _ -> panic - } - } - _ -> panic - } -} - -fn all_neighbors(c: Coord) -> List(Coord) { - use dx <- list.flat_map([-1, 0, 1]) - use dy <- list.filter_map([-1, 0, 1]) - case dx, dy { - 0, 0 -> Error(Nil) - _, _ -> Ok(Coord(c.x + dx, c.y + dy)) - } -} - -fn sum_valid_parts(acc: Int, part: Part, board: Board) -> Int { - let neighbors = - part.coords - |> list.flat_map(all_neighbors) - |> list.unique() - - let sym = [Ok(Symbol(Gear)), Ok(Symbol(SomethingElse))] - case list.any(neighbors, fn(c) { list.contains(sym, dict.get(board, c)) }) { - True -> acc + part.part_number - False -> acc - } -} - -pub fn part1(input: String) -> Int { - let board = to_board(input) - - board - |> find_all_part_digits - |> to_parts - |> list.fold(0, fn(acc, p) { sum_valid_parts(acc, p, board) }) -} - -fn to_part_with_neighbors(part: Part) -> Part { - part.coords - |> list.flat_map(all_neighbors) - |> list.unique - |> Part(part.part_number) -} - -fn find_part_numbers_near_gear(gear: Coord, parts: List(Part)) -> List(Int) { - use part <- list.filter_map(parts) - case list.contains(part.coords, gear) { - True -> Ok(part.part_number) - False -> Error(Nil) - } -} - -fn to_sum_of_gear_ratios(adjacent_parts: List(List(Int))) -> Int { - use acc, ps <- list.fold(adjacent_parts, 0) - case ps { - [p1, p2] -> acc + p1 * p2 - _ -> acc - } -} - -pub fn part2(input: String) -> Int { - let board = to_board(input) - - let parts = - board - |> find_all_part_digits - |> to_parts - |> list.map(to_part_with_neighbors) - - board - |> dict.filter(fn(_, v) { v == Symbol(Gear) }) - |> dict.keys - |> list.map(find_part_numbers_near_gear(_, parts)) - |> to_sum_of_gear_ratios -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("3") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day4/.gitignore b/aoc2023/src/day4/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day4/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day4/solve.gleam b/aoc2023/src/day4/solve.gleam deleted file mode 100644 index 34d6098..0000000 --- a/aoc2023/src/day4/solve.gleam +++ /dev/null @@ -1,98 +0,0 @@ -import adglent.{First, Second} -import gleam/bool -import gleam/dict.{type Dict} -import gleam/int -import gleam/io -import gleam/list -import gleam/option.{None, Some} -import gleam/result -import gleam/set.{type Set} -import gleam/string - -type Card { - Card(number: Int, winners: Int) -} - -fn numbers_to_set(str: String) -> Set(Int) { - str - |> string.split(" ") - |> list.map(int.parse) - |> result.values() - |> set.from_list() -} - -fn parse_card(card: String) -> Card { - let assert Ok(#("Card" <> n_str, rest)) = string.split_once(card, ": ") - let assert Ok(#(winning_str, has_str)) = string.split_once(rest, " | ") - let assert Ok(n) = int.parse(string.trim(n_str)) - - let winning = numbers_to_set(winning_str) - let has = numbers_to_set(has_str) - let winners = set.size(set.intersection(winning, has)) - - Card(number: n, winners: winners) -} - -fn win_points(n: Int) { - bool.guard(n < 2, n, fn() { 2 * win_points(n - 1) }) -} - -pub fn part1(input: String) { - use acc, c <- list.fold(string.split(input, "\n"), 0) - c - |> parse_card - |> fn(c: Card) { win_points(c.winners) } - |> int.add(acc) -} - -fn win_more_cards(cards: List(String), count: Dict(Int, Int)) { - case cards { - [] -> - count - |> dict.values - |> int.sum - [raw_card, ..rest] -> { - let card = parse_card(raw_card) - case card.winners { - 0 -> win_more_cards(rest, count) - n -> win_more_cards(rest, update_counts(n, card, count)) - } - } - } -} - -fn update_counts(n: Int, card: Card, count: Dict(Int, Int)) -> Dict(Int, Int) { - let assert Ok(bonus) = dict.get(count, card.number) - use acc, n <- list.fold(list.range(card.number + 1, card.number + n), count) - use c <- dict.update(acc, n) - case c { - Some(i) -> i + bonus - None -> panic as "won a card that doesn't exist in the card pile" - } -} - -pub fn part2(input: String) { - let cards = string.split(input, "\n") - - let count = - list.range(1, list.length(cards)) - |> list.map(fn(n) { #(n, 1) }) - |> dict.from_list() - - win_more_cards(cards, count) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("4") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day5/.gitignore b/aoc2023/src/day5/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day5/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day5/solve.gleam b/aoc2023/src/day5/solve.gleam deleted file mode 100644 index 58e2ae0..0000000 --- a/aoc2023/src/day5/solve.gleam +++ /dev/null @@ -1,167 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/string -import gleam/result -import gleam/list.{Continue, Stop} -import gleam/int -import gleam/function - -// Types ------------------------------------------------------------------------------------------- - -pub type Almanac { - Almanac(seeds: List(Int), mappers: List(Mapper)) -} - -pub type MappingRange { - MRange(start: Int, end: Int, offset: Int) -} - -pub type SeedRange { - SRange(start: Int, end: Int) -} - -type Mapper = - List(MappingRange) - -// Parsing ----------------------------------------------------------------------------------------- - -fn parse_input(input: String) { - let assert ["seeds: " <> raw_seeds, ..raw_mappers] = - string.split(input, on: "\n\n") - - let seeds = string_to_int_list(raw_seeds) - let mappers = - list.map( - raw_mappers, - function.compose(string.split(_, on: "\n"), parse_mapper), - ) - Almanac(seeds, mappers) -} - -fn string_to_int_list(str: String) { - str - |> string.split(on: " ") - |> list.map(int.parse) - |> result.values -} - -fn parse_mapper(strs: List(String)) -> Mapper { - let assert [_, ..raw_ranges] = strs - list.map(raw_ranges, parse_mrange) - |> list.sort(fn(a, b) { int.compare(a.start, b.start) }) -} - -fn parse_mrange(str: String) -> MappingRange { - let assert [destination, source, range_width] = string_to_int_list(str) - MRange(source, source + range_width - 1, destination - source) -} - -// Part 1 ------------------------------------------------------------------------------------------ - -pub fn part1(input: String) { - let Almanac(seeds, mappers) = parse_input(input) - - list.map(seeds, list.fold(over: mappers, from: _, with: correspond)) - |> list.reduce(int.min) - |> result.unwrap(0) - |> string.inspect -} - -fn correspond(n: Int, mapper: Mapper) { - use acc, mrange <- list.fold_until(over: mapper, from: n) - case mrange.start <= acc && acc <= mrange.end { - True -> Stop(acc + mrange.offset) - False -> Continue(acc) - } -} - -// Part 2 ------------------------------------------------------------------------------------------ - -pub fn part2(input: String) { - let Almanac(seeds, mappers) = parse_input(input) - - let assert [SRange(answer, _), ..] = - seeds - |> list.sized_chunk(into: 2) - |> list.map(fn(chunk) { - let assert [start, length] = chunk - [SRange(start, start + length - 1)] - |> remap_all_seed_ranges(mappers) - }) - |> list.flatten() - |> list.sort(fn(a, b) { int.compare(a.start, b.start) }) - - string.inspect(answer) -} - -fn remap_all_seed_ranges(srs: List(SeedRange), mappers: List(Mapper)) { - case mappers { - [] -> srs - [mapper, ..rest] -> - list.flat_map(srs, remap_range(_, mapper)) - |> remap_all_seed_ranges(rest) - } -} - -fn remap_range(r: SeedRange, mapper: Mapper) -> List(SeedRange) { - do_remap_range(r, mapper, []) -} - -fn transform_range(r: SeedRange, mapper: MappingRange) -> SeedRange { - SRange(r.start + mapper.offset, r.end + mapper.offset) -} - -fn do_remap_range(r: SeedRange, mapper: Mapper, acc: List(SeedRange)) { - case mapper { - // no more mappings -> no mapping covers this range - [] -> [r, ..acc] - // range is to the left of current mapping -> no mapping covers this range - [m, ..] if r.end < m.start -> [r, ..acc] - // range is to the right of current mapping -> move to next mapping - [m, ..ms] if r.start > m.end -> do_remap_range(r, ms, acc) - // range is fully inside mapping -> range is transformed - [m, ..] if r.start >= m.start && r.end <= m.end -> [ - transform_range(r, m), - ..acc - ] - // range overlaps start but not end -> left side not transformed, right side transformed - [m, ..] if r.start < m.start && r.end <= m.end -> [ - SRange(r.start, m.start - 1), - transform_range(SRange(m.start, r.end), m), - ..acc - ] - // range overlaps end but not start -> left side transformed, right side moves to next mapping - [m, ..ms] if r.start >= m.start && r.end > m.end -> - do_remap_range( - SRange(m.end + 1, r.end), - ms, - [transform_range(SRange(r.start, m.end), m), ..acc], - ) - // mapping is fully inside range -> left not transformed, middle transformed, right to next - [m, ..ms] -> - do_remap_range( - SRange(m.end + 1, r.end), - ms, - [ - SRange(r.start, m.start - 1), - transform_range(SRange(m.start, m.end), m), - ..acc - ], - ) - } -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("5") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day6/.gitignore b/aoc2023/src/day6/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day6/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day6/solve.gleam b/aoc2023/src/day6/solve.gleam deleted file mode 100644 index 88044c4..0000000 --- a/aoc2023/src/day6/solve.gleam +++ /dev/null @@ -1,85 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/string -import gleam/int -import gleam/list -import gleam/result - -type Race { - Race(time: Int, distance: Int) -} - -fn parse_with_bad_kerning(input: String) { - input - |> string.split("\n") - |> list.map(fn(str) { - str - |> string.split(" ") - |> list.map(int.parse) - |> result.values - }) - |> list.transpose - |> list.map(fn(ns) { - let assert [t, d] = ns - Race(t, d) - }) -} - -fn find_bound(race: Race, button_time: Int, step: Int) { - let travel_time = race.time - button_time - case button_time * travel_time > race.distance { - True -> button_time - False -> find_bound(race, button_time + step, step) - } -} - -fn lower_bound(race: Race) { - find_bound(race, 1, 1) -} - -fn upper_bound(race: Race) { - find_bound(race, race.time, -1) -} - -pub fn part1(input: String) { - { - use acc, race <- list.fold(parse_with_bad_kerning(input), 1) - acc * { upper_bound(race) - lower_bound(race) + 1 } - } - |> string.inspect -} - -fn parse_properly(input: String) { - input - |> string.replace(" ", "") - |> string.split("\n") - |> list.flat_map(string.split(_, ":")) - |> list.map(int.parse) - |> result.values -} - -pub fn part2(input: String) { - let assert [time, distance] = - input - |> parse_properly - - let race = Race(time, distance) - - upper_bound(race) - lower_bound(race) + 1 - |> string.inspect -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("6") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day7/.gitignore b/aoc2023/src/day7/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day7/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day7/solve.gleam b/aoc2023/src/day7/solve.gleam deleted file mode 100644 index 4454883..0000000 --- a/aoc2023/src/day7/solve.gleam +++ /dev/null @@ -1,140 +0,0 @@ -import adglent.{First, Second} -import gleam/bool -import gleam/function -import gleam/int -import gleam/io -import gleam/list -import gleam/order.{type Order, Eq, Lt} -import gleam/string - -// Types ------------------------------------------------------------------------------------------- - -type Hand { - Hand(cards: List(Int), wager: Int) -} - -// Common functions -------------------------------------------------------------------------------- - -fn parse_hand(str: String) -> Hand { - let assert [cards, wager] = string.split(str, " ") - let cards = - string.to_graphemes(cards) - |> list.map(card_rank) - let assert Ok(wager) = int.parse(wager) - - Hand(cards, wager) -} - -fn classify_hand(hand: Hand) -> Int { - case list.length(list.unique(hand.cards)), card_counts(hand) { - 1, _ -> 8 - 2, [1, 4] -> 7 - 2, [2, 3] -> 6 - 3, [1, 1, 3] -> 5 - 3, [1, 2, 2] -> 4 - 4, _ -> 3 - 5, _ -> 2 - _, _ -> 1 - } -} - -fn card_counts(hand: Hand) { - hand.cards - |> list.sort(int.compare) - |> list.chunk(function.identity) - |> list.map(list.length) - |> list.sort(int.compare) -} - -fn card_rank(card: String) -> Int { - case int.parse(card), card { - Ok(n), _ -> n - _, "A" -> 14 - _, "K" -> 13 - _, "Q" -> 12 - _, "J" -> 11 - _, "T" -> 10 - _, _ -> 1 - } -} - -fn compare_hands(hand1: Hand, hand2: Hand, using: fn(Hand) -> Int) -> Order { - case int.compare(using(hand1), using(hand2)) { - Eq -> compare_top_card(hand1.cards, hand2.cards) - other -> other - } -} - -fn compare_top_card(cards1: List(Int), cards2: List(Int)) -> Order { - use <- bool.guard(cards1 == [] || cards2 == [], Eq) - let assert [c1, ..rest1] = cards1 - let assert [c2, ..rest2] = cards2 - case int.compare(c1, c2) { - Eq -> compare_top_card(rest1, rest2) - other -> other - } -} - -fn part(input: String, comparator: fn(Hand, Hand) -> Order) { - input - |> string.split("\n") - |> list.map(parse_hand) - |> list.sort(comparator) - |> list.index_map(fn(i, h) { { i + 1 } * h.wager }) - |> int.sum - |> string.inspect -} - -// Part 1 ------------------------------------------------------------------------------------------ - -pub fn part1(input: String) { - part(input, compare_without_wilds) -} - -fn compare_without_wilds(hand1: Hand, hand2: Hand) { - compare_hands(hand1, hand2, classify_hand) -} - -// Part 2 ------------------------------------------------------------------------------------------ - -pub fn part2(input: String) { - part(string.replace(input, "J", "*"), compare_hands_considering_jokers) -} - -fn find_best_joker_substitution(hand: Hand) { - use acc, card <- list.fold(list.range(2, 14), Hand([], 0)) - let subbed_cards = { - use c <- list.map(hand.cards) - case c { - 1 -> card - other -> other - } - } - let subbed_hand = Hand(..hand, cards: subbed_cards) - case compare_hands(acc, subbed_hand, classify_hand) { - Lt -> subbed_hand - _ -> acc - } -} - -fn compare_hands_considering_jokers(hand1: Hand, hand2: Hand) -> Order { - use hand <- compare_hands(hand1, hand2) - hand - |> find_best_joker_substitution - |> classify_hand -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("7") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day8/.gitignore b/aoc2023/src/day8/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day8/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day8/solve.gleam b/aoc2023/src/day8/solve.gleam deleted file mode 100644 index 6b36e2d..0000000 --- a/aoc2023/src/day8/solve.gleam +++ /dev/null @@ -1,91 +0,0 @@ -import adglent.{First, Second} -import gleam/bool -import gleam/dict.{type Dict} -import gleam/io -import gleam/iterator.{type Iterator, Next} -import gleam/list -import gleam/option.{Some} -import gleam/string -import gleam/regex.{type Match, Match} -import gleam_community/maths/arithmetics - -type Paths { - Paths(to_left: String, to_right: String) -} - -type Maze = - Dict(String, Paths) - -fn parse(input: String) -> #(Iterator(String), Dict(String, Paths)) { - let assert [directions_str, maze_str] = string.split(input, "\n\n") - - let directions = - directions_str - |> string.to_graphemes() - |> iterator.from_list - |> iterator.cycle - - let assert Ok(re) = regex.from_string("(...) = \\((...), (...)\\)") - let maze = - maze_str - |> string.split("\n") - |> list.map(fn(str) { - let assert [Match(submatches: [Some(name), Some(left), Some(right)], ..)] = - regex.scan(re, str) - #(name, Paths(left, right)) - }) - |> dict.from_list - - #(directions, maze) -} - -fn to_next_step( - current: String, - stop_at: String, - count: Int, - directions: Iterator(String), - maze: Maze, -) -> Int { - use <- bool.guard(string.ends_with(current, stop_at), count) - let assert Next(next_direction, rest_directions) = iterator.step(directions) - let assert Ok(paths) = dict.get(maze, current) - case next_direction { - "L" -> paths.to_left - "R" -> paths.to_right - _ -> panic as "bad direction" - } - |> to_next_step(stop_at, count + 1, rest_directions, maze) -} - -pub fn part1(input: String) -> Int { - let #(directions, maze) = parse(input) - - to_next_step("AAA", "ZZZ", 0, directions, maze) -} - -pub fn part2(input: String) -> Int { - let #(directions, maze) = parse(input) - - use acc, name <- list.fold(dict.keys(maze), 1) - case string.ends_with(name, "A") { - False -> acc - True -> - to_next_step(name, "Z", 0, directions, maze) - |> arithmetics.lcm(acc) - } -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("8") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/day9/.gitignore b/aoc2023/src/day9/.gitignore deleted file mode 100644 index ae40cea..0000000 --- a/aoc2023/src/day9/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input.txt \ No newline at end of file diff --git a/aoc2023/src/day9/solve.gleam b/aoc2023/src/day9/solve.gleam deleted file mode 100644 index a2cc7ae..0000000 --- a/aoc2023/src/day9/solve.gleam +++ /dev/null @@ -1,70 +0,0 @@ -import adglent.{First, Second} -import gleam/io -import gleam/list -import gleam/string -import gleam/int - -fn parse(input: String, backwards backwards: Bool) -> List(List(Int)) { - use line <- list.map(string.split(input, "\n")) - use n_str <- list.map(maybe_backwards(string.split(line, " "), backwards)) - let assert Ok(n) = int.parse(n_str) - n -} - -fn maybe_backwards(xs: List(a), backwards: Bool) -> List(a) { - case backwards { - False -> list.reverse(xs) - True -> xs - } -} - -fn is_constant(ns: List(Int)) -> Bool { - case list.unique(ns) { - [_] -> True - _ -> False - } -} - -fn take_derivative(ns: List(Int)) -> List(Int) { - ns - |> list.window_by_2 - |> list.map(fn(tup) { tup.0 - tup.1 }) -} - -fn extrapolate(ns: List(Int)) { - case is_constant(ns), ns { - True, [n, ..] -> n - False, [n, ..] -> n + extrapolate(take_derivative(ns)) - _, _ -> panic as "list empty when it shouldn't be" - } -} - -fn part(input: String, backwards backwards: Bool) { - input - |> parse(backwards: backwards) - |> list.fold(0, fn(acc, ns) { extrapolate(ns) + acc }) - |> string.inspect -} - -pub fn part1(input: String) { - part(input, backwards: False) -} - -pub fn part2(input: String) { - part(input, backwards: True) -} - -pub fn main() { - let assert Ok(part) = adglent.get_part() - let assert Ok(input) = adglent.get_input("9") - case part { - First -> - part1(input) - |> adglent.inspect - |> io.println - Second -> - part2(input) - |> adglent.inspect - |> io.println - } -} diff --git a/aoc2023/src/utilities/array2d.gleam b/aoc2023/src/utilities/array2d.gleam deleted file mode 100644 index 8538129..0000000 --- a/aoc2023/src/utilities/array2d.gleam +++ /dev/null @@ -1,74 +0,0 @@ -import gleam/list -import gleam/dict.{type Dict} -import gleam/string -import gleam/int -import gleam/result - -pub type Posn { - Posn(r: Int, c: Int) -} - -pub type Array2D(a) = - Dict(Posn, a) - -pub fn add_posns(p1: Posn, p2: Posn) -> Posn { - case p1, p2 { - Posn(r1, c1), Posn(r2, c2) -> Posn(r1 + r2, c1 + c2) - } -} - -pub fn ortho_neighbors(p: Posn) -> List(Posn) { - let Posn(r, c) = p - [Posn(r + 1, c), Posn(r - 1, c), Posn(r, c + 1), Posn(r, c - 1)] -} - -pub fn to_2d_array(xss: List(List(a))) -> Array2D(a) { - to_2d_array_using(xss, fn(x) { Ok(x) }) -} - -pub fn to_2d_array_using( - xss: List(List(a)), - f: fn(a) -> Result(b, Nil), -) -> Array2D(b) { - { - use r, row <- list.index_map(xss) - use c, cell <- list.index_map(row) - case f(cell) { - Ok(contents) -> Ok(#(Posn(r, c), contents)) - Error(Nil) -> Error(Nil) - } - } - |> list.flatten - |> result.values - |> dict.from_list -} - -pub fn to_2d_intarray(xss: List(List(String))) -> Array2D(Int) { - { - use r, row <- list.index_map(xss) - use c, cell <- list.index_map(row) - let assert Ok(n) = int.parse(cell) - #(Posn(r, c), n) - } - |> list.flatten - |> dict.from_list -} - -pub fn to_list_of_lists(str: String) -> List(List(String)) { - str - |> string.split("\n") - |> list.map(string.to_graphemes) -} - -pub fn parse_grid(str: String) -> Array2D(String) { - parse_grid_using(str, fn(x) { Ok(x) }) -} - -pub fn parse_grid_using( - str: String, - f: fn(String) -> Result(a, Nil), -) -> Array2D(a) { - str - |> to_list_of_lists - |> to_2d_array_using(f) -} diff --git a/aoc2023/src/utilities/memo.gleam b/aoc2023/src/utilities/memo.gleam deleted file mode 100644 index b06d8fd..0000000 --- a/aoc2023/src/utilities/memo.gleam +++ /dev/null @@ -1,57 +0,0 @@ -import gleam/dict.{type Dict} -import gleam/otp/actor.{type Next, Continue, Stop} -import gleam/erlang/process.{type Subject, Normal} -import gleam/option.{None} - -const timeout = 1000 - -type Message(k, v) { - Shutdown - Get(key: k, client: Subject(Result(v, Nil))) - Set(key: k, value: v) -} - -type Server(k, v) = - Subject(Message(k, v)) - -pub opaque type Cache(k, v) { - Cache(server: Server(k, v)) -} - -fn handle_message( - message: Message(k, v), - dict: Dict(k, v), -) -> Next(Message(k, v), Dict(k, v)) { - case message { - Shutdown -> Stop(Normal) - Get(key, client) -> { - process.send(client, dict.get(dict, key)) - Continue(dict, None) - } - Set(key, value) -> Continue(dict.insert(dict, key, value), None) - } -} - -pub fn create(apply fun: fn(Cache(k, v)) -> t) -> t { - let assert Ok(server) = actor.start(dict.new(), handle_message) - let result = fun(Cache(server)) - process.send(server, Shutdown) - result -} - -pub fn set(in cache: Cache(k, v), for key: k, insert value: v) -> Nil { - process.send(cache.server, Set(key, value)) -} - -pub fn get(from cache: Cache(k, v), fetch key: k) -> Result(v, Nil) { - process.call(cache.server, fn(c) { Get(key, c) }, timeout) -} - -pub fn memoize(with cache: Cache(k, v), this key: k, apply fun: fn() -> v) -> v { - let result = case get(from: cache, fetch: key) { - Ok(value) -> value - Error(Nil) -> fun() - } - set(in: cache, for: key, insert: result) - result -} diff --git a/aoc2023/src/utilities/prioqueue.gleam b/aoc2023/src/utilities/prioqueue.gleam deleted file mode 100644 index 640748b..0000000 --- a/aoc2023/src/utilities/prioqueue.gleam +++ /dev/null @@ -1,62 +0,0 @@ -//adapted from https://github.com/byronanderson/adventofcode2021/blob/main/gleam_advent/src/priority_queue.gleam - -import gleam/dict.{type Dict} - -type Ref - -@external(erlang, "erlang", "make_ref") -fn make_ref() -> Ref - -type PQueue(a) - -pub opaque type PriorityQueue(a) { - PriorityQueue(queue: PQueue(#(a, Ref)), refs: Dict(a, Ref)) -} - -type OutResult(a) { - Empty - Value(a, Int) -} - -@external(erlang, "pqueue2", "new") -fn new_() -> PQueue(a) - -@external(erlang, "pqueue2", "in") -fn insert_(item: a, prio: Int, queue: PQueue(a)) -> PQueue(a) - -@external(erlang, "pqueue2", "pout") -fn pop_(queue: PQueue(a)) -> #(OutResult(a), PQueue(a)) - -pub fn new() -> PriorityQueue(a) { - PriorityQueue(queue: new_(), refs: dict.new()) -} - -pub fn insert( - queue: PriorityQueue(a), - value: a, - priority: Int, -) -> PriorityQueue(a) { - let ref = make_ref() - - let refs = - queue.refs - |> dict.insert(value, ref) - - PriorityQueue(refs: refs, queue: insert_(#(value, ref), priority, queue.queue), - ) -} - -pub fn pop(queue: PriorityQueue(a)) -> Result(#(a, PriorityQueue(a)), Nil) { - case pop_(queue.queue) { - #(Value(#(value, ref), _priority), pqueue) -> { - let assert Ok(recently_enqueued_ref) = dict.get(queue.refs, value) - case recently_enqueued_ref == ref { - True -> Ok(#(value, PriorityQueue(refs: queue.refs, queue: pqueue))) - False -> pop(PriorityQueue(refs: queue.refs, queue: pqueue)) - } - } - #(Empty, _pqueue) -> { - Error(Nil) - } - } -} diff --git a/aoc2023/test/aoc2023_test.gleam b/aoc2023/test/aoc2023_test.gleam deleted file mode 100644 index 2b696a4..0000000 --- a/aoc2023/test/aoc2023_test.gleam +++ /dev/null @@ -1,5 +0,0 @@ -import showtime - -pub fn main() { - showtime.main() -} diff --git a/aoc2023/test/day1/day1_test.gleam b/aoc2023/test/day1/day1_test.gleam deleted file mode 100644 index 374653c..0000000 --- a/aoc2023/test/day1/day1_test.gleam +++ /dev/null @@ -1,57 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day1/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "1abc2 -pqr3stu8vwx -a1b2c3d4e5f -treb7uchet", - "142", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "two1nine -eightwothree -abcone2threexyz -xtwone3four -4nineeightseven2 -zoneight234 -7pqrstsixteen", - "281", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day10/day10_test.gleam b/aoc2023/test/day10/day10_test.gleam deleted file mode 100644 index be9d82e..0000000 --- a/aoc2023/test/day10/day10_test.gleam +++ /dev/null @@ -1,60 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day10/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "7-F7- -.FJ|7 -SJLL7 -|F--J -LJ.LJ", - "8", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "........... -.S-------7. -.|F-----7|. -.||OOOOO||. -.||OOOOO||. -.|L-7OF-J|. -.|II|O|II|. -.L--JOL--J. -.....O.....", - "4", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day11/day11_test.gleam b/aoc2023/test/day11/day11_test.gleam deleted file mode 100644 index 8bb8c06..0000000 --- a/aoc2023/test/day11/day11_test.gleam +++ /dev/null @@ -1,66 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day11/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "...#...... -.......#.. -#......... -.......... -......#... -.#........ -.........# -.......... -.......#.. -#...#.....", - "374", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "...#...... -.......#.. -#......... -.......... -......#... -.#........ -.........# -.......... -.......#.. -#...#.....", - "8410", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day12/day12_test.gleam b/aoc2023/test/day12/day12_test.gleam deleted file mode 100644 index 3daf0e9..0000000 --- a/aoc2023/test/day12/day12_test.gleam +++ /dev/null @@ -1,48 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day12/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "???.### 1,1,3 -.??..??...?##. 1,1,3 -?#?#?#?#?#?#?#? 1,3,1,6 -????.#...#... 4,1,1 -????.######..#####. 1,6,5 -?###???????? 3,2,1", - "21", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day13/day13_test.gleam b/aoc2023/test/day13/day13_test.gleam deleted file mode 100644 index 7c65bed..0000000 --- a/aoc2023/test/day13/day13_test.gleam +++ /dev/null @@ -1,76 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day13/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "#.##..##. -..#.##.#. -##......# -##......# -..#.##.#. -..##..##. -#.#.##.#. - -#...##..# -#....#..# -..##..### -#####.##. -#####.##. -..##..### -#....#..#", - "405", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "#.##..##. -..#.##.#. -##......# -##......# -..#.##.#. -..##..##. -#.#.##.#. - -#...##..# -#....#..# -..##..### -#####.##. -#####.##. -..##..### -#....#..#", - "400", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day14/day14_test.gleam b/aoc2023/test/day14/day14_test.gleam deleted file mode 100644 index 8efa74e..0000000 --- a/aoc2023/test/day14/day14_test.gleam +++ /dev/null @@ -1,66 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day14/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "O....#.... -O.OO#....# -.....##... -OO.#O....O -.O.....O#. -O.#..O.#.# -..O..#O..O -.......O.. -#....###.. -#OO..#....", - "136", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "O....#.... -O.OO#....# -.....##... -OO.#O....O -.O.....O#. -O.#..O.#.# -..O..#O..O -.......O.. -#....###.. -#OO..#....", - "64", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day15/day15_test.gleam b/aoc2023/test/day15/day15_test.gleam deleted file mode 100644 index 0ecaecc..0000000 --- a/aoc2023/test/day15/day15_test.gleam +++ /dev/null @@ -1,42 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day15/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example("rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7", "1320"), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example("rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7", "145"), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day16/day16_test.gleam b/aoc2023/test/day16/day16_test.gleam deleted file mode 100644 index 036504e..0000000 --- a/aoc2023/test/day16/day16_test.gleam +++ /dev/null @@ -1,66 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day16/solve - -type Problem1AnswerType = - Int - -type Problem2AnswerType = - Int - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - ".|...\\.... -|.-.\\..... -.....|-... -........|. -.......... -.........\\ -..../.\\\\.. -.-.-/..|.. -.|....-|.\\ -..//.|....", - 46, - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - ".|...\\.... -|.-.\\..... -.....|-... -........|. -.......... -.........\\ -..../.\\\\.. -.-.-/..|.. -.|....-|.\\ -..//.|....", - 51, - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day17/day17_test.gleam b/aoc2023/test/day17/day17_test.gleam deleted file mode 100644 index c1ebd22..0000000 --- a/aoc2023/test/day17/day17_test.gleam +++ /dev/null @@ -1,55 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day17/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "2413432311323 -3215453535623 -3255245654254 -3446585845452 -4546657867536 -1438598798454 -4457876987766 -3637877979653 -4654967986887 -4564679986453 -1224686865563 -2546548887735 -4322674655533", - "102", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day18/day18_test.gleam b/aoc2023/test/day18/day18_test.gleam deleted file mode 100644 index 7b510c8..0000000 --- a/aoc2023/test/day18/day18_test.gleam +++ /dev/null @@ -1,74 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day18/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "R 6 (#70c710) -D 5 (#0dc571) -L 2 (#5713f0) -D 2 (#d2c081) -R 2 (#59c680) -D 2 (#411b91) -L 5 (#8ceee2) -U 2 (#caa173) -L 1 (#1b58a2) -U 2 (#caa171) -R 2 (#7807d2) -U 3 (#a77fa3) -L 2 (#015232) -U 2 (#7a21e3)", - "62", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "R 6 (#70c710) -D 5 (#0dc571) -L 2 (#5713f0) -D 2 (#d2c081) -R 2 (#59c680) -D 2 (#411b91) -L 5 (#8ceee2) -U 2 (#caa173) -L 1 (#1b58a2) -U 2 (#caa171) -R 2 (#7807d2) -U 3 (#a77fa3) -L 2 (#015232) -U 2 (#7a21e3)", - "952408144115", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day19/day19_test.gleam b/aoc2023/test/day19/day19_test.gleam deleted file mode 100644 index c911de5..0000000 --- a/aoc2023/test/day19/day19_test.gleam +++ /dev/null @@ -1,80 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day19/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "px{a<2006:qkq,m>2090:A,rfg} -pv{a>1716:R,A} -lnx{m>1548:A,A} -rfg{s<537:gd,x>2440:R,A} -qs{s>3448:A,lnx} -qkq{x<1416:A,crn} -crn{x>2662:A,R} -in{s<1351:px,qqz} -qqz{s>2770:qs,m<1801:hdj,R} -gd{a>3333:R,R} -hdj{m>838:A,pv} - -{x=787,m=2655,a=1222,s=2876} -{x=1679,m=44,a=2067,s=496} -{x=2036,m=264,a=79,s=2244} -{x=2461,m=1339,a=466,s=291} -{x=2127,m=1623,a=2188,s=1013}", - "19114", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "px{a<2006:qkq,m>2090:A,rfg} -pv{a>1716:R,A} -lnx{m>1548:A,A} -rfg{s<537:gd,x>2440:R,A} -qs{s>3448:A,lnx} -qkq{x<1416:A,crn} -crn{x>2662:A,R} -in{s<1351:px,qqz} -qqz{s>2770:qs,m<1801:hdj,R} -gd{a>3333:R,R} -hdj{m>838:A,pv} - -{x=787,m=2655,a=1222,s=2876} -{x=1679,m=44,a=2067,s=496} -{x=2036,m=264,a=79,s=2244} -{x=2461,m=1339,a=466,s=291} -{x=2127,m=1623,a=2188,s=1013}", - "167409079868000", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day2/day2_test.gleam b/aoc2023/test/day2/day2_test.gleam deleted file mode 100644 index 28a65da..0000000 --- a/aoc2023/test/day2/day2_test.gleam +++ /dev/null @@ -1,57 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day2/solve - -type Problem1AnswerType = - Int - -type Problem2AnswerType = - Int - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green -Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue -Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red -Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red -Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", - 8, - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green -Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue -Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red -Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red -Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", - 2286, - ), - Example("Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", 48), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day20/day20_test.gleam b/aoc2023/test/day20/day20_test.gleam deleted file mode 100644 index 92e8afb..0000000 --- a/aoc2023/test/day20/day20_test.gleam +++ /dev/null @@ -1,56 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day20/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "broadcaster -> a, b, c -%a -> b -%b -> c -%c -> inv -&inv -> a", - "32000000", - ), - Example( - "broadcaster -> a -%a -> inv, con -&inv -> b -%b -> con -&con -> output -output -> ", - "11687500", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day21/day21_test.gleam b/aoc2023/test/day21/day21_test.gleam deleted file mode 100644 index 5f46808..0000000 --- a/aoc2023/test/day21/day21_test.gleam +++ /dev/null @@ -1,38 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day21/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day22/day22_test.gleam b/aoc2023/test/day22/day22_test.gleam deleted file mode 100644 index 3f8c0ca..0000000 --- a/aoc2023/test/day22/day22_test.gleam +++ /dev/null @@ -1,60 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day22/solve - -type Problem1AnswerType = - Int - -type Problem2AnswerType = - Int - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "1,0,1~1,2,1 -0,0,2~2,0,2 -0,2,3~2,2,3 -0,0,4~0,2,4 -2,0,5~2,2,5 -0,1,6~2,1,6 -1,1,8~1,1,9", - 5, - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "1,0,1~1,2,1 -0,0,2~2,0,2 -0,2,3~2,2,3 -0,0,4~0,2,4 -2,0,5~2,2,5 -0,1,6~2,1,6 -1,1,8~1,1,9", - 7, - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day23/day23_test.gleam b/aoc2023/test/day23/day23_test.gleam deleted file mode 100644 index 206571c..0000000 --- a/aoc2023/test/day23/day23_test.gleam +++ /dev/null @@ -1,92 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day23/solve - -type Problem1AnswerType = - Int - -type Problem2AnswerType = - Int - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "#.##################### -#.......#########...### -#######.#########.#.### -###.....#.>.>.###.#.### -###v#####.#v#.###.#.### -###.>...#.#.#.....#...# -###v###.#.#.#########.# -###...#.#.#.......#...# -#####.#.#.#######.#.### -#.....#.#.#.......#...# -#.#####.#.#.#########v# -#.#...#...#...###...>.# -#.#.#v#######v###.###v# -#...#.>.#...>.>.#.###.# -#####v#.#.###v#.#.###.# -#.....#...#...#.#.#...# -#.#########.###.#.#.### -#...###...#...#...#.### -###.###.#.###v#####v### -#...#...#.#.>.>.#.>.### -#.###.###.#.###.#.#v### -#.....###...###...#...# -#####################.#", - 94, - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "#.##################### -#.......#########...### -#######.#########.#.### -###.....#.>.>.###.#.### -###v#####.#v#.###.#.### -###.>...#.#.#.....#...# -###v###.#.#.#########.# -###...#.#.#.......#...# -#####.#.#.#######.#.### -#.....#.#.#.......#...# -#.#####.#.#.#########v# -#.#...#...#...###...>.# -#.#.#v#######v###.###v# -#...#.>.#...>.>.#.###.# -#####v#.#.###v#.#.###.# -#.....#...#...#.#.#...# -#.#########.###.#.#.### -#...###...#...#...#.### -###.###.#.###v#####v### -#...#...#.#.>.>.#.>.### -#.###.###.#.###.#.#v### -#.....###...###...#...# -#####################.#", - 154, - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day3/day3_test.gleam b/aoc2023/test/day3/day3_test.gleam deleted file mode 100644 index 30e17a9..0000000 --- a/aoc2023/test/day3/day3_test.gleam +++ /dev/null @@ -1,66 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day3/solve - -type Problem1AnswerType = - Int - -type Problem2AnswerType = - Int - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "467..114.. -...*...... -..35..633. -......#... -617*...... -.....+.58. -..592..... -......755. -...$.*.... -.664.598..", - 4361, - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "467..114.. -...*...... -..35..633. -......#... -617*...... -.....+.58. -..592..... -......755. -...$.*.... -.664.598..", - 467_835, - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day4/day4_test.gleam b/aoc2023/test/day4/day4_test.gleam deleted file mode 100644 index 324fe36..0000000 --- a/aoc2023/test/day4/day4_test.gleam +++ /dev/null @@ -1,58 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day4/solve - -type Problem1AnswerType = - Int - -type Problem2AnswerType = - Int - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 -Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 -Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 -Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 -Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 -Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11", - 13, - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 -Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 -Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 -Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 -Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 -Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11", - 30, - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day5/day5_test.gleam b/aoc2023/test/day5/day5_test.gleam deleted file mode 100644 index 86a8692..0000000 --- a/aoc2023/test/day5/day5_test.gleam +++ /dev/null @@ -1,112 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day5/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "seeds: 79 14 55 13 - -seed-to-soil map: -50 98 2 -52 50 48 - -soil-to-fertilizer map: -0 15 37 -37 52 2 -39 0 15 - -fertilizer-to-water map: -49 53 8 -0 11 42 -42 0 7 -57 7 4 - -water-to-light map: -88 18 7 -18 25 70 - -light-to-temperature map: -45 77 23 -81 45 19 -68 64 13 - -temperature-to-humidity map: -0 69 1 -1 0 69 - -humidity-to-location map: -60 56 37 -56 93 4", - "35", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "seeds: 79 14 55 13 - -seed-to-soil map: -50 98 2 -52 50 48 - -soil-to-fertilizer map: -0 15 37 -37 52 2 -39 0 15 - -fertilizer-to-water map: -49 53 8 -0 11 42 -42 0 7 -57 7 4 - -water-to-light map: -88 18 7 -18 25 70 - -light-to-temperature map: -45 77 23 -81 45 19 -68 64 13 - -temperature-to-humidity map: -0 69 1 -1 0 69 - -humidity-to-location map: -60 56 37 -56 93 4", - "46", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day6/day6_test.gleam b/aoc2023/test/day6/day6_test.gleam deleted file mode 100644 index c551993..0000000 --- a/aoc2023/test/day6/day6_test.gleam +++ /dev/null @@ -1,50 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day6/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "Time: 7 15 30 -Distance: 9 40 200", - "288", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "Time: 7 15 30 -Distance: 9 40 200", - "71503", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day7/day7_test.gleam b/aoc2023/test/day7/day7_test.gleam deleted file mode 100644 index f7f8454..0000000 --- a/aoc2023/test/day7/day7_test.gleam +++ /dev/null @@ -1,56 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day7/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "32T3K 765 -T55J5 684 -KK677 28 -KTJJT 220 -QQQJA 483", - "6440", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "32T3K 765 -T55J5 684 -KK677 28 -KTJJT 220 -QQQJA 483", - "5905", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day8/day8_test.gleam b/aoc2023/test/day8/day8_test.gleam deleted file mode 100644 index 2cd499a..0000000 --- a/aoc2023/test/day8/day8_test.gleam +++ /dev/null @@ -1,61 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day8/solve - -type Problem1AnswerType = - Int - -type Problem2AnswerType = - Int - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "LLR - -AAA = (BBB, BBB) -BBB = (AAA, ZZZ) -ZZZ = (ZZZ, ZZZ)", - 6, - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "LR - -11A = (11B, XXX) -11B = (XXX, 11Z) -11Z = (11B, XXX) -22A = (22B, XXX) -22B = (22C, 22C) -22C = (22Z, 22Z) -22Z = (22B, 22B) -XXX = (XXX, XXX)", - 6, - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} diff --git a/aoc2023/test/day9/day9_test.gleam b/aoc2023/test/day9/day9_test.gleam deleted file mode 100644 index 84fd3ba..0000000 --- a/aoc2023/test/day9/day9_test.gleam +++ /dev/null @@ -1,52 +0,0 @@ -import gleam/list -import showtime/tests/should -import adglent.{type Example, Example} -import day9/solve - -type Problem1AnswerType = - String - -type Problem2AnswerType = - String - -/// Add examples for part 1 here: -/// ```gleam -///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")] -/// ``` -const part1_examples: List(Example(Problem1AnswerType)) = [ - Example( - "0 3 6 9 12 15 -1 3 6 10 15 21 -10 13 16 21 30 45", - "114", - ), -] - -/// Add examples for part 2 here: -/// ```gleam -///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] -/// ``` -const part2_examples: List(Example(Problem2AnswerType)) = [ - Example( - "0 3 6 9 12 15 -1 3 6 10 15 21 -10 13 16 21 30 45", - "2", - ), -] - -pub fn part1_test() { - part1_examples - |> should.not_equal([]) - use example <- list.map(part1_examples) - solve.part1(example.input) - |> should.equal(example.answer) -} - -pub fn part2_test() { - part2_examples - |> should.not_equal([]) - use example <- list.map(part2_examples) - solve.part2(example.input) - |> should.equal(example.answer) -} -- cgit v1.2.3