diff --git a/day12/main.exs b/day12/main.exs new file mode 100644 index 0000000..df6c5f6 --- /dev/null +++ b/day12/main.exs @@ -0,0 +1,155 @@ +defmodule Day12 do + def run do + garden = + File.read!("data.txt") + |> String.trim() + |> String.split("\n") + |> Enum.map(fn el -> String.graphemes(el) end) + + height = length(garden) + width = length(hd(garden)) + + # generate a list of regions (a region is a lists of coords) + regions = + Enum.reduce(0..(height - 1), [], fn row, acc1 -> + Enum.reduce(0..(width - 1), acc1, fn col, acc2 -> + # does this coordinate already exist in a region + if Enum.any?(acc2, fn x -> Enum.member?(x, {row, col}) end) do + acc2 + else + char = element_at({row, col}, garden) + res = findRegion(char, {row, col}, garden, []) + + if is_nil(res), do: acc2, else: [res | acc2] + end + end) + end) + + part1 = Enum.reduce(regions, 0, fn r, acc -> acc + perimeter(r, garden) * length(r) end) + dbg(part1) + + # part2 = Enum.map(regions, fn r -> sides(r, garden) end) + part2 = Enum.reduce(regions, 0, fn r, acc -> acc + sides(r, garden) * length(r) end) + dbg(part2) + end + + defp sides(region, garden) do + # scan from north to south, west to east + height = length(garden) + width = length(hd(garden)) + + left_to_right = + Enum.reduce(0..(height - 1), %{top: false, bottom: false, sides: 0}, fn row, acc -> + Enum.reduce(0..(width - 1), acc, fn col, acc2 -> + el = element_at({row, col}, garden) + in_region = Enum.member?(region, {row, col}) + + if in_region do + # if the cell above is different, and it wasn't before, we're starting a new side + upper_cell_different = element_at({row - 1, col}, garden) != el + up_side = if upper_cell_different and !acc2.top, do: 1, else: 0 + # same with cell below + lower_cell_different = element_at({row + 1, col}, garden) != el + down_side = if lower_cell_different and !acc2.bottom, do: 1, else: 0 + + %{ + top: upper_cell_different, + bottom: lower_cell_different, + sides: acc2.sides + up_side + down_side + } + else + # we are not in a region + %{top: false, bottom: false, sides: acc2.sides} + end + end) + end) + + top_to_bottom = + Enum.reduce(0..(width - 1), %{left: false, right: false, sides: 0}, fn col, acc -> + Enum.reduce(0..(height - 1), acc, fn row, acc2 -> + el = element_at({row, col}, garden) + in_region = Enum.member?(region, {row, col}) + + if in_region do + # if the cell to the left is different, and it wasn't before, we're starting a new side + left_cell_different = element_at({row, col - 1}, garden) != el + left_side = if left_cell_different and !acc2.left, do: 1, else: 0 + # same with cell to the right + right_cell_different = element_at({row, col + 1}, garden) != el + right_side = if right_cell_different and !acc2.right, do: 1, else: 0 + + %{ + left: left_cell_different, + right: right_cell_different, + sides: acc2.sides + left_side + right_side + } + else + # we are not in a region + %{left: false, right: false, sides: acc2.sides} + end + end) + end) + + left_to_right.sides + top_to_bottom.sides + end + + defp perimeter(region, garden) do + # scan from north to south, west to east, when we enter or leave a region, add 1 + height = length(garden) + width = length(hd(garden)) + + left_to_right = + Enum.reduce(0..height, %{in_region: false, edges: 0}, fn row, acc -> + Enum.reduce(0..width, acc, fn col, acc2 -> + # el = element_at({row, col}, garden) + in_region = Enum.member?(region, {row, col}) + edge = if in_region != acc2.in_region, do: 1, else: 0 + %{in_region: in_region, edges: acc2.edges + edge} + end) + end) + + top_to_bottom = + Enum.reduce(0..width, %{in_region: false, edges: 0}, fn col, acc -> + Enum.reduce(0..height, acc, fn row, acc2 -> + # el = element_at({row, col}, garden) + in_region = Enum.member?(region, {row, col}) + edge = if in_region != acc2.in_region, do: 1, else: 0 + %{in_region: in_region, edges: acc2.edges + edge} + end) + end) + + left_to_right.edges + top_to_bottom.edges + end + + defp findRegion(char, {row, col}, garden, region) do + # find the char at coord + el = element_at({row, col}, garden) + + # if its out of bounds, it is a different char, or it's already in this region + if is_nil(el) or char != el or Enum.member?(region, {row, col}) do + nil + else + # its in the region! add it + new_region = [{row, col} | region] + + # check the 4 neighbors + [{row - 1, col}, {row, col - 1}, {row + 1, col}, {row, col + 1}] + |> Enum.reject(fn el -> Enum.member?(new_region, el) end) + |> Enum.reduce(new_region, fn c, acc -> + res = findRegion(char, c, garden, acc) + if is_nil(res), do: acc, else: res + end) + end + end + + defp element_at({x, y}, garden) do + if x < 0 or y < 0 do + nil + else + row = Enum.at(garden, x) + if is_nil(row), do: nil, else: Enum.at(row, y) + end + end +end + +Day12.run()