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

Day 16

lib/day16.livemd

Day 16

Setup

samples_part_a = [
  "D2FE28",
  "38006F45291200",
  "EE00D40C823060",
  "8A004A801A8002F478",
  "620080001611562C8802118E34",
  "C0015000016115A2E0802F182340",
  "A0016C880162017C3686B18A3D4780"
]

samples_part_b = [
  "C200B40A82",
  "04005AC33890",
  "880086C3E88112",
  "CE00C43D881120",
  "D8005AC2A8F0",
  "F600BC2D8F",
  "9C005AC2F8F0",
  "9C0141080250320F1802104A08"
]

input =
  "020D708041258C0B4C683E61F674A1401595CC3DE669AC4FB7BEFEE840182CDF033401296F44367F938371802D2CC9801A980021304609C431007239C2C860400F7C36B005E446A44662A2805925FF96CBCE0033C5736D13D9CFCDC001C89BF57505799C0D1802D2639801A900021105A3A43C1007A1EC368A72D86130057401782F25B9054B94B003013EDF34133218A00D4A6F1985624B331FE359C354F7EB64A8524027D4DEB785CA00D540010D8E9132270803F1CA1D416200FDAC01697DCEB43D9DC5F6B7239CCA7557200986C013912598FF0BE4DFCC012C0091E7EFFA6E44123CE74624FBA01001328C01C8FF06E0A9803D1FA3343E3007A1641684C600B47DE009024ED7DD9564ED7DD940C017A00AF26654F76B5C62C65295B1B4ED8C1804DD979E2B13A97029CFCB3F1F96F28CE43318560F8400E2CAA5D80270FA1C90099D3D41BE00DD00010B893132108002131662342D91AFCA6330001073EA2E0054BC098804B5C00CC667B79727FF646267FA9E3971C96E71E8C00D911A9C738EC401A6CBEA33BC09B8015697BB7CD746E4A9FD4BB5613004BC01598EEE96EF755149B9A049D80480230C0041E514A51467D226E692801F049F73287F7AC29CB453E4B1FDE1F624100203368B3670200C46E93D13CAD11A6673B63A42600C00021119E304271006A30C3B844200E45F8A306C8037C9CA6FF850B004A459672B5C4E66A80090CC4F31E1D80193E60068801EC056498012804C58011BEC0414A00EF46005880162006800A3460073007B620070801E801073002B2C0055CEE9BC801DC9F5B913587D2C90600E4D93CE1A4DB51007E7399B066802339EEC65F519CF7632FAB900A45398C4A45B401AB8803506A2E4300004262AC13866401434D984CA4490ACA81CC0FB008B93764F9A8AE4F7ABED6B293330D46B7969998021C9EEF67C97BAC122822017C1C9FA0745B930D9C480"

Part a

