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

Day 03

2023/day03.livemd

Day 03

input = File.read!("./2023/input_day03.txt")
lines = String.split(input, "\n", trim: true)

Common

defmodule Board do
  defstruct [:grid, :by_type]

  def get_cell(board, coord) do
    board.grid[coord]
  end

  def parse_lines(lines) do
    grid =
      lines
      |> Stream.with_index()
      |> Stream.flat_map(&parse_line/1)
      |> Map.new()

    by_type =
      Enum.group_by(
        grid,
        fn
          {_coord, {:number, _}} -> :number
          {_coord, {:ref, _, _}} -> :number
          {_coord, {:symbol, _symbol}} -> :symbol
          {_coord, :empty} -> :empty
        end,
        &elem(&1, 0)
      )

    %__MODULE__{grid: grid, by_type: by_type}
  end

  defp parse_line({line, x}) do
    line
    |> to_charlist()
    |> Stream.with_index()
    |> Stream.transform(
      fn -> [] end,
      fn
        {char, y}, digits when char in ?0..?9 ->
          coord = {x, y}
          new_digits = [{coord, char - ?0} | digits]
          {[], new_digits}

        {char, y}, digits ->
          coord = {x, y}

          content =
            case char do
              ?. -> :empty
              symbol -> {:symbol, symbol}
            end

          current_cell = {coord, content}
          inserted_cells = process_digits(digits) ++ [current_cell]

          {inserted_cells, []}
      end,
      fn digits -> {process_digits(digits), []} end,
      fn _acc -> :ok end
    )
  end

  defp process_digits(digits)
  defp process_digits([]), do: []

  defp process_digits(digits) do
    digits = Enum.reverse(digits)
    number = digits |> Enum.map(&elem(&1, 1)) |> Integer.undigits()

    [first | others] = digits
    first_coord = elem(first, 0)

    [
      {first_coord, {:number, number}}
      | Enum.map(others, &{elem(&1, 0), {:ref, first_coord, number}})
    ]
  end
end

board = Board.parse_lines(lines)

Part 1

engine_parts =
  for {x, y} <- board.by_type[:symbol], dx <- -1..1, dy <- -1..1 do
    coord = {x + dx, y + dy}

    case Board.get_cell(board, coord) do
      {:number, n} -> {coord, n}
      {:ref, ref_coord, n} -> {ref_coord, n}
      _other -> nil
    end
  end

engine_parts =
  engine_parts
  |> Enum.reject(&amp;is_nil/1)
  |> Enum.uniq()

engine_parts
|> Enum.map(&amp;elem(&amp;1, 1))
|> Enum.sum()

Part 2

gear_ratios =
  for gear_coord <- board.by_type[:symbol],
      match?({:symbol, ?*}, Board.get_cell(board, gear_coord)) do
    {x, y} = gear_coord

    parts =
      for dx <- -1..1, dy <- -1..1 do
        part_coord = {x + dx, y + dy}

        case Board.get_cell(board, part_coord) do
          {:number, n} -> {part_coord, n}
          {:ref, ref_coord, n} -> {ref_coord, n}
          _other -> nil
        end
      end

    parts =
      parts
      |> Enum.reject(&amp;is_nil/1)
      |> Enum.uniq()

    case parts do
      [{_, part1}, {_, part2}] ->
        part1 * part2

      _other ->
        nil
    end
  end

gear_ratios
|> Enum.reject(&amp;is_nil/1)
|> Enum.sum()