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

Day19

2021/day19.livemd

Day19

Untitled

data =
  "input"
  |> IO.getn(1_000_000)
  |> String.trim()
  |> String.split(~r/^--- /m, trim: true)
  |> Enum.map(
    &(&1
      |> String.split(" ---", trim: true)
      |> then(fn ["scanner " <> scanner, beacons] ->
        {String.to_integer(scanner),
         beacons
         |> String.split(["\r\n", "\n"], trim: true)
         |> Enum.map(fn beacon ->
           beacon |> String.split(",") |> Enum.map(fn n -> String.to_integer(n) end)
         end)}
      end))
  )
  |> Map.new()
defmodule D19 do
  def prepare(data) do
    data
    |> Enum.map(fn {scanner, beacons} -> {scanner, {length(beacons), quadrants(beacons)}} end)
    |> Map.new()
  end

  @rs [
    &amp;D19.not_rotate/1,
    &amp;D19.rotate_x_cw/1,
    &amp;D19.rotate_y_cw/1,
    &amp;D19.rotate_z_cw/1,
    &amp;D19.rotate_x_ac/1,
    &amp;D19.rotate_y_ac/1,
    &amp;D19.rotate_z_ac/1
  ]

  def quadrants(beacons) do
    beacons
    |> Stream.map(fn beacon ->
      for r1 <- @rs, r2 <- @rs, r3 <- @rs do
        beacon |> r1.() |> r2.() |> r3.()
      end
      |> Enum.uniq()
    end)
    |> Stream.zip()
    |> Enum.map(&amp;Tuple.to_list/1)
  end

  def not_rotate(point), do: point
  def rotate_x_cw([x, y, z]), do: [x, z, -y]
  def rotate_y_cw([x, y, z]), do: [-z, y, x]
  def rotate_z_cw([x, y, z]), do: [y, -x, z]
  def rotate_x_ac([x, y, z]), do: [x, -z, y]
  def rotate_y_ac([x, y, z]), do: [z, y, -x]
  def rotate_z_ac([x, y, z]), do: [-y, x, z]

  def detect_connection(q1, qs2) do
    Enum.find_value(Enum.with_index(qs2), fn {q2, i2} ->
      for [x1, y1, z1] <- q1, [x2, y2, z2] <- q2 do
        {x1 - x2, y1 - y2, z1 - z2}
      end
      |> Enum.frequencies()
      |> Enum.find(&amp;(elem(&amp;1, 1) >= 12))
      |> then(fn
        nil -> nil
        {diff, count} -> {i2, diff, count}
      end)
    end)
  end

  def total({s1, s2, {_, _, count}}, {sum, seen}, data) do
    {sum + if(s1 in seen, do: 0, else: elem(data[s1], 0)) +
       if(s2 in seen, do: 0, else: elem(data[s2], 0)) - count,
     seen
     |> MapSet.put(s1)
     |> MapSet.put(s2)}
  end

  def solve1(data) do
    data = prepare(data)

    for {s1, {_, qs1}} <- data, {s2, {_, qs2}} <- data, s1 < s2 do
      {s1, s2, detect_connection(hd(qs1), qs2)}
    end
    |> Enum.reject(&amp;is_nil(elem(&amp;1, 2)))
    |> Enum.reduce({0, MapSet.new()}, &amp;total(&amp;1, &amp;2, data))
  end

  def connect([{s1, {_, qs1}} | rest], qi) do
    Enum.reduce(rest, qi, fn {s2, {_, qs2}} = item, qi ->
      with {{i1, [x1, y1, z1]}, nil} <- {qi[s1], qi[s2]},
           {i2, {x2, y2, z2}, _count} <- detect_connection(Enum.at(qs1, i1), qs2) do
        connect(
          [item | rest -- [item]],
          Map.put(qi, s2, {i2, [x1 + x2, y1 + y2, z1 + z2]})
        )
      else
        _ ->
          qi
      end
    end)
  end

  def solve2(data) do
    data = prepare(data)

    coords =
      data
      |> Enum.to_list()
      |> connect(%{0 => {0, [0, 0, 0]}})

    for {s1, {_, [x1, y1, z1]}} <- coords, {s2, {_, [x2, y2, z2]}} <- coords, s1 < s2 do
      abs(x2 - x1) + abs(y2 - y1) + abs(z2 - z1)
    end
    |> Enum.max()
  end
end

P1

D19.solve1(data)

P2

D19.solve2(data)