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

Day 18

lib/day18.livemd

Day 18

Setup

sample = """
[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]
[[[5,[2,8]],4],[5,[[9,9],0]]]
[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]
[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]
[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]
[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]
[[[[5,4],[7,7]],8],[[8,3],8]]
[[9,3],[[9,9],[6,[4,9]]]]
[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]
[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]
"""

input = """
[[5,[[8,5],8]],[[9,3],[0,3]]]
[[[8,[3,6]],[0,[1,2]]],[[[4,1],4],[7,[5,8]]]]
[[[[2,0],6],[4,8]],[8,[3,0]]]
[[[[8,2],[8,3]],[[0,9],5]],3]
[[[[4,4],[2,3]],8],[[1,[3,8]],[8,4]]]
[[[2,[1,0]],[[3,2],0]],[9,2]]
[[[0,4],[[9,8],[6,6]]],3]
[[[6,[6,2]],[[4,5],3]],[0,7]]
[[[[2,8],4],[[1,2],[3,8]]],9]
[[[3,[7,6]],[9,9]],[[8,9],[[6,2],[3,4]]]]
[0,[4,[[4,1],8]]]
[[[[3,3],[8,7]],[9,7]],[[7,6],4]]
[[1,[[0,6],4]],[[5,[3,3]],6]]
[[[0,4],7],4]
[[[2,[1,7]],8],[6,[2,[8,7]]]]
[[[9,7],[[2,5],[7,9]]],[1,[4,[8,6]]]]
[[[[3,7],6],[[1,6],[5,4]]],[0,[6,2]]]
[9,5]
[[[[6,1],[4,5]],[[4,2],8]],[[5,[5,1]],[[7,3],6]]]
[9,[9,[[7,4],[6,9]]]]
[3,[[9,2],9]]
[[[[1,7],6],[3,[9,7]]],[[6,[4,3]],[[5,6],5]]]
[[[[0,6],[7,9]],[[2,8],2]],[5,[3,[4,9]]]]
[0,[[[1,8],7],[9,4]]]
[5,[0,[[4,5],6]]]
[[[[7,9],[4,9]],[7,[7,5]]],[6,[[1,6],[8,7]]]]
[[[4,[0,1]],[[9,0],[8,1]]],[[0,7],[5,[0,4]]]]
[0,1]
[[[[7,7],[0,7]],8],[[3,[4,2]],[6,6]]]
[[[[7,8],[4,3]],[7,[7,0]]],[3,[2,3]]]
[[[[6,9],[3,2]],[[4,7],2]],[[3,[7,5]],[[3,3],3]]]
[7,[[[6,7],[1,3]],9]]
[[[[2,9],[2,1]],[3,[9,9]]],[1,[[0,7],[2,4]]]]
[[7,4],[[[0,0],5],[[2,4],5]]]
[[[[2,9],8],[3,4]],[6,[[8,7],[4,3]]]]
[[[8,3],0],[4,[[6,7],5]]]
[[[6,[4,1]],[[1,1],[0,4]]],[[[6,2],[8,6]],[5,2]]]
[9,[[[5,6],0],[[7,2],3]]]
[7,[[[6,6],[1,7]],8]]
[[1,[9,[6,2]]],[[4,0],[[7,7],[4,2]]]]
[[[5,[5,9]],[0,[7,2]]],[[3,3],6]]
[[5,[3,6]],[[0,[8,4]],[6,[5,5]]]]
[[[0,5],[[8,7],[0,3]]],[[[4,1],[6,2]],[[3,2],[2,7]]]]
[[[6,9],5],[[7,3],[[5,0],[2,2]]]]
[[3,5],[[1,[3,4]],[2,[5,3]]]]
[[9,9],[4,6]]
[[[[6,4],[3,7]],[3,8]],[3,[2,[3,7]]]]
[[4,[[1,1],6]],[7,[[1,1],6]]]
[[[[6,4],3],9],2]
[[[[8,1],3],7],[4,[9,1]]]
[4,[[[7,7],6],8]]
[[[7,5],[8,1]],[[6,[6,5]],[6,7]]]
[[8,[3,[1,3]]],[2,[[6,1],[0,5]]]]
[9,[[8,6],0]]
[[8,[1,5]],[[[6,4],6],1]]
[2,[[3,[4,6]],[[2,9],[6,4]]]]
[[[[0,9],[2,0]],[[2,4],7]],[[[7,1],3],[7,9]]]
[[[3,6],[[6,6],1]],[[[0,5],[6,8]],5]]
[[[4,5],[[5,1],0]],[3,[[3,1],[2,8]]]]
[[[[9,0],[7,6]],5],[6,[[0,3],1]]]
[[[1,4],[5,7]],[[9,[3,8]],3]]
[[[7,7],1],[[[5,0],[4,0]],8]]
[[[[0,9],[0,6]],[[5,8],[7,4]]],2]
[[[[0,2],1],[[4,8],0]],[4,[[8,7],[9,1]]]]
[[[1,[2,0]],[[8,4],[0,0]]],5]
[[[9,[8,1]],[[1,1],[4,2]]],[9,[7,[6,9]]]]
[[[[0,2],[1,5]],[[9,2],[8,7]]],[[6,8],[6,0]]]
[[[3,[6,7]],[[9,8],[6,9]]],[8,[[4,6],5]]]
[[[9,[1,5]],[[4,8],9]],2]
[[[0,[1,5]],0],0]
[[[[4,1],4],[4,[7,4]]],[[[3,9],9],3]]
[[[9,7],[[8,7],[0,0]]],[[[0,0],3],3]]
[[9,[[2,0],6]],[[8,6],[5,4]]]
[2,[6,1]]
[[7,[1,[9,5]]],[[[7,8],[1,0]],[6,3]]]
[[[[2,3],1],[7,3]],[[[1,5],[2,2]],[[6,3],7]]]
[4,6]
[[[[4,0],1],2],[[[0,5],8],[8,[0,4]]]]
[[5,[7,0]],[[[4,5],[0,2]],5]]
[[[5,[3,1]],[[8,4],[4,9]]],[2,[[4,8],9]]]
[[[0,7],2],[[[2,5],8],[0,[5,3]]]]
[[[[2,2],[8,1]],[8,[1,3]]],[6,7]]
[[[9,2],[[4,8],[7,1]]],[[[5,2],7],[5,8]]]
[[[2,8],[[3,6],[8,3]]],[[0,5],6]]
[[[3,[7,6]],[4,[5,2]]],6]
[[7,[[5,2],8]],[1,[8,[8,3]]]]
[[[[8,9],7],[[1,1],0]],[[3,6],[[7,8],9]]]
[[4,[[4,2],[7,9]]],[[8,9],[8,8]]]
[[[5,5],[9,[0,7]]],[[[5,8],8],4]]
[[8,[[4,4],[0,0]]],[[2,1],[[2,5],3]]]
[[6,[[4,3],[1,6]]],0]
[[[4,[1,6]],2],[[0,7],1]]
[[[6,[9,9]],[4,8]],[[[1,1],9],[4,[1,7]]]]
[[[[2,1],6],[[3,8],[2,2]]],[9,[7,6]]]
[[0,[[1,0],9]],[8,[0,6]]]
[[[8,[3,4]],[[6,7],[9,9]]],[[7,[6,8]],[[7,7],[6,8]]]]
[4,[[[4,5],[4,4]],[5,[9,0]]]]
[[[[8,2],7],[6,5]],2]
[[9,7],[4,[[5,3],7]]]
[[[[6,5],0],1],[[[5,8],[3,9]],[[9,4],[8,3]]]]
"""

