diff options
Diffstat (limited to 'aoc2023-gleam')
80 files changed, 4662 insertions, 0 deletions
diff --git a/aoc2023-gleam/.DS_Store b/aoc2023-gleam/.DS_Store Binary files differnew file mode 100644 index 0000000..5172429 --- /dev/null +++ b/aoc2023-gleam/.DS_Store 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 + +[](https://hex.pm/packages/aoc2023) +[](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 <https://hexdocs.pm/aoc2023>. + +## Use + +* Set up a solution: `gleam run -m adglent/day <n>` +* Check against examples: `gleam test -- --modules=day<n>/day<n>_test` +* Get final answer: `gleam run -m day<n>/solve <p>`
\ 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..416a155 --- /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 = ["gap", "gleam_community_ansi", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_otp", "gleam_stdlib", "glint", "simplifile", "snag", "tom"], otp_app = "adglent", source = "hex", outer_checksum = "A20D35001061F8AD602E3B92FB3AC0E1E4EEC642AD2AAE0ACEAD3A85F37DA7F0" }, + { name = "gap", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "2EE1B0A17E85CF73A0C1D29DA315A2699117A8F549C8E8D89FA8261BE41EDEB1" }, + { name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" }, + { name = "gleam_community_colour", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "A49A5E3AE8B637A5ACBA80ECB9B1AFE89FD3D5351FF6410A42B84F666D40D7D5" }, + { name = "gleam_community_maths", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_maths", source = "hex", outer_checksum = "E30C61A75051DAF7CFD77C4FBAA04140FDA0B5D831955E7A74521E5576E2780D" }, + { 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.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" }, + { name = "gleam_httpc", version = "2.1.2", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "ACD05CA3BAC7780DF5FFAE334621FD199D1B490FAF6ECDFF74316CAA61CE88E6" }, + { name = "gleam_otp", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "18EF8242A5E54BA92F717C7222F03B3228AEE00D1F286D4C56C3E8C18AA2588E" }, + { name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" }, + { name = "glint", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "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.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "EB9AA8E65E5C1E3E0FDCFC81BC363FD433CB122D7D062750FFDF24DE4AC40116" }, + { 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..37a19d2 --- /dev/null +++ b/aoc2023-gleam/src/day1/solve.gleam @@ -0,0 +1,57 @@ +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..893b83c --- /dev/null +++ b/aoc2023-gleam/src/day12/solve.gleam @@ -0,0 +1,90 @@ +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..6f9b9a0 --- /dev/null +++ b/aoc2023-gleam/src/day13/solve.gleam @@ -0,0 +1,87 @@ +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) + acc + + case note { + Horizontal(n) -> 100 * n + Vertical(n) -> n + } +} + +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..ecc5361 --- /dev/null +++ b/aoc2023-gleam/src/day14/solve.gleam @@ -0,0 +1,94 @@ +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..608955f --- /dev/null +++ b/aoc2023-gleam/src/day2/solve.gleam @@ -0,0 +1,66 @@ +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..7c05310 --- /dev/null +++ b/aoc2023-gleam/src/day5/solve.gleam @@ -0,0 +1,162 @@ +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..abf21b9 --- /dev/null +++ b/aoc2023-gleam/src/utilities/prioqueue.gleam @@ -0,0 +1,64 @@ +//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..2ce48e2 --- /dev/null +++ b/aoc2023-gleam/test/day17/day17_test.gleam @@ -0,0 +1,54 @@ +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", + ), +] + +// /// ``` +// const part2_examples: List(Example(Problem2AnswerType)) = [] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +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..9b79b05 --- /dev/null +++ b/aoc2023-gleam/test/day20/day20_test.gleam @@ -0,0 +1,55 @@ +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", + ), +] + +// const part2_examples: List(Example(Problem2AnswerType)) = [] + +/// Add examples for part 2 here: +/// ```gleam +///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")] +/// ``` +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) +} |