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

Day 16

2021/day_16.livemd

Day 16

Input

Mix.install([
  {:kino, github: "livebook-dev/kino"}
])
textarea = Kino.Input.textarea("Input:")

Common

defmodule Common do
  def parse_input(raw_input) do
    raw_input
    |> String.trim()
    |> Base.decode16!()
  end
end

defmodule Packet.Operator do
  @enforce_keys [:version, :type_id, :subpackets]
  defstruct [:version, :type_id, :subpackets]
end

defmodule Packet.Literal do
  @enforce_keys [:version, :value]
  defstruct [:version, :value]
end

defmodule Packet do
  import Bitwise
  alias __MODULE__.{Operator, Literal}

  def parse(<<>>), do: []

  def parse(<>) do
    {value, rest} = parse_literal_value(rest, 0)
    packet = %Literal{version: version, value: value}

    {packet, rest}
  end

  def parse(<>) do
    {subpackets, rest} =
      Enum.reduce(1..subpackets_count, {[], rest}, fn _, {acc, rest} ->
        {packet, rest} = parse(rest)
        {[packet | acc], rest}
      end)

    packet = %Operator{
      version: version,
      type_id: type_id,
      subpackets: Enum.reverse(subpackets)
    }

    {packet, rest}
  end

  def parse(<<
        version::3,
        type_id::3,
        0::1,
        subpackets_length::15,
        subpackets_bits::size(subpackets_length)-bits,
        rest::bits
      >>) do
    packet = %Operator{
      version: version,
      type_id: type_id,
      subpackets: parse_subpackets(subpackets_bits, [])
    }

    {packet, rest}
  end

  def values([]), do: []

  def values([packet | rest]) do
    [value(packet) | values(rest)]
  end

  def value(%Literal{value: value}), do: value

  def value(%Operator{type_id: type_id, subpackets: subpackets}) do
    values = values(subpackets)
    operator_value(type_id, values)
  end

  def versions_sum([]), do: 0

  def versions_sum([%Operator{version: version, subpackets: subpackets} | rest]),
    do: version + versions_sum(subpackets) + versions_sum(rest)

  def versions_sum([%Literal{version: version} | rest]),
    do: version + versions_sum(rest)

  # private

  defp operator_value(type_id, values)
  defp operator_value(0, values), do: Enum.sum(values)
  defp operator_value(1, values), do: Enum.product(values)
  defp operator_value(2, values), do: Enum.min(values)
  defp operator_value(3, values), do: Enum.max(values)
  defp operator_value(5, [lhs, rhs]) when lhs > rhs, do: 1
  defp operator_value(5, _values), do: 0
  defp operator_value(6, [lhs, rhs]) when lhs < rhs, do: 1
  defp operator_value(6, _values), do: 0
  defp operator_value(7, [same, same]), do: 1
  defp operator_value(7, _values), do: 0

  defp parse_subpackets("", acc), do: Enum.reverse(acc)

  defp parse_subpackets(bits, acc) do
    {packet, rest} = parse(bits)
    parse_subpackets(rest, [packet | acc])
  end

  defp parse_literal_value(<<1::1, part::4, rest::bits>>, acc),
    do: parse_literal_value(rest, increment_acc(acc, part))

  defp parse_literal_value(<<0::1, part::4, rest::bits>>, acc),
    do: {increment_acc(acc, part), rest}

  defp increment_acc(acc, part), do: (acc <<< 4) + part
end
raw_input = Kino.Input.read(textarea)
input = Common.parse_input(raw_input)

Part 1

defmodule Part1 do
  def run(input) do
    {packet, _rest} = Packet.parse(input)

    Packet.versions_sum([packet])
  end
end
Part1.run(input)

Part 2

defmodule Part2 do
  def run(input) do
    {packet, _rest} = Packet.parse(input)

    Packet.value(packet)
  end
end
Part2.run(input)