Part a

defmodule Day18 do
  # SHARED
  def any_true_in_tree?(bool) when is_boolean(bool), do: bool
  def any_true_in_tree?(x), do: x |> List.flatten() |> Enum.any?()

  def number_to_false(digit) when is_integer(digit), do: false
  def number_to_false([x, y]), do: [number_to_false(x), number_to_false(y)]

  # MAGNITUDE, SUM, and REDUCE
  def magnitude(number) when is_integer(number), do: number
  def magnitude([x, y]), do: 3 * magnitude(x) + 2 * magnitude(y)

  def sum_and_reduce(a, b), do: reduce([a, b])

  def reduce(number) do
    # IO.puts "Reducing: #{inspect(number, charlists: :as_lists)}"
    case {any_true_in_tree?(first_exploding_pair(number, 0)),
          any_true_in_tree?(first_splitting_pair(number))} do
      {true, _} -> reduce(explode(number))
      {_, true} -> reduce(split(number))
      _ -> number
    end
  end

  # EXPLODE
  def first_exploding_pair(digit, _) when is_integer(digit), do: false
  def first_exploding_pair(_pair, nesting_level) when nesting_level >= 4, do: true

  def first_exploding_pair([x, y], nesting_level) do
    left = first_exploding_pair(x, nesting_level + 1)

    if any_true_in_tree?(left) do
      [left, number_to_false(y)]
    else
      [left, first_exploding_pair(y, nesting_level + 1)]
    end
  end

  def explode(pair) do
    # explode(pair, first_exploding_pair(pair, 0))
    {new_pair, _, _} = explode(pair, first_exploding_pair(pair, 0))
    new_pair
  end

  def explode([x, y], true), do: {0, x, y}

  def explode([x, y], [s1, s2]) do
    if any_true_in_tree?(s1) do
      # Left branch explodes
      {new_x, left, right} = explode(x, s1)
      {[new_x, add_left(y, right)], left, 0}
    else
      # Right branch explodes
      {new_y, left, right} = explode(y, s2)
      {[add_right(x, left), new_y], 0, right}
    end
  end

  def add_left(digit, add) when is_integer(digit), do: digit + add
  def add_left([x, y], add), do: [add_left(x, add), y]

  def add_right(digit, add) when is_integer(digit), do: digit + add
  def add_right([x, y], add), do: [x, add_right(y, add)]

  # SPLIT
  def first_splitting_pair(digit) when is_integer(digit), do: digit >= 10

  def first_splitting_pair([x, y]) do
    left = first_splitting_pair(x)

    if any_true_in_tree?(left) do
      [left, number_to_false(y)]
    else
      [left, first_splitting_pair(y)]
    end
  end

  def split(x), do: split(x, first_splitting_pair(x))

  def split(x, true) when is_integer(x), do: [div(x, 2), x - div(x, 2)]
  def split(x, false) when is_integer(x), do: x
  def split([x, y], [s1, s2]), do: [split(x, s1), split(y, s2)]
