# ETOOBUSY đźš€ minimal blogging for the impatient

# AoC 2021/19 - GPS is smarter - part 2

**TL;DR**

On with Advent of Code puzzle 19 from 2021: GPS is smarter, hereâ€™s whyâ€¦

In the last post of 2021 (AoC 2021/19 - GPS is smarter) we left with
a high level solution for the puzzle, but I did not include the real
*workhorse*, that is how to match the â€ślocalâ€ť views of two different
scanners.

In particular, two scanners have a positive match if itâ€™s possible to relate at least 12 beacons from one view to the other. But how can that be done?

The first thing to be addressed is that two different scanners might have completely different orientations. What is the positive X dimension for one scanner, might be the negative Z dimension for another scanner. This means that we will have to keep one of the two fixed, while trying all different possible arrangements for the other one.

All in all, then, we have to consider:

- 6 possible ways of arranging the three dimensions:

```
XYZ XZY YXZ YZX ZXY ZYX
```

- for each of them, 8 possible ways of arranging the orientation, e.g. for the first:

```
X Y Z
X Y -Z
X -Y Z
X -Y -Z
-X Y Z
-X Y -Z
-X -Y Z
-X -Y -Z
```

for a total of 48 possible orientations. This is not a big deal, though, because it â€śjustâ€ť means that we have to remix the input coordinates for the beacons seen by a scanner, possibly with a change of sign; after doing this, anyway, the matching algorithm would be the same for each of the 48 arrangements.

In our code we are representing each position with a triple of integers, with X, Y, and Z associated to positions 0, 1, and 2 in each triple. To get the first dimension to associate to X, then, we can iterate from 0 to 2; to iterate through the two orientations for X, we can iterate from 0 to 1.

So here we are with `match-scanners`

:

```
sub match-scanners ($alice, $umberto) {
for 0 .. 2 -> $xd {
for 0, 1 -> $xdf {
my $lm = ListsMatcher.new(
alice => $alice<lists>[0][0],
umberto => $umberto<lists>[$xd][$xdf],
min-items => (12 - $umberto<repetitions>[$xd]),
);
while my $m = $lm.next-match {
my @pairings;
for @$m -> ($va, $vbc) {
my $vb = $xdf ?? -$vbc !! $vbc;
for $alice<byc>[0]{$va}.List X $umberto<byc>[$xd]{$vb}.List
-> ($ap, $bp) { @pairings.push: ($ap, $bp) }
}
for @pairings.combinations(12) -> $c {
my @yzs = check-pairings($c, $xd);
next unless @yzs.elems > 1;
# YAY! matching, return transformed $umberto wrt $alice
my $x = $c[0][1][$xd];
my $xdo = $c[0][0][0] - ($xdf ?? -$x !! $x);
#($xd, $xdf, $xdo, |@yzs).note;
return transform($umberto, $xd, $xdf, $xdo, |@yzs);
}
}
}
}
return;
}
```

As you can see, the outer loops go through to get the X-dimension (`$xd`

0 to
2) and the X-orientation (`$xdf`

, i.e. the factor for the X-dimension).

OK, we now have an iteration for the X dimension, what about the other
two? In our algorithm we donâ€™t explicitly loop through themâ€¦ well, not
here at least. To have a successful match of 12 beacons, they must match
in **each** dimension (modulo the different arrangements), so why not
start with the X dimension only?

So, we extract two lists, one for the X dimension of the fixed scanner,
one for the candidate X dimension for the â€śmovableâ€ť scanner, along with
its sign. As we already knew we would need them, we find them
conveniently pre-calculated in `$alice<lists>[0][0]`

and
`$umberto<lists>[$xd][$xdf]`

.

The `ListMatcher`

does what the name says, i.e. it compares two lists
against each other to find whether they *match*. A match, in this case,
means that the two lists have *at least* 11 relative gaps that are the
same across the two lists, like in the following example with 3 matching
gaps and 4 matching positions only:

```
alice -10 -5 -1 2 5 8 13
|<-->|<--->|<------>|
umberto 5 7 9 12 16 18
```

The `ListMatcher`

returns an iterator that will output all possible
matching of 12 X positions or more, so that we can do some further
checking. We are guaranteed that there will be 12 or more matches
between two different scanners, but along each dimensions there might be
more and we have to check them all, using the other dimensions.

This is done in the `while`

loop inside:

