aboutsummaryrefslogtreecommitdiff
path: root/aoc2023
diff options
context:
space:
mode:
authorHJ <thechairman@thechairman.info>2023-12-03 01:59:44 -0500
committerHJ <thechairman@thechairman.info>2023-12-03 01:59:44 -0500
commit5108ae09e37ff6c71a9d791ef70897c4b6791c42 (patch)
treeaa4ffcda8feda4da308c465f728a9b2f37c6b39f /aoc2023
parent8c6a76dcd8384d4b4d06376f9af7ec9422291721 (diff)
downloadgleam_aoc-5108ae09e37ff6c71a9d791ef70897c4b6791c42.tar.gz
gleam_aoc-5108ae09e37ff6c71a9d791ef70897c4b6791c42.zip
day 3 complete
Diffstat (limited to 'aoc2023')
-rw-r--r--aoc2023/src/day3/.gitignore1
-rw-r--r--aoc2023/src/day3/solve.gleam192
-rw-r--r--aoc2023/test/day3/day3_test.gleam66
3 files changed, 259 insertions, 0 deletions
diff --git a/aoc2023/src/day3/.gitignore b/aoc2023/src/day3/.gitignore
new file mode 100644
index 0000000..ae40cea
--- /dev/null
+++ b/aoc2023/src/day3/.gitignore
@@ -0,0 +1 @@
+input.txt \ No newline at end of file
diff --git a/aoc2023/src/day3/solve.gleam b/aoc2023/src/day3/solve.gleam
new file mode 100644
index 0000000..5df337d
--- /dev/null
+++ b/aoc2023/src/day3/solve.gleam
@@ -0,0 +1,192 @@
+import adglent.{First, Second}
+import gleam/io
+import gleam/dict.{type Dict}
+import gleam/string
+import gleam/list
+import gleam/int
+import gleam/order.{Eq}
+import gleam/result
+
+type Coord {
+ Coord(x: Int, y: Int)
+}
+
+type Symbol {
+ Number(Int)
+ Gear
+ OtherSymbol
+ Empty
+}
+
+type Board =
+ Dict(Coord, Symbol)
+
+fn to_symbol(c: String) -> Symbol {
+ case int.parse(c), c {
+ Ok(n), _ -> Number(n)
+ Error(Nil), "." -> Empty
+ Error(Nil), "*" -> Gear
+ _, _ -> OtherSymbol
+ }
+}
+
+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 find_all_numbers(b: Board) {
+ b
+ |> dict.filter(fn(_, v) {
+ case v {
+ Number(_) -> True
+ _ -> False
+ }
+ })
+ |> dict.to_list()
+ |> list.sort(fn(a, b) {
+ let #(Coord(x_a, y_a), _) = a
+ let #(Coord(x_b, y_b), _) = b
+ case int.compare(y_a, y_b) {
+ Eq -> int.compare(x_a, x_b)
+ other -> other
+ }
+ })
+}
+
+fn group_parts(ns, acc) {
+ case ns, acc {
+ [], _ -> acc
+ [#(Coord(x, y) as c, Number(n)), ..t], [
+ #([Coord(x0, y0), ..] as cs, n0),
+ ..acc_rest
+ ] if y == y0 ->
+ case { x - 1 == x0 } {
+ True -> group_parts(t, [#([c, ..cs], n0 * 10 + n), ..acc_rest])
+ False -> group_parts(t, [#([c], n), ..acc])
+ }
+ [#(coord, Number(n)), ..t], _ -> group_parts(t, [#([coord], n), ..acc])
+ }
+}
+
+fn all_neighbors(c: Coord) -> List(Coord) {
+ let Coord(x, y) = c
+ 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(x + dx, y + dy))
+ }
+}
+
+fn check_part_neighbors(
+ part: #(List(Coord), Int),
+ board: Board,
+) -> Result(Int, Nil) {
+ let #(coords, n) = part
+
+ let neighbors =
+ coords
+ |> list.flat_map(all_neighbors)
+ |> list.unique()
+
+ case
+ list.any(
+ neighbors,
+ fn(c) {
+ let content = dict.get(board, c)
+ content == Ok(OtherSymbol) || content == Ok(Gear)
+ },
+ )
+ {
+ True -> Ok(n)
+ False -> Error(Nil)
+ }
+}
+
+pub fn part1(input: String) {
+ let board = to_board(input)
+
+ board
+ |> find_all_numbers
+ |> group_parts([])
+ |> list.map(check_part_neighbors(_, board))
+ |> result.values
+ |> int.sum
+}
+
+fn find_gears(b: Board) {
+ dict.filter(b, fn(_, v) { v == Gear })
+ |> dict.keys
+}
+
+fn to_part_with_neighbors(part: #(List(Coord), Int)) {
+ let #(coords, n) = part
+
+ let neighbors =
+ coords
+ |> list.flat_map(all_neighbors)
+ |> list.unique
+ |> list.filter(fn(c) { !list.contains(coords, c) })
+
+ #(neighbors, n)
+}
+
+fn find_parts_near_gear(gear: Coord, parts: List(#(List(Coord), Int))) {
+ parts
+ |> list.filter_map(fn(part) {
+ let #(neighbors, n) = part
+ case list.contains(neighbors, gear) {
+ True -> Ok(n)
+ False -> Error(Nil)
+ }
+ })
+}
+
+fn keep_gears_near_two_parts(sets_of_parts: List(List(Int))) {
+ list.filter_map(
+ sets_of_parts,
+ fn(ps) {
+ case ps {
+ [p1, p2] -> Ok(p1 * p2)
+ _ -> Error(Nil)
+ }
+ },
+ )
+}
+
+pub fn part2(input: String) {
+ let board = to_board(input)
+
+ let parts =
+ board
+ |> find_all_numbers
+ |> group_parts([])
+ |> list.map(to_part_with_neighbors)
+
+ board
+ |> find_gears
+ |> list.map(find_parts_near_gear(_, parts))
+ |> keep_gears_near_two_parts
+ |> int.sum
+}
+
+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/test/day3/day3_test.gleam b/aoc2023/test/day3/day3_test.gleam
new file mode 100644
index 0000000..30e17a9
--- /dev/null
+++ b/aoc2023/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)
+}