end

# EXPLODE SAMPLES
if false do
  IO.puts(inspect(Day18.explode([[[[[9, 8], 1], 2], 3], 4])))
  IO.puts(Day18.explode([[[[[9, 8], 1], 2], 3], 4]) == [[[[0, 9], 2], 3], 4])
  IO.puts(inspect(Day18.explode([7, [6, [5, [4, [3, 2]]]]])))
  IO.puts(Day18.explode([7, [6, [5, [4, [3, 2]]]]]) == [7, [6, [5, [7, 0]]]])
  IO.puts(inspect(Day18.explode([[6, [5, [4, [3, 2]]]], 1])))
  IO.puts(Day18.explode([[6, [5, [4, [3, 2]]]], 1]) == [[6, [5, [7, 0]]], 3])
  IO.puts(inspect(Day18.explode([[3, [2, [1, [7, 3]]]], [6, [5, [4, [3, 2]]]]])))

  IO.puts(
    Day18.explode([[3, [2, [1, [7, 3]]]], [6, [5, [4, [3, 2]]]]]) == [
      [3, [2, [8, 0]]],
      [9, [5, [4, [3, 2]]]]
    ]
  )

  IO.puts(inspect(Day18.explode([[3, [2, [8, 0]]], [9, [5, [4, [3, 2]]]]])))

  IO.puts(
    Day18.explode([[3, [2, [8, 0]]], [9, [5, [4, [3, 2]]]]]) == [
      [3, [2, [8, 0]]],
      [9, [5, [7, 0]]]
    ]
  )
end

# SPLIT SAMPLES
if false do
  IO.puts(inspect(Day18.split([[[[0, 7], 4], [15, [0, 13]]], [1, 1]]), charlists: :as_lists))

  IO.puts(
    Day18.split([[[[0, 7], 4], [15, [0, 13]]], [1, 1]]) == [
      [[[0, 7], 4], [[7, 8], [0, 13]]],
      [1, 1]
    ]
  )

  IO.puts(inspect(Day18.split([[[[0, 7], 4], [[7, 8], [0, 13]]], [1, 1]]), charlists: :as_lists))

  IO.puts(
    Day18.split([[[[0, 7], 4], [[7, 8], [0, 13]]], [1, 1]]) == [
      [[[0, 7], 4], [[7, 8], [0, [6, 7]]]],
      [1, 1]
    ]
  )
end

# REDUCE SAMPLES
if false do
  IO.puts(
    inspect(Day18.reduce([[[[[4, 3], 4], 4], [7, [[8, 4], 9]]], [1, 1]]), charlists: :as_lists)
  )

  IO.puts(
    Day18.reduce([[[[[4, 3], 4], 4], [7, [[8, 4], 9]]], [1, 1]]) == [
      [[[0, 7], 4], [[7, 8], [6, 0]]],
      [8, 1]
    ]
  )
end

# SUM SAMPLES
# "[[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]]" |> Code.eval_string() |> elem(0)

input
|> String.split("\n", trim: true)
|> Enum.map(fn line -> line |> Code.eval_string() |> elem(0) end)
|> Enum.reduce(fn x, acc -> Day18.sum_and_reduce(acc, x) end)
|> Day18.magnitude()

Part b

numbers =
  input
  |> String.split("\n", trim: true)
  |> Enum.map(fn line -> line |> Code.eval_string() |> elem(0) end)

for line_1 <- numbers,
    line_2 <- numbers do
  Day18.sum_and_reduce(line_1, line_2) |> Day18.magnitude()
end
|> Enum.max()