From f17388b5ec81aa2482ef49eeed5041b808283bf1 Mon Sep 17 00:00:00 2001 From: Hamcha Date: Fri, 13 Dec 2024 10:58:35 +0100 Subject: [PATCH] day 12 --- sample/day12-sample.txt | 4 ++ sample/day12-sample2.txt | 5 ++ sample/day12-sample3.txt | 10 +++ sample/day12-sample4.txt | 5 ++ sample/day12-sample5.txt | 6 ++ src/day12_p1.gleam | 97 ++++++++++++++++++++++++++++ src/day12_p2.gleam | 132 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 259 insertions(+) create mode 100644 sample/day12-sample.txt create mode 100644 sample/day12-sample2.txt create mode 100644 sample/day12-sample3.txt create mode 100644 sample/day12-sample4.txt create mode 100644 sample/day12-sample5.txt create mode 100644 src/day12_p1.gleam create mode 100644 src/day12_p2.gleam diff --git a/sample/day12-sample.txt b/sample/day12-sample.txt new file mode 100644 index 0000000..b41163a --- /dev/null +++ b/sample/day12-sample.txt @@ -0,0 +1,4 @@ +AAAA +BBCD +BBCC +EEEC diff --git a/sample/day12-sample2.txt b/sample/day12-sample2.txt new file mode 100644 index 0000000..50a7304 --- /dev/null +++ b/sample/day12-sample2.txt @@ -0,0 +1,5 @@ +OOOOO +OXOXO +OOOOO +OXOXO +OOOOO diff --git a/sample/day12-sample3.txt b/sample/day12-sample3.txt new file mode 100644 index 0000000..85b768f --- /dev/null +++ b/sample/day12-sample3.txt @@ -0,0 +1,10 @@ +RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE diff --git a/sample/day12-sample4.txt b/sample/day12-sample4.txt new file mode 100644 index 0000000..5818d30 --- /dev/null +++ b/sample/day12-sample4.txt @@ -0,0 +1,5 @@ +EEEEE +EXXXX +EEEEE +EXXXX +EEEEE diff --git a/sample/day12-sample5.txt b/sample/day12-sample5.txt new file mode 100644 index 0000000..f7cd38f --- /dev/null +++ b/sample/day12-sample5.txt @@ -0,0 +1,6 @@ +AAAAAA +AAABBA +AAABBA +ABBAAA +ABBAAA +AAAAAA diff --git a/src/day12_p1.gleam b/src/day12_p1.gleam new file mode 100644 index 0000000..79b729d --- /dev/null +++ b/src/day12_p1.gleam @@ -0,0 +1,97 @@ +import gleam/bool +import gleam/dict.{type Dict} +import gleam/int +import gleam/io +import gleam/iterator +import gleam/list.{contains, count, filter, fold, map, reverse} +import gleam/pair +import gleam/string +import stdin.{stdin} + +pub fn main() { + stdin() + |> read_input + |> detect_regions + |> map(calculate_price) + |> fold(0, int.add) + |> io.debug +} + +fn calculate_price(areas: List(#(Int, Int))) { + let area = areas |> list.length + + let perimeter = + areas + |> fold(0, fn(acc, area) { + let #(x, y) = area + let bounds = + [#(x + 1, y), #(x - 1, y), #(x, y + 1), #(x, y - 1)] + |> count(fn(point) { areas |> contains(point) |> bool.negate }) + acc + bounds + }) + + area * perimeter +} + +fn detect_regions(textmap: Dict(#(Int, Int), String)) -> List(List(#(Int, Int))) { + textmap + |> dict.keys + |> fold(#(textmap, []), fn(acc, x) { + let #(searchspace, lists) = acc + // Make sure we haven't already included this key in another region + case lists |> list.find(fn(lst) { lst |> contains(x) }) { + Ok(_) -> acc + _ -> { + let assert Ok(plant) = dict.get(textmap, x) + find_region(searchspace, x, plant, []) + |> pair.map_second(fn(lst) { [lst |> list.unique, ..lists] }) + } + } + }) + |> pair.second +} + +fn find_region( + textmap: Dict(#(Int, Int), String), + start: #(Int, Int), + plant: String, + processed: List(#(Int, Int)), +) -> #(Dict(#(Int, Int), String), List(#(Int, Int))) { + let #(x, y) = start + let searchspace = dict.delete(textmap, start) + + [#(x - 1, y), #(x + 1, y), #(x, y - 1), #(x, y + 1)] + |> filter(fn(x) { is_same(textmap, x, plant) }) + |> fold(#(searchspace, [start, ..processed]), fn(acc, x) { + let #(partialmap, current) = acc + find_region(partialmap, x, plant, current) + }) +} + +fn is_same( + textmap: Dict(#(Int, Int), String), + point: #(Int, Int), + plant: String, +) -> Bool { + case dict.get(textmap, point) { + Ok(s) -> s == plant + _ -> False + } +} + +fn read_input(it: iterator.Iterator(String)) -> Dict(#(Int, Int), String) { + it + |> iterator.fold([], fn(acc, line) { + case string.trim(line) { + "" -> acc + str -> { + [str |> string.to_graphemes, ..acc] + } + } + }) + |> reverse + |> list.index_fold(dict.new(), fn(acc, line, y) { + line + |> list.index_fold(acc, fn(dic, num, x) { dict.insert(dic, #(x, y), num) }) + }) +} diff --git a/src/day12_p2.gleam b/src/day12_p2.gleam new file mode 100644 index 0000000..cca2b30 --- /dev/null +++ b/src/day12_p2.gleam @@ -0,0 +1,132 @@ +import gleam/bool +import gleam/dict.{type Dict} +import gleam/int +import gleam/io +import gleam/iterator +import gleam/list.{contains, filter, fold, map, reverse} +import gleam/pair +import gleam/string +import stdin.{stdin} + +pub fn main() { + stdin() + |> read_input + |> detect_regions + |> map(calculate_price) + |> fold(0, int.add) + |> io.debug +} + +fn calculate_price(areas: List(#(Int, Int))) { + let area = areas |> list.length + + let perimeter = + areas + |> fold([], fn(acc, area) { + let bounds = find_bounds(areas, area) + list.append(acc, bounds) + }) + |> count_contiguous + + area * perimeter +} + +type Bound { + Left + Right + Top + Bottom +} + +fn find_bounds( + areas: List(#(Int, Int)), + area: #(Int, Int), +) -> List(#(Bound, #(Int, Int))) { + let #(x, y) = area + [ + #(Right, #(x + 1, y)), + #(Left, #(x - 1, y)), + #(Bottom, #(x, y + 1)), + #(Top, #(x, y - 1)), + ] + |> filter(fn(p) { + let #(_, point) = p + areas |> contains(point) |> bool.negate + }) +} + +fn count_contiguous(bounds: List(#(Bound, #(Int, Int)))) -> Int { + bounds + |> list.count(fn(b) { + case b { + #(Top, #(x, y)) -> bounds |> contains(#(Top, #(x - 1, y))) + #(Bottom, #(x, y)) -> bounds |> contains(#(Bottom, #(x - 1, y))) + #(Left, #(x, y)) -> bounds |> contains(#(Left, #(x, y - 1))) + #(Right, #(x, y)) -> bounds |> contains(#(Right, #(x, y - 1))) + } + |> bool.negate + }) +} + +fn detect_regions(textmap: Dict(#(Int, Int), String)) -> List(List(#(Int, Int))) { + textmap + |> dict.keys + |> fold(#(textmap, []), fn(acc, x) { + let #(searchspace, lists) = acc + // Make sure we haven't already included this key in another region + case lists |> list.find(fn(lst) { lst |> contains(x) }) { + Ok(_) -> acc + _ -> { + let assert Ok(plant) = dict.get(textmap, x) + find_region(searchspace, x, plant, []) + |> pair.map_second(fn(lst) { [lst |> list.unique, ..lists] }) + } + } + }) + |> pair.second +} + +fn find_region( + textmap: Dict(#(Int, Int), String), + start: #(Int, Int), + plant: String, + processed: List(#(Int, Int)), +) -> #(Dict(#(Int, Int), String), List(#(Int, Int))) { + let #(x, y) = start + let searchspace = dict.delete(textmap, start) + + [#(x - 1, y), #(x + 1, y), #(x, y - 1), #(x, y + 1)] + |> filter(fn(x) { is_same(textmap, x, plant) }) + |> fold(#(searchspace, [start, ..processed]), fn(acc, x) { + let #(partialmap, current) = acc + find_region(partialmap, x, plant, current) + }) +} + +fn is_same( + textmap: Dict(#(Int, Int), String), + point: #(Int, Int), + plant: String, +) -> Bool { + case dict.get(textmap, point) { + Ok(s) -> s == plant + _ -> False + } +} + +fn read_input(it: iterator.Iterator(String)) -> Dict(#(Int, Int), String) { + it + |> iterator.fold([], fn(acc, line) { + case string.trim(line) { + "" -> acc + str -> { + [str |> string.to_graphemes, ..acc] + } + } + }) + |> reverse + |> list.index_fold(dict.new(), fn(acc, line, y) { + line + |> list.index_fold(acc, fn(dic, num, x) { dict.insert(dic, #(x, y), num) }) + }) +}