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

Day 3

2023/day03.livemd

Day 3

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

Section

input = Kino.Input.textarea("Input", monospace: true)
defmodule EngineSchematic do
  def build_schema(input) do
    input
    |> String.codepoints()
    |> build_schema(0, 0, %{}, %{})
  end

  defp build_schema([], _, _, parts, symbols) do
    {parts, Enum.group_by(symbols, &elem(&1, 1), &elem(&1, 0))}
  end

  defp build_schema([i | _] = input, x, y, parts, symbols) when i in ~w(0 9 8 7 6 5 4 3 2 1) do
    {part, rest} = Enum.split_while(input, &(&1 in ~w(0 9 8 7 6 5 4 3 2 1)))

    part_length = length(part)
    part = part |> Enum.map(&String.to_integer/1) |> Integer.undigits()

    build_schema(rest, x, y + part_length, Map.put(parts, {x, y, part_length}, part), symbols)
  end

  defp build_schema(["\n" | rest], x, _y, parts, symbols) do
    build_schema(rest, x + 1, 0, parts, symbols)
  end

  defp build_schema(["." | rest], x, y, parts, symbols) do
    build_schema(rest, x, y + 1, parts, symbols)
  end

  defp build_schema([s | rest], x, y, parts, symbols) do
    build_schema(rest, x, y + 1, parts, Map.put(symbols, {x, y}, s))
  end

  def part_valid?({{x, y, len}, _}, symbols) do
    not MapSet.disjoint?(
      symbols,
      for y <- (y - 1)..(y + len), reduce: MapSet.new([{x, y - 1}, {x, y + len}]) do
        set ->
          set
          |> MapSet.put({x - 1, y})
          |> MapSet.put({x + 1, y})
      end
    )
  end

  def flatten_parts(parts) do
    Enum.reduce(parts, %{}, fn {{x, y, len}, part}, parts ->
      Map.merge(
        parts,
        Map.new(0..(len - 1), &amp;{{x, y + &amp;1}, part})
      )
    end)
  end

  def gear_ratio({x, y}, flatten_parts) do
    Map.take(flatten_parts, [
      {x - 1, y + 1},
      {x, y + 1},
      {x + 1, y + 1},
      {x - 1, y},
      {x + 1, y},
      {x - 1, y - 1},
      {x, y - 1},
      {x + 1, y - 1}
    ])
    |> Map.values()
    |> Enum.uniq()
    |> then(fn
      [a, b] -> a * b
      _ -> 0
    end)
  end
end

{parts, symbols} =
  input
  |> Kino.Input.read()
  |> EngineSchematic.build_schema()

P1

symbol_positions =
  symbols
  |> Map.values()
  |> List.flatten()
  |> MapSet.new()

parts
|> Enum.filter(&amp;EngineSchematic.part_valid?(&amp;1, symbol_positions))
|> Enum.map(&amp;elem(&amp;1, 1))
|> Enum.sum()

P2

parts = EngineSchematic.flatten_parts(parts)

symbols
|> Map.get("*")
|> Enum.map(&amp;EngineSchematic.gear_ratio(&amp;1, parts))
|> Enum.sum()