input = Kino.Input.textarea("Puzzle Input")
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(&(&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(&Code.eval_string/1)
|> Enum.map(&elem(&1, 0))
|> Enum.chunk_every(2)
|> Enum.map(&List.to_tuple/1)
end
end
part_1 =
input
|> Kino.Input.read()
|> Puzzle.part_one()
part_2 =
input
|> Kino.Input.read()
|> Puzzle.part_two()
{part_1, part_2}
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()