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()