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

Day 3

2023/day3.livemd

Day 3

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

Section

input = Kino.Input.textarea("Input")
input_data = Kino.Input.read(input)

rows = String.split(input_data, "\n")
rrows = Enum.zip(1..length(rows), rows)

defmodule Sol do
  def read_row({row_id, row}, m) do
    cols = Enum.zip(1..String.length(row), String.to_charlist(row))
    Enum.reduce(cols, m, fn {col_id, letter}, m -> Map.put(m, {row_id, col_id}, letter) end)
  end

  def read(text) do
    rows = String.split(text)
    row_n = length(rows)
    col_n = String.length(hd(rows))

    maze =
      Enum.zip(1..length(rows), rows)
      |> Enum.reduce(%{}, fn row_data, acc -> Sol.read_row(row_data, acc) end)

    {row_n, col_n, maze}
  end

  def is_symbol?(s) do
    s != ?. and (s < ?0 or s > ?9)
  end

  def get_near_symbols(r, c, matrix) do
    for pos <- [
          {r - 1, c - 1},
          {r - 1, c},
          {r - 1, c + 1},
          {r, c - 1},
          {r, c + 1},
          {r + 1, c - 1},
          {r + 1, c},
          {r + 1, c + 1}
        ],
        Sol.is_symbol?(Map.get(matrix, pos, ?.)) do
      pos
    end
  end

  def update_symbol_number_map(symbol_number_map, symbols, value) do
    symbols
    |> Enum.reduce(symbol_number_map, fn symbol_loc, acc ->
      case Map.has_key?(acc, symbol_loc) do
        true -> Map.put(acc, symbol_loc, [value | acc[symbol_loc]])
        false -> Map.put(acc, symbol_loc, [value])
      end
    end)
  end

  def scan_row(_, col_id, col_n, _, _, _, symbol_number_map) when col_id > col_n + 1 do
    symbol_number_map
  end

  def scan_row(row_id, col_id, col_n, maze, current_val, near_symbol_set, symbol_number_map) do
    letter = Map.get(maze, {row_id, col_id}, ?.)

    case letter do
      _ when letter >= ?0 and letter <= ?9 ->
        Sol.scan_row(
          row_id,
          col_id + 1,
          col_n,
          maze,
          current_val * 10 + letter - ?0,
          Sol.get_near_symbols(row_id, col_id, maze)
          |> MapSet.new()
          |> MapSet.union(near_symbol_set),
          symbol_number_map
        )

      _ ->
        Sol.scan_row(
          row_id,
          col_id + 1,
          col_n,
          maze,
          0,
          MapSet.new(),
          Sol.update_symbol_number_map(symbol_number_map, near_symbol_set, current_val)
        )
    end
  end

  def build(row_n, col_n, maze) do
    Enum.reduce(1..row_n, %{}, fn row_id, symbol_number_map ->
      Sol.scan_row(row_id, 1, col_n, maze, 0, MapSet.new(), symbol_number_map)
    end)
  end

  def solve_part1(row_n, col_n, maze) do
    Sol.build(row_n, col_n, maze)
    |> Map.values()
    |> Enum.flat_map(&amp; &amp;1)
    |> Enum.sum()
  end

  def solve_part2(row_n, col_n, maze) do
    Sol.build(row_n, col_n, maze)
    |> Map.values()
    |> Enum.map(fn nums ->
      case length(nums) do
        2 -> Enum.product(nums)
        _ -> 0
      end
    end)
    |> Enum.sum()
  end
end

{row_n, col_n, maze} = Sol.read(input_data)
{Sol.solve_part1(row_n, col_n, maze), Sol.solve_part2(row_n, col_n, maze)}
[1, 2, 3] |> Enum.into(MapSet.new())