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

Advent 2023 - Day 3

day3.livemd

Advent 2023 - Day 3

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

Section

input = Kino.Input.textarea("Please paste your input file")
input =
  input
  |> Kino.Input.read()
  |> String.split("\n")
  |> Enum.map(fn row ->
    row
    |> String.codepoints()
    |> Enum.map(fn cell ->
      case Integer.parse(cell) do
        {x, _} -> x
        :error -> cell
      end
    end)
    |> Enum.map(fn cell ->
      case cell do
        "." -> nil
        x -> x
      end
    end)
  end)
[
  [4, 6, 7, nil, nil, 1, 1, 4, nil, nil],
  [nil, nil, nil, "*", nil, nil, nil, nil, nil, nil],
  [nil, nil, 3, 5, nil, nil, 6, 3, 3, nil],
  [nil, nil, nil, nil, nil, nil, "#", nil, nil, nil],
  [6, 1, 7, "*", nil, nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, "+", nil, 5, 8, nil],
  [nil, nil, 5, 9, 2, nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, 7, 5, 5, nil],
  [nil, nil, nil, "$", nil, "*", nil, nil, nil, nil],
  [nil, 6, 6, 4, nil, 5, 9, 8, nil, nil]
]

Part 1

defmodule Schematic do
  def surronding(grid, {x, y}) do
    for xp <- -1..1 do
      for yp <- -1..1 do
        x = xp + x
        y = yp + y

        if (xp == 0 &amp;&amp; yp == 0) || x < 0 || y < 0 do
          {nil, {x, y}}
        else
          row = Enum.at(grid, y)

          if is_nil(row) do
            {nil, {x, y}}
          else
            {Enum.at(row, x), {x, y}}
          end
        end
      end
    end
  end

  def mark_seen(grid) do
    grid
    |> Enum.with_index()
    |> Enum.map(fn {row, y} ->
      row
      |> Enum.with_index()
      |> Enum.map(fn {cell, x} ->
        near_symbol =
          surronding(grid, {x, y})
          |> List.flatten()
          |> Enum.map(&amp;elem(&amp;1, 0))
          |> Enum.filter(&amp;is_bitstring(&amp;1))
          |> Enum.any?()

        {cell, near_symbol}
      end)
    end)
  end

  def smush_into_part_ids(row) do
    smush_into_part_ids(row, [], [])
    |> Enum.map(&amp;Enum.reverse(&amp;1))
  end

  def smush_into_part_ids([{num, symbol} | rest], cur_num, so_far) when is_number(num) do
    smush_into_part_ids(rest, [{num, symbol} | cur_num], so_far)
  end

  def smush_into_part_ids([_not_num | rest], cur_num, so_far) when length(cur_num) == 0 do
    smush_into_part_ids(rest, cur_num, so_far)
  end

  def smush_into_part_ids([_not_num | rest], cur_num, so_far) do
    smush_into_part_ids(rest, [], [cur_num | so_far])
  end

  def smush_into_part_ids([], cur_num, so_far) when length(cur_num) == 0 do
    so_far
  end

  def smush_into_part_ids([], cur_num, so_far) do
    [cur_num | so_far]
  end
end
{:module, Schematic, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:smush_into_part_ids, 3}}
Schematic.mark_seen(input)
|> Enum.map(&amp;Schematic.smush_into_part_ids(&amp;1))
|> Enum.concat()
|> Enum.filter(fn part_id ->
  Enum.any?(part_id, fn {_, symbol} -> symbol end)
end)
|> Enum.map(fn part_id ->
  part_id
  |> Enum.map(&amp;elem(&amp;1, 0))
  |> Enum.join("")
  |> Integer.parse()
  |> elem(0)
end)
|> Enum.sum()
4361

Part 2

defmodule GearSchematic do
  def mark_seen(grid) do
    grid
    |> Enum.with_index()
    |> Enum.map(fn {row, y} ->
      row
      |> Enum.with_index()
      |> Enum.map(fn {cell, x} ->
        near_symbols =
          Schematic.surronding(grid, {x, y})
          |> List.flatten()
          |> Enum.filter(fn {val, _} -> val == "*" end)

        has_near_symbols = Enum.any?(near_symbols)

        {cell, {has_near_symbols, near_symbols}}
      end)
    end)
  end
end
{:module, GearSchematic, <<70, 79, 82, 49, 0, 0, 10, ...>>, {:mark_seen, 1}}
parts =
  GearSchematic.mark_seen(input)
  |> Enum.map(&amp;Schematic.smush_into_part_ids(&amp;1))
  |> Enum.concat()
  |> Enum.filter(fn part_id ->
    Enum.any?(part_id, fn {_, {symbol, _}} -> symbol end)
  end)
  |> Enum.map(fn part_id ->
    number =
      part_id
      |> Enum.map(&amp;elem(&amp;1, 0))
      |> Enum.join("")
      |> Integer.parse()
      |> elem(0)

    matched_symbols =
      part_id
      |> Enum.filter(fn {_, {seen, _}} -> seen end)
      |> Enum.map(fn {_, {_, positions}} ->
        positions
        |> Enum.map(fn {_, pos} -> pos end)
      end)
      |> List.flatten()
      |> Enum.uniq()

    {number, matched_symbols}
  end)
[{467, [{3, 1}]}, {35, [{3, 1}]}, {617, [{3, 4}]}, {755, [{5, 8}]}, {598, [{5, 8}]}]
Enum.reduce(parts, %{}, fn {part_number, neighbors}, acc ->
  Enum.reduce(neighbors, acc, fn point, acc ->
    Map.get_and_update(acc, point, fn cur ->
      next =
        if is_nil(cur) do
          [part_number]
        else
          [part_number | cur]
        end

      {cur, next}
    end)
    |> elem(1)
  end)
end)
|> Map.filter(fn {_key, val} -> length(val) == 2 end)
|> Map.values()
|> Enum.map(fn [r, l] -> r * l end)
|> Enum.sum()
467835