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

2022 Day 13

day_13.livemd

2022 Day 13

Mix.install([:kino])

Input

Run in Livebook

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

Code

Module

defmodule Puzzle do
  @dividers [[[2]], [[6]]]

  def part_one(input) do
    input
    |> process_input()
    |> process_list_pairs()
    |> Enum.with_index(1)
    |> Keyword.get_values(true)
    |> Enum.sum()
  end

  def part_two(input) do
    input
    |> process_input()
    |> process_signal_packets()
    |> locate_dividers()
    |> Enum.product()
    |> dbg()
  end

  def sorter(left, right) do
    process_list_pair({left, right})
  end

  defp process_signal_packets(list_pairs) do
    list_pairs
    |> Enum.reduce([], fn {left, right}, acc -> [left | [right | acc]] end)
    |> Enum.concat(@dividers)
    |> Enum.sort(&sorter/2)
  end

  defp locate_dividers(sorted_packets) do
    for divider <- @dividers do
      Enum.find_index(sorted_packets, fn packet -> packet == divider end)
    end
    # 1-order indexing
    |> Enum.map(&amp;(&amp;1 + 1))
  end

  defp process_list_pairs(list_pairs) do
    Enum.reduce(list_pairs, [], fn pair, acc ->
      [process_list_pair(pair) | acc]
    end)
    |> Enum.reverse()
  end

  defp process_list_pair(list_pair) do
    {signal, acc} = compare_lists(list_pair)

    case signal do
      :halt ->
        acc

      :cont ->
        process_list_pair(acc)
    end
  end

  defp compare_lists({[], []}), do: {:cont, {[], []}}
  defp compare_lists({[], _}), do: {:halt, true}
  defp compare_lists({_, []}), do: {:halt, false}

  defp compare_lists({[l | l_tail], [r | r_tail]}) when is_list(l) and is_integer(r) do
    compare_lists({[l | l_tail], [[r] | r_tail]})
  end

  defp compare_lists({[l | l_tail], [r | r_tail]}) when is_integer(l) and is_list(r) do
    compare_lists({[[l] | l_tail], [r | r_tail]})
  end

  defp compare_lists({[l | l_tail], [r | r_tail]}) when is_integer(l) and is_integer(r) do
    cond do
      l < r ->
        {:halt, true}

      l > r ->
        {:halt, false}

      l == r ->
        {:cont, {l_tail, r_tail}}
    end
  end

  defp compare_lists({[l | l_tail], [r | r_tail]}) when is_list(l) and is_list(r) do
    case compare_lists({l, r}) do
      {:halt, bool} ->
        {:halt, bool}

      {:cont, {[], []}} ->
        {:cont, {l_tail, r_tail}}

      {:cont, {l_rem, r_rem}} ->
        {:cont, {[l_rem | l_tail], [r_rem | r_tail]}}
    end
  end

  defp process_input(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&amp;Code.eval_string/1)
    |> Enum.map(&amp;elem(&amp;1, 0))
    |> Enum.chunk_every(2)
    |> Enum.map(&amp;List.to_tuple/1)
  end
end

Evaluate

part_1 =
  input
  |> Kino.Input.read()
  |> Puzzle.part_one()

part_2 =
  input
  |> Kino.Input.read()
  |> Puzzle.part_two()

{part_1, part_2}

Test

ExUnit.start(autorun: false)

defmodule PuzzleTest do
  use ExUnit.Case, async: true

  setup do
    input = ~s(
[1,1,3,1,1]
[1,1,5,1,1]

[[1],[2,3,4]]
[[1],4]

[9]
[[8,7,6]]

[[4,4],4,4]
[[4,4],4,4,4]

[7,7,7,7]
[7,7,7]

[]
[3]

[[[]]]
[[]]

[1,[2,[3,[4,[5,6,7]]]],8,9]
[1,[2,[3,[4,[5,6,0]]]],8,9]
)

    {:ok, input: input}
  end

  test "part_one", %{input: input} do
    # right order: 1, 2, 4, 6
    assert Puzzle.part_one(input) == 13
  end

  test "part_two", %{input: input} do
    assert Puzzle.part_two(input) == 140
  end
end

ExUnit.run()