Powered by AppSignal & Oban Pro

Day 12

day12.livemd

Day 12

# Note: when making the next template, something like this works well:
#   `cat day04.livemd | sed 's/12/04/' > day04.livemd`
# When inspecting lists of numbers, use "charlists: :as_lists"
#
Mix.install([
  # Join the string so a copy of dayN to dayM doesn't destroy it.
  {:kino, "~> 0.1" <> "4.2"}
])

# Join the string so a copy of dayN to dayM doesn't destroy it.
IEx.Helpers.c("/Users/johnb/dev/2" <> "0" <> "2" <> "4adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input

Installation and Data

input_example = Kino.Input.textarea("Example Data", monospace: true)
input_puzzleInput = Kino.Input.textarea("Puzzle Input", monospace: true)
input_source_select =
  Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
data = fn ->
  (Kino.Input.read(input_source_select) == :example &&
     Kino.Input.read(input_example)) ||
    Kino.Input.read(input_puzzleInput)
end

Solution

defmodule Day12 do
  def parse(text) do
    text
    |> AOC.as_grid()
  end

  def find_contiguous_region(_grid, [] = _region_cells, result), do: result
  def find_contiguous_region(grid, [first | _rest] = _region_cells, result) do
    # AOC.inspect([first, result], label: "find_contiguous_region")
    result = [first | result]
    first_neighbors = AOC.neighbors4(grid, first)
      |> Enum.filter(fn x -> grid[x] == grid[first] end)
      |> Enum.reject(fn x -> x in result end)
    if first_neighbors == [] do
      result
    else
      first_neighbors
      |> Enum.reduce(result, fn neighbor, acc ->
        find_contiguous_region(grid, [neighbor] ++ result, acc)
      end)
    end
    |> Enum.uniq()
    # |> AOC.inspect(label: "contiguous_region")
  end
  
  def find_separate_regions(grid, region_cells) do
    # AOC.inspect([grid, region_cells], label: "find_separate_regions")
    1..Enum.count(region_cells)
    |> Enum.reduce_while({[], region_cells}, fn _iteration, {regions, remainder} ->
      contiguous = find_contiguous_region(grid, remainder, [])
        |> Enum.sort()
      remainder = remainder -- contiguous
      # AOC.inspect([grid[List.first(region_cells)], contiguous, remainder], label: "55")
      if remainder == [] do
        {:halt, regions ++ [contiguous]} #|> AOC.inspect(label: "65")
      else
        # AOC.inspect([regions, remainder], label: "67")
        {:cont, {regions ++ [contiguous], remainder}}
        # {:cont, {regions ++ [contiguous], find_separate_regions(grid, remainder)}}
      end
    end)
  end

  def solve1(text) do
    grid = parse(text)

    naive_regions = Enum.reduce(AOC.grid_cells(grid), %{}, fn cell, acc ->
      region_letter = grid[cell]
      Map.put(acc, region_letter, (acc[region_letter] || []) ++ [cell])
    end)
    # |> AOC.inspect()

    numbered_regions = naive_regions
      |> Enum.reduce(%{0 => []}, fn {_region_name, region_cells}, acc ->
        find_separate_regions(grid, region_cells)
        # |> AOC.inspect(label: "separate_regions?")
        |> Enum.reduce(acc, fn region, acc1 ->
          region_number = 1 + Enum.max(Map.keys(acc1))
          put_in(acc1, [region_number], List.flatten(region))
        end)
        # |> AOC.inspect(label: "numbered_regions")
      end)
      |> Map.delete(0)

    numbered_regions
      |> Enum.map(fn {k, v} -> 
        AOC.inspect([k, v], label: "#{grid[List.first(v)]} (#{Enum.count(v)})") 
      end)

    numbered_regions
    |> Enum.map(fn {region_id, region} ->
      area = Enum.count(region)
      letter = grid[List.first(region)]
      perimeter = Enum.map(region, fn cell ->
          AOC.neighbors4_including_offgrid(grid, cell)
          |> Enum.reject(fn neighbor -> grid[neighbor] == letter end)
        end)
        |> List.flatten()
        |> Enum.sort()
        # |> AOC.inspect(label: "perimeter cells")
        # |> Enum.uniq()
        |> Enum.count()
      AOC.inspect([letter, region_id, area, perimeter])
      perimeter * area
    end)
    |> Enum.sum()
    
  end

  def solve2(text) do
    parse(text)
  end
end

# Example:

data.()
|> Day12.solve1()
|> IO.inspect(label: "\n*** Part 1 solution (example: 1930)")
# 1452678

# data.()
# |> Day12.solve2()
# |> IO.inspect(label: "\n*** Part 2 solution (example: )")
#