diff options
author | J.J <thechairman@thechairman.info> | 2023-12-03 12:20:36 -0500 |
---|---|---|
committer | J.J <thechairman@thechairman.info> | 2023-12-03 12:20:36 -0500 |
commit | 03a8465fef75336989ff7d20050b643e541e5a36 (patch) | |
tree | e30d983f8a532d86de22a08fd2f3fb77bc66bfd8 /aoc2023 | |
parent | 0607a032238b545d3d49d3af0e1763f82ccdd31a (diff) | |
parent | 8a521c9df766b733f03d357340f74935c65f7de7 (diff) | |
download | gleam_aoc-03a8465fef75336989ff7d20050b643e541e5a36.tar.gz gleam_aoc-03a8465fef75336989ff7d20050b643e541e5a36.zip |
Merge branch 'main' of https://github.com/hunkyjimpjorps/AdventOfCode
Diffstat (limited to 'aoc2023')
-rw-r--r-- | aoc2023/src/day3/.gitignore | 1 | ||||
-rw-r--r-- | aoc2023/src/day3/solve.gleam | 178 | ||||
-rw-r--r-- | aoc2023/test/day3/day3_test.gleam | 66 |
3 files changed, 245 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..fa16bb8 --- /dev/null +++ b/aoc2023/src/day3/solve.gleam @@ -0,0 +1,178 @@ +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]) + } + } + } + } +} + +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/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) +} |