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

Day 3

2023/03.livemd

Day 3

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

Input

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

Part 1

example = """
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
"""

expected = 4361
4361
defmodule Part1 do
  def parse(input) do
    for {line, row} <- String.split(input, "\n", trim: true) |> Enum.with_index(), reduce: %{} do
      acc ->
        for [{col, len}] <- Regex.scan(~r/\d+|[^.]/, line, return: :index), into: acc do
          string = String.slice(line, col, len)

          case Integer.parse(string) do
            :error -> {{row, col}, string}
            {int, ""} -> {{row, col}, int}
          end
        end
    end
  end

  def classify({_, value}) when is_integer(value), do: :number
  def classify({_, _}), do: :symbol

  def adjacent?({{row, col}, integer}, check) do
    digits = Integer.digits(integer) |> Enum.count()
    top_bottom = Enum.map((col - 1)..(col + digits), &amp;[{row - 1, &amp;1}, {row + 1, &amp;1}])
    left_right = [{row, col - 1}, {row, col + digits}]

    List.flatten([left_right, top_bottom])
    |> Enum.any?(&amp;Enum.member?(check, &amp;1))
  end

  def run(input) do
    %{symbol: symbols, number: numbers} = parse(input) |> Enum.group_by(&amp;classify/1)

    symbol_set = MapSet.new(symbols, &amp;elem(&amp;1, 0))

    Enum.filter(numbers, &amp;adjacent?(&amp;1, symbol_set))
    |> Enum.map(&amp;elem(&amp;1, 1))
    |> Enum.sum()
  end
end

Part1.run(example) == expected
true
Kino.Input.read(input)
|> Part1.run()
529618

Part 2

expected = [16345, 451_490]
[16345, 451490]
defmodule Part2 do
  def classify({_, "*"}), do: :maybe_gear
  def classify({_, value}) when is_integer(value), do: :number
  def classify({_, _}), do: :symbol

  def run(input) do
    %{maybe_gear: maybe_gears, number: numbers} =
      Part1.parse(input) |> Enum.group_by(&amp;classify/1)

    gear_parts =
      for gear <- Enum.map(maybe_gears, &amp;elem(&amp;1, 0)) do
        Enum.filter(numbers, &amp;Part1.adjacent?(&amp;1, [gear]))
        |> Enum.map(&amp;elem(&amp;1, 1))
      end

    Enum.filter(gear_parts, &amp;(Enum.count(&amp;1) == 2))
    |> Enum.map(&amp;Enum.product/1)
  end
end

Part2.run(example) == expected
true
Kino.Input.read(input)
|> Part2.run()
|> Enum.sum()
77509019