Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of code day 08

2024/livebooks/day-12.livemd

Advent of code day 08

Mix.install([
  {:kino, "~> 0.5.0"}
])

Setup input

example = Kino.Input.textarea("Please paste your input example:")
input = Kino.Input.textarea("Please paste your real input:")

Parse

parsed =
  example
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(&(String.split(&1, "", trim: true) |> List.to_tuple()))
  |> List.to_tuple()

rows = tuple_size(parsed) - 1
cols = tuple_size(elem(parsed, 0)) - 1

grid =
  for l <- 0..rows, c <- 0..cols, into: %{} do
    {{l, c}, elem(elem(parsed, l), c)}
  end
defmodule Solver do
  def walk(set, grid, {r, c} = point, prev) do
    if point in set or grid[point] != grid[prev] do
      set
    else
      MapSet.put(set, point)
      |> walk(grid, {r - 1, c}, point)
      |> walk(grid, {r, c + 1}, point)
      |> walk(grid, {r + 1, c}, point)
      |> walk(grid, {r, c - 1}, point)
    end
  end

  # a garden have 04 sides if its alone so we check to see if we
  # find a neighbour right next to us and subtract it
  def calculate_price(region) do
    Enum.reduce(region, 0, fn {r, c} = _point, acc ->
      amount_of_untouched_neigbour = 0

      amount_of_untouched_neigbour =
        if MapSet.member?(region, {r - 1, c}),
          do: amount_of_untouched_neigbour,
          else: amount_of_untouched_neigbour + 1

      amount_of_untouched_neigbour =
        if MapSet.member?(region, {r, c + 1}),
          do: amount_of_untouched_neigbour,
          else: amount_of_untouched_neigbour + 1

      amount_of_untouched_neigbour =
        if MapSet.member?(region, {r + 1, c}),
          do: amount_of_untouched_neigbour,
          else: amount_of_untouched_neigbour + 1

      amount_of_untouched_neigbour =
        if MapSet.member?(region, {r, c - 1}),
          do: amount_of_untouched_neigbour,
          else: amount_of_untouched_neigbour + 1

      amount_of_untouched_neigbour + acc
    end) * MapSet.size(region)
  end

  # we will calculate the corners
  def calculate_price_by_sides(region) do
    corner_candidates = candidates(region)

    Enum.reduce(corner_candidates, 0, fn {ccc, ccr}, corners ->
      config = generate_config(region, {ccc, ccr})

      case Enum.count(config, fn x -> x == true end) do
        1 ->
          corners + 1

        2 ->
          if config == [true, false, true, false] or config == [false, true, false, true] do
            corners + 2
          else
            corners
          end

        3 ->
          corners + 1

        _ ->
          corners
      end
    end) * MapSet.size(region)
  end

  defp generate_config(region, {r, c}) do
    for {rc, cc} <- [
          {r - 0.5, c - 0.5},
          {r + 0.5, c - 0.5},
          {r + 0.5, c + 0.5},
          {r - 0.5, c + 0.5}
        ] do
      MapSet.member?(region, {trunc(rc), trunc(cc)})
    end
  end

  defp candidates(region) do
    Enum.reduce(region, MapSet.new(), fn {r, c}, set ->
      corners = [{r - 0.5, c - 0.5}, {r + 0.5, c - 0.5}, {r + 0.5, c + 0.5}, {r - 0.5, c + 0.5}]

      Enum.reduce(corners, set, fn cc, set ->
        MapSet.put(set, cc)
      end)
    end)
  end
end
regions =
  Enum.reduce(grid, {MapSet.new(), []}, fn {point, _}, {set, regions} ->
    if point in set do
      {set, regions}
    else
      region = Solver.walk(MapSet.new(), grid, point, point)
      set = MapSet.union(set, region)
      {set, [region | regions]}
    end
  end)
  |> then(fn {_, regions} -> regions end)

Part 01

Enum.reduce(regions, 0, fn region, acc ->
  Solver.calculate_price(region) + acc
end)

Part 02

Enum.reduce(regions, 0, fn region, acc -> 
  Solver.calculate_price_by_sides(region)  + acc
end)