Powered by AppSignal & Oban Pro

Day16 - BitString+AST

2021/day16ast.livemd

Day16 - BitString+AST

Untitled

data =
  "input"
  |> IO.getn(1_000_000)
  |> String.trim()
  |> :binary.decode_hex()
defmodule D16 do
  def parse(data) do
    {data, %{}}
    |> parse_meta(:version)
    |> parse_meta(:type)
    |> parse_by_type()
  end

  def parse_meta({<>, meta}, meta_key) do
    {rest, Map.put(meta, meta_key, meta_value)}
  end

  def parse_by_type({data, %{type: 4} = meta}) do
    {literal, rest} = parse_literal(data)
    {{bits_to_int(literal), rest}, meta}
  end

  def parse_by_type({data, meta}) do
    {parse_operator(data), meta}
  end

  def parse_literal(<<1::1, n::4, rest::bitstring>>) do
    {data, rest} = parse_literal(rest)
    {<>, rest}
  end

  def parse_literal(<<0::1, n::4, rest::bitstring>>) do
    {<>, rest}
  end

  def parse_operator(<<0::1, length::15, rest::bitstring>>) do
    <> = rest
    {parse_sub(payload), rest}
  end

  def parse_operator(<<1::1, length::11, rest::bitstring>>) do
    {data, [rest]} = Enum.split(parse_sub(rest, length), -1)
    {data, rest}
  end

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

  def parse_sub(data) do
    {{data, rest}, meta} = parse(data)
    [{{data, []}, meta} | parse_sub(rest)]
  end

  def parse_sub(rest, 0), do: [rest]

  def parse_sub(data, n) do
    {{data, rest}, meta} = parse(data)
    [{{data, []}, meta} | parse_sub(rest, n - 1)]
  end

  def solve1({{data, _rest}, meta}) when is_list(data) do
    meta.version + (data |> Enum.map(&amp;solve1/1) |> Enum.sum())
  end

  def solve1({{_data, _rest}, meta}) do
    meta.version
  end

  def solve2({{data, _}, %{type: 0}}) do
    data |> Enum.map(&amp;solve2/1) |> unroll(:+)
  end

  def solve2({{data, _}, %{type: 1}}) do
    data |> Enum.map(&amp;solve2/1) |> unroll(:*)
  end

  def solve2({{data, _}, %{type: 2}}) do
    data |> Enum.map(&amp;solve2/1) |> unroll(:min)
  end

  def solve2({{data, _}, %{type: 3}}) do
    data |> Enum.map(&amp;solve2/1) |> unroll(:max)
  end

  def solve2({{data, _}, %{type: 4}}) do
    data
  end

  def solve2({{data, _}, %{type: 5}}) do
    data |> Enum.map(&amp;solve2/1) |> branch(:>)
  end

  def solve2({{data, _}, %{type: 6}}) do
    data |> Enum.map(&amp;solve2/1) |> branch(:<)
  end

  def solve2({{data, _}, %{type: 7}}) do
    data |> Enum.map(&amp;solve2/1) |> branch(:==)
  end

  defp branch([left, right], op) do
    {:||, [],
     [
       {:&amp;&amp;, [],
        [
          {op, [], [left, right]},
          1
        ]},
       0
     ]}
  end

  defp unroll([e1, e2], op) do
    {op, [], [e1, e2]}
  end

  defp unroll([e], _op) do
    e
  end

  defp unroll([e | l], op) do
    {op, [], [e, unroll(l, op)]}
  end

  defp bits_to_int(bits) do
    s = bit_size(bits)
    <> = bits
    int
  end
end
data
|> D16.parse()
|> D16.solve1()
data
|> D16.parse()
|> D16.solve2()
|> Code.eval_quoted()