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

Day 03

2023/day-03.livemd

Day 03

Mix.install([
  {:kino, "~> 0.11"},
  {:nimble_parsec, "~> 1.4"}
])

example_input =
  Kino.Input.textarea("example input:")
  |> Kino.render()

real_input = Kino.Input.textarea("real input:")

Common

defmodule Parser do
  import NimbleParsec

  number = integer(min: 1) |> unwrap_and_tag(:number)
  period = string(".") |> replace(:period)
  symbol = ascii_char([{:not, ?\n}]) |> unwrap_and_tag(:symbol)

  char = choice([period, number, symbol])

  line =
    times(char, min: 1)
    |> ignore(ascii_char([?\n]))
    |> wrap()

  lines = times(line, min: 1)

  defparsec(:parse, lines |> eos() |> reduce(:reducer))

  defp reducer(lines) do
    {acc, symbols, gears, _} =
      for {line, i} <- Enum.with_index(lines),
          {char, reset_if_zero} <- Enum.with_index(line),
          reduce: {[], [], [], 0} do
        {numbers, symbols, gears, j} ->
          j = if reset_if_zero == 0, do: 0, else: j

          case char do
            :period ->
              {numbers, symbols, gears, j + 1}

            {:number, number} ->
              length = length(Integer.digits(number))

              coords =
                for j <- j..(j + length - 1), do: {i, j}

              {[{number, coords} | numbers], symbols, gears, j + length}

            {:symbol, ?*} ->
              {numbers, [{i, j} | symbols], [{i, j} | gears], j + 1}

            {:symbol, _} ->
              {numbers, [{i, j} | symbols], gears, j + 1}
          end
      end

    {acc, symbols, gears}
  end
end

parse = fn input ->
  input
  |> Kino.Input.read()
  |> Parser.parse()
  |> case do
    {:ok, [acc], "", _, _, _} -> acc
  end
end

neighbors = fn {i, j} ->
  for x <- (i - 1)..(i + 1), y <- (j - 1)..(j + 1), x != i or y != j, do: {x, y}
end

neighbor? = fn coord, list ->
  neighbors.(coord)
  |> Enum.any?(&amp;(&amp;1 in list))
end

Part 1

{numbers, symbols, _} =
  real_input
  |> then(parse)

numbers
|> Enum.filter(fn {_, coords} ->
  coords
  |> Enum.any?(&amp;neighbor?.(&amp;1, symbols))
end)
|> Enum.map(&amp;elem(&amp;1, 0))
|> Enum.sum()

Part 2

{numbers, _, gears} =
  real_input
  |> then(parse)

gears
|> Enum.reduce(0, fn gear, acc ->
  case Enum.filter(numbers, fn {_, coords} ->
         Enum.any?(coords, &amp;neighbor?.(&amp;1, [gear]))
       end) do
    [{a, _}, {b, _}] -> acc + a * b
    _ -> acc
  end
end)