defmodule Day16 do
  @hex_dict """
  0 = 0000
  1 = 0001
  2 = 0010
  3 = 0011
  4 = 0100
  5 = 0101
  6 = 0110
  7 = 0111
  8 = 1000
  9 = 1001
  A = 1010
  B = 1011
  C = 1100
  D = 1101
  E = 1110
  F = 1111
  """

  # Part A
  def decode(hex_string) do
    hex_string
    |> hex_to_binary
    |> parse_transmission()
    |> tap(&IO.puts(inspect(&1)))
  end

  def sum_versions([]), do: 0

  def sum_versions([%{version: version, sub_packets: sub_packets} | maps]) do
    version + sum_versions(sub_packets) + sum_versions(maps)
  end

  def sum_versions([%{version: version} | maps]) do
    version + sum_versions(maps)
  end

  def parse_transmission(transmission), do: parse_transmission(transmission, [])

  def parse_transmission("", array_of_maps), do: array_of_maps

  def parse_transmission(transmission, array_of_maps) do
    # IO.puts("Parsing transmission: #{inspect(transmission)}")

    if bin_to_hex(transmission) == 0 do
      array_of_maps
    else
      {rest, map} = parse_packet(transmission)
      parse_transmission(rest, array_of_maps ++ [map])
    end
  end

  def parse_n_packets(transmission, n), do: parse_n_packets(transmission, n, [])
  def parse_n_packets(transmission, 0, maps), do: {transmission, maps}

  def parse_n_packets(transmission, n, maps) do
    {rest, map} = parse_packet(transmission)
    parse_n_packets(rest, n - 1, maps ++ [map])
  end

  def parse_packet(packet) do
    packet
    |> parse_version()
    |> parse_type_id()
    |> parse_payload()
  end

  def hex_to_bin(hex) do
    @hex_dict
    |> String.split(["\n", " = "], trim: true)
    |> Enum.chunk_every(2)
    |> Enum.reduce(%{}, fn [head | tail], acc ->
      Map.put(acc, head, hd(tail))
    end)
    |> Map.fetch!(hex)
  end

  def bin_to_hex(bin) do
    Integer.parse(bin, 2) |> elem(0)
  end

  def hex_to_binary(string), do: hex_to_binary(string, "")
  def hex_to_binary("", response), do: response

  def hex_to_binary(<>, response) do
    hex_to_binary(rest, response <> hex_to_bin(<<a>>))
  end

  def parse_version(<>) do
    {rest, Map.put(%{}, :version, bin_to_hex(version))}
  end

  def parse_type_id({<>, map}) do
    {rest, Map.put(map, :type_id, bin_to_hex(type_id))}
  end

  def parse_payload({payload, %{type_id: 4} = map}) do
    parse_literal(payload, map)
  end

  def parse_payload({payload, map}) do
    parse_operator(payload, map)
  end

  def parse_literal(string, map), do: parse_literal(string, "", map)

  def parse_literal(<<"0", payload::binary>>, response, map) do
    <> = payload
    {rest_of_transmission, Map.put(map, :literal, bin_to_hex(response <> bin))}
  end

  def parse_literal(<<"1", payload::binary>>, response, map) do
    <> = payload
    parse_literal(rest, response <> bin, map)
  end

  def parse_operator(<<"0", payload::binary>>, map) do
    <> = payload
    bits = bin_to_hex(length_in_bits)
    <> = rest
    parsed_sub_packets = parse_transmission(sub_packets)

    new_map =
      map
      # |> Map.put(:length_in_bits, bits)
      |> Map.put(:sub_packets, parsed_sub_packets)

    {rest_of_transmission, new_map}
  end

  def parse_operator(<<"1", payload::binary>>, map) do
    <> = payload
    packets = bin_to_hex(length_in_packets)

    {rest_of_transmission, parsed_sub_packets} = parse_n_packets(rest, packets)

    new_map =
      map
      # |> Map.put(:length_in_packets, packets)
      |> Map.put(:sub_packets, parsed_sub_packets)

    {rest_of_transmission, new_map}
  end

  # Part B
  def calculate(%{literal: literal}), do: literal

  def calculate(%{type_id: 0, sub_packets: sub_packets}) do
    case length(sub_packets) do
      1 -> calculate(sub_packets)
      _ -> Enum.sum(calculate(sub_packets))
    end
  end

  def calculate(%{type_id: 1, sub_packets: sub_packets}) do
    case length(sub_packets) do
      1 -> calculate(sub_packets)
      _ -> Enum.product(calculate(sub_packets))
    end
  end

  def calculate(%{type_id: 2, sub_packets: sub_packets}) do
    case length(sub_packets) do
      1 -> calculate(sub_packets)
      _ -> Enum.min(calculate(sub_packets))
    end
  end

  def calculate(%{type_id: 3, sub_packets: sub_packets}) do
    case length(sub_packets) do
      1 -> calculate(sub_packets)
      _ -> Enum.max(calculate(sub_packets))
    end
  end

  def calculate(%{type_id: other, sub_packets: sub_packets}) do
    compare(
      sub_packets |> calculate |> Enum.at(0),
      sub_packets |> calculate |> Enum.at(1),
      other
    )
  end

  def calculate([map]), do: calculate(map)
  def calculate(maps), do: maps |> Enum.map(&amp;calculate/1)

  def compare(a, b, 5) do
    if a > b do
      1
    else
      0
    end
  end

  def compare(a, b, 6) do
    if a < b do
      1
    else
      0
    end
  end

  def compare(a, b, 7) do
    if a == b do
      1
    else
      0
    end
  end
end

# for sample <- samples_part_a do
#   IO.puts("### Sample is: #{sample}")
#   IO.puts("### Sum of versions is: #{inspect(sample |> Day16.decode() |> Day16.sum_versions())}")
# end

input |> Day16.decode() |> Day16.sum_versions()

Part b

# for sample <- samples_part_b do
#   IO.puts("### Sample is: #{sample}")
#   IO.puts("### Calculations is: #{inspect(Day16.decode(sample) |> Day16.calculate())}")
# end

input |> Day16.decode() |> Day16.calculate()