# Day 19 ## Section ```elixir # File.read!("day19.txt") input = File.read!("2021/day19.txt") |> String.split("\n\n") |> Enum.map(fn scanner -> scanner |> String.split("\n", trim: true) |> Enum.drop(1) |> Enum.map(fn coords -> coords |> String.split(",") |> Enum.map(&String.to_integer/1) |> Enum.zip(~w[x y z]a) |> Map.new(fn {v, k} -> {k, v} end) end) |> MapSet.new() end) length(input) ``` ``` 34 ``` To find rotation of point $b_1$ and $b_2$ around shift point $b_0$ $$ \left\{ \begin{alignat*}{3} {b_1}_x r_x + {b_0}_x = {a_1}_x \\ {b_2}_x r_x + {b_0}_x = {a_2}_x \end{alignat*} \right. \left\{ \begin{alignat*}{3} {b_1}_y r_y + {b_0}_y = {a_1}_y \\ {b_2}_y r_y + {b_0}_y = {a_2}_y \\ \end{alignat*} \right. \left\{ \begin{alignat*}{3} {b_1}_z r_z + {b_0}_z = {a_1}_z \\ {b_2}_z r_z + {b_0}_z = {a_2}_z \\ \end{alignat*} \right. $$ Where $$r_x, r_y, r_z \in \{-1, 1\}$$ and $$a_{xyz}, b_{xyz} = \text{const}$$. This mean that we need to solve: $$ B_x r_x + b_0 = a_x \\ B_y r_y + b_0 = a_y \\ B_z r_z + b_0 = a_z $$ Where: $$ B_d = \begin{bmatrix} {b_1}_d & 1 \\ {b_2}_d & 1 \\ \end{bmatrix} b_0 = \begin{bmatrix} {b_0}_d \\ {b_0}_d \end{bmatrix} a_d = \begin{bmatrix} {a_1}_d \\ {a_2}_d \end{bmatrix} $$ By applying [Cramer's Rule](https://en.wikipedia.org/wiki/Cramer%27s_rule) we can solve these linear equations with (we are looking for $b_0$ and $r$): $$ r_d = \frac{ \begin{vmatrix} {a_1}_d & 1 \\ {a_2}_d & 1 \end{vmatrix} }{ \begin{vmatrix} {b_1}_d & 1 \\ {b_2}_d & 1 \end{vmatrix} } = \frac{{a_1}_d - {a_2}_d}{{b_1}_d - {b_2}_d} \\ b_{d_0} = \frac{ \begin{vmatrix} {b_1}_d & {a_1}_d \\ {b_2}_d & {a_2}_d \end{vmatrix} }{ \begin{vmatrix} {b_1}_d & 1 \\ {b_2}_d & 1 \end{vmatrix} } = \frac{{b_1}_d {a_2}_d - {a_1}_d {b_2}_d}{{b_1}_d - {b_2}_d} $$ ```elixir defmodule Day19 do def rebase(base, pairs1) do pairs0 = pairs(base) # Take common points pa = Map.take(pairs0, Map.keys(pairs1)) pb = Map.take(pairs1, Map.keys(pairs0)) # Merge them [{{a, b, axes1}, {_, _, axes2}} = p0 | others] = Map.merge(pa, pb, fn _, a, b -> {a, b} end) |> Map.values() p1 = Enum.find(others, fn {{x, y, _}, _} -> a in [x, y] end) p2 = Enum.find(others, fn {{x, y, _}, _} -> b in [x, y] end) axes = axes_transformations(axes1, axes2) a_1 = a a_2 = b b_1 = reaxe(select_common(p0, p1), axes) b_2 = reaxe(select_common(p0, p2), axes) transform = for d <- ~w[x y z]a, into: %{} do a_1 = a_1[d] a_2 = a_2[d] b_1 = b_1[d] b_2 = b_2[d] det_b = b_1 - b_2 r = div(a_1 - a_2, det_b) b_0 = div(b_1 * a_2 - a_1 * b_2, det_b) {d, {r, b_0}} end new_points = pairs1 |> Map.values() |> Enum.flat_map(&[elem(&1, 0), elem(&1, 1)]) |> Enum.uniq() |> Enum.map(&reaxe(&1, axes)) |> do_rebase(transform) |> MapSet.new() %{x: {_, sx}, y: {_, sy}, z: {_, sz}} = transform scanner = %{x: sx, y: sy, z: sz} {new_points, scanner} end defp do_rebase(points, transform) do points |> Enum.map(fn p -> Map.merge(p, transform, fn _k, d, {r, b} -> d * r + b end) end) |> MapSet.new() end defp select_common(a, b) do case {a, b} do {{_, {_, x, _}}, {_, {_, x, _}}} -> x {{_, {x, _, _}}, {_, {x, _, _}}} -> x {{_, {_, x, _}}, {_, {x, _, _}}} -> x {{_, {x, _, _}}, {_, {_, x, _}}} -> x end end defp axes_transformations(a, b) do Map.merge(a, b, fn _, a, b -> {a, b} end) |> Map.values() |> Map.new() end defp reaxe(point, axes) do %{ x: point[axes[:x]], y: point[axes[:y]], z: point[axes[:z]] } end def pairs(points) do for a <- points, b <- points, a < b, dx = abs(a.x - b.x), dy = abs(a.y - b.y), dz = abs(a.z - b.z), into: %{}, do: {{dx ** 2 + dy ** 2 + dz ** 2, dx + dy + dz}, {a, b, %{dx => :x, dy => :y, dz => :z}}} end end ``` ``` {:module, Day19, <<70, 79, 82, 49, 0, 0, 29, ...>>, {:pairs, 1}} ``` ```elixir [first | rest] = Enum.map(input, &Day19.pairs/1) {joinable, later} = Enum.split_with(rest, fn pairs -> s1 = MapSet.new(first, fn {k, _} -> k end) s2 = MapSet.new(pairs, fn {k, _} -> k end) case MapSet.size(MapSet.intersection(s1, s2)) do 66 -> true x when x < 66 -> false end end) {length(joinable), length(later)} ``` ``` {3, 30} ``` ```elixir # Distances between each pair of points in `first` points_set = MapSet.new(hd(input)) {points_set, scanners} = Enum.reduce_while( 1..34, {points_set, joinable, later, []}, fn i, {set, joinable, later, scanners} -> {time, {set, scanners}} = :timer.tc(fn -> Enum.reduce(joinable, {set, scanners}, fn new, {base, scanners} -> {new, scanner} = Day19.rebase(base, new) {MapSet.union(new, base), [scanner | scanners]} end) end) # IO.inspect(time / 1_000_000) current = Day19.pairs(set) Enum.split_with(later, fn pairs -> s1 = MapSet.new(current, fn {k, _} -> k end) s2 = MapSet.new(pairs, fn {k, _} -> k end) MapSet.size(MapSet.intersection(s1, s2)) >= 66 end) |> case do {[], []} -> {:halt, {set, scanners}} {joinable, later} -> # IO.inspect({length(joinable), length(later)}, label: i) {:cont, {set, joinable, later, scanners}} end end ) MapSet.size(points_set) ``` ``` warning: variable "i" is unused (if the variable is not meant to be used, prefix it with an underscore) 2021/day19.livemd#cell:w7cczzbdb2mrwtou27usauoozuqydf3v:9 warning: variable "time" is unused (if the variable is not meant to be used, prefix it with an underscore) 2021/day19.livemd#cell:w7cczzbdb2mrwtou27usauoozuqydf3v:10 ``` ``` 396 ``` ```elixir for( a <- scanners, b <- scanners, do: abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z) ) |> Enum.max() ``` ``` 11828 ```