Powered by AppSignal & Oban Pro

2023 - day 3

2023/elixir/day-3.livemd

2023 - day 3

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

Section

sample_input = """
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
"""
puzzle_input = Kino.Input.textarea("input")
puzzle_input = Kino.Input.read(puzzle_input)

Part1

defmodule Day3.Part1 do
  def solve(input) do
    input
    |> parse()
    |> do_solve()
  end

  def do_solve(input_map) do
    for {{r, _c}, {num, {st, ed}}} <- input_map,
        included?((r - 1)..(r + 1), (st - 1)..(ed + 1), input_map) do
      num
    end
    |> Enum.sum()
  end

  defp included?(r_range, c_range, input_map) do
    for rr <- r_range, cc <- c_range, rr >= 0 and cc >= 0 do
      {rr, cc}
    end
    |> Enum.any?(&is_binary(input_map[&1]))
  end

  def parse(input) do
    input
    |> String.split("\n")
    |> Enum.with_index()
    |> Enum.reduce(%{}, fn {line, ri}, acc ->
      Map.merge(acc, parse_line(line, ri, 0))
    end)
  end

  defp parse_line(line, ri, ci, acc \\ %{}, state \\ :none)

  defp parse_line("", ri, ci, acc, state) do
    case state do
      :none ->
        acc

      {:num, num, start} ->
        Map.put(acc, {ri, start}, {num, {start, ci - 1}})
    end
  end

  defp parse_line(line, ri, ci, acc, :none) do
    case line do
      <<n::integer, rest::binary>> when n in ?0..?9 ->
        parse_line(rest, ri, ci + 1, acc, {:num, n - ?0, ci})

      "." <> rest ->
        parse_line(rest, ri, ci + 1, acc, :none)

      <<symbol::utf8, rest::binary>> ->
        acc = Map.put(acc, {ri, ci}, <<symbol>>)
        parse_line(rest, ri, ci + 1, acc, :none)
    end
  end

  defp parse_line(line, ri, ci, acc, {:num, num, start}) do
    case line do
      <<n::integer, rest::binary>> when n in ?0..?9 ->
        parse_line(rest, ri, ci + 1, acc, {:num, num * 10 + (n - ?0), start})

      "." <> rest ->
        acc = Map.put(acc, {ri, start}, {num, {start, ci - 1}})
        parse_line(rest, ri, ci + 1, acc, :none)

      <<symbol::utf8, rest::binary>> ->
        acc = Map.put(acc, {ri, start}, {num, {start, ci - 1}})
        acc = Map.put(acc, {ri, ci}, <<symbol>>)
        parse_line(rest, ri, ci + 1, acc, :none)
    end
  end
end
# Day3.Part1.solve(sample_input)
Day3.Part1.solve(puzzle_input)

Part 2

defmodule Day3.Part2 do
  def solve(input) do
    input
    |> parse()
    |> do_solve()
  end

  def do_solve(input_map) do
    for {{r, c}, :multiple} <- input_map do
      get_adjacent_multi(r, c, input_map)
    end
    |> Enum.sum()
  end

  def get_adjacent_multi(r, c, input_map) do
    set =
      for ri <- (r - 1)..(r + 1),
          ci <- (c - 1)..(c + 1),
          ri >= 0 and ci >= 0,
          match?({_, {_, _}}, input_map[{ri, ci}]),
          {num, {st, ed}} = input_map[{ri, ci}],
          reduce: MapSet.new() do
        set ->
          MapSet.put(set, {num, st, ed})
      end

    case MapSet.to_list(set) do
      [{a, _, _}, {b, _, _}] -> a * b
      _ -> 0
    end
  end

  def parse(input) do
    input
    |> String.split("\n")
    |> Enum.with_index()
    |> Enum.reduce(%{}, fn {line, ri}, acc ->
      Map.merge(acc, parse_line(line, ri, 0))
    end)
  end

  defp parse_line(line, ri, ci, acc \\ %{}, state \\ :none)

  defp parse_line("", ri, ci, acc, state) do
    case state do
      :none ->
        acc

      {:num, num, start} ->
        Map.put(acc, {ri, start}, {num, {start, ci - 1}})
    end
  end

  defp parse_line(line, ri, ci, acc, :none) do
    case line do
      <<n::integer, rest::binary>> when n in ?0..?9 ->
        parse_line(rest, ri, ci + 1, acc, {:num, n - ?0, ci})

      "*" <> rest ->
        acc = Map.put(acc, {ri, ci}, :multiple)
        parse_line(rest, ri, ci + 1, acc, :none)

      <<_symbol::utf8, rest::binary>> ->
        parse_line(rest, ri, ci + 1, acc, :none)
    end
  end

  defp parse_line(line, ri, ci, acc, {:num, num, start}) do
    case line do
      <<n::integer, rest::binary>> when n in ?0..?9 ->
        parse_line(rest, ri, ci + 1, acc, {:num, num * 10 + (n - ?0), start})

      "*" <> rest ->
        acc = Map.put(acc, {ri, ci}, :multiple)

        acc =
          start..(ci - 1)
          |> Enum.reduce(acc, fn c, inner_acc ->
            Map.put(inner_acc, {ri, c}, {num, {start, ci - 1}})
          end)

        parse_line(rest, ri, ci + 1, acc, :none)

      <<_symbol::utf8, rest::binary>> ->
        acc =
          start..(ci - 1)
          |> Enum.reduce(acc, fn c, inner_acc ->
            Map.put(inner_acc, {ri, c}, {num, {start, ci - 1}})
          end)

        parse_line(rest, ri, ci + 1, acc, :none)
    end
  end
end
# Day3.Part2.solve(sample_input)
Day3.Part2.solve(puzzle_input)