```
while my $m = $lm.next-match {
my @pairings;
for @$m -> ($va, $vbc) {
my $vb = $xdf ?? -$vbc !! $vbc;
for $alice<byc>[0]{$va}.List X $umberto<byc>[$xd]{$vb}.List
-> ($ap, $bp) { @pairings.push: ($ap, $bp) }
}
for @pairings.combinations(12) -> $c {
my @yzs = check-pairings($c, $xd);
next unless @yzs.elems > 1;
# YAY! matching, return transformed $umberto wrt $alice
my $x = $c[0][1][$xd];
my $xdo = $c[0][0][0] - ($xdf ?? -$x !! $x);
#($xd, $xdf, $xdo, |@yzs).note;
return transform($umberto, $xd, $xdf, $xdo, |@yzs);
}
}
```

We first extract the (12 or more) paired points into `@pairings`

, by
iterating over the match `$m`

. The `$va`

is the value for Alice, while
the `$vbc`

is the *candidate* value for Umberto.

Yes, it was initially called Berto, but Umberto was better to express the concept of

unbound.

So the value for Umberto is actually `$vb`

, calculated based on the
axis flipping (which is actually a sign change).

Here we leverage something other that we pre-calculated inside the two
scanners, placed in the hash associated to `byc`

(i.e. â€śby coordinateâ€ť),
that indexes each beacon by coordinate in a list (usually containing one
single beacon, but possibly more in case of overlaps).

As there might be more than 12 pairings inside, we have also to consider
all possible subsets of 12 items, so we iterate over
`@pairings.combinations(12)`

. This will also take care of possible
overlaps over the X dimensions of two beacon positions.

OK, now we have twelve *possible* associations of beaconsâ€¦ are they
the right ones? This is what `check-pairings`

is for. It takes them (as
well as the indication of which dimension in Umberto is considered the
X) and returns, if they match, what dimensions are associated to the Y
and Z dimensions for Umberto, as well as their orientation. Otherwiseâ€¦
it does not return enough stuff, and we can check another combination.

But if it workedâ€¦ itâ€™s a match! So we can transform Umberto according
to this with `transform()`

, using for each axis:

- what axis it comes from
- whether the sign is flipped or not
- what is the offset

This will eventually bring Umberto in Aliceâ€™s coordinate system, so we
just have to `return`

it.

Letâ€™s see this `transform`

- quite boring, I know:

```
sub transform ($src, $xd, $xdf, $xdo, $yd, $ydf, $ydo, $zd, $zdf, $zdo) {
my @coords = $src<coords>.map: -> $p {
my ($x, $y, $z) = $p[$xd, $yd, $zd];
$x = $xdo + ($xdf ?? -$x !! $x);
$y = $ydo + ($ydf ?? -$y !! $y);
$z = $zdo + ($zdf ?? -$z !! $z);
($x, $y, $z);
};
return generate-scanner($src<name>, @coords, ($xdo, $ydo, $zdo));
}
```

We will see `generate-scanner`

in a future post, donâ€™t worry!

Time for `check-pairings`

, that makes sure that the twelve associations
are actually consistent, i.e. all possible with a single set of
translations over the three dimensions:

```
sub check-pairings ($pairs, $xd) {
my @ds = (0 .. 2).grep: * != $xd;
OUTER-LOOP:
for (@ds, @ds.reverse.Array) X (0, 1) X (0, 1) -> (($yd, $zd), $ydf, $zdf) {
my ($y, $z) = $pairs[0][1][$yd, $zd];
my $y-offset = $pairs[0][0][1] - ($ydf ?? -$y !! $y);
my $z-offset = $pairs[0][0][2] - ($zdf ?? -$z !! $z);
for @$pairs -> ($a, $b) {
my ($y, $z) = $b[$yd, $zd];
next OUTER-LOOP if $y-offset != $a[1] - ($ydf ?? -$y !! $y);
next OUTER-LOOP if $z-offset != $a[2] - ($zdf ?? -$z !! $z);
}
return $yd, $ydf, $y-offset, $zd, $zdf, $z-offset;
}
return [];
}
```

As you might remember, so far we only looped over candidates for the X-dimension and the X-orientation, so itâ€™s time to loop over the other two. We do it here becauseâ€¦ we canâ€™t delay this any more! Luckily weâ€™re only dealing with 12 points, so itâ€™s a more restricted situation.

Looping over the different dimensions is expressed in a compact way in
the `for`

loop specification: we isolate the â€śremainingâ€ť dimensions into
`@ds`

, and consider it as well as its reverse; then all possible values
for flipping over the Y and Z dimensions. Overall a three-parts
cartesian product, nifty!

Inside each loop we first calculate the offsets based on the *first*
association, then we verify that the same offset applies to all pairs.
We might skip the first one hereâ€¦ but who cares?

If the match is successful we return the specific arrangement along with the offset, as we already saw.

There are still a couple things that we have to iron out at this pointâ€¦ matching two lists of numbers, and getting the inputs. This will be, my friends, material for another post.

Stay safe in the meantime!

*Comments? Octodon, Twitter, GitHub, Reddit, or drop me a line!*