Thanks for this and for the previously undefined "cross" in another post.
I now see why I got 48 rotations rather than 24 - I hadn't taken on board
that all scanners used the "same" coord system,  which others have interpreted
as all using the same parity.

You mentioned looking for 67 or 68 of the same beacon distances.  My criterion was >: 66,  iirc,  since 12 matching distinct points would have 66 = 11.12%2 pairs of
non-zero distances.

My routine ("newexam" - exam for "examine",  and new because the old one didn't work) was slightly faster than yours with the earlier "relori";  changing to this revised one, just below,  halves the time of a19,  and slightly reduces the space used.  Both of your
versions use somewhat less space:

ts =:   6!:2 , 7!:2@]   NB.time & space

   100 ts'#~.newexam scdata'   NB. MD
0.054425904 303264
   100 ts'a19 scdata'                   NB. RM - earlier vn of relori
0.063075894 271488
   100 ts'a19 scdata'                   NB. RM - revised vn of relori
0.036779334 245376

Mike

On 09/01/2022 10:45, Raul Miller wrote:
Here's a better 'relori', in my opinion:

NB. y: scans from two scanners
relori=: {{
   sigs=: signature each y
   upsigs=: 0-.~(~. #~ 2 = #/.~) ;~.@,each sigs
   masks=: (* </~@i.@#) each sigs e.L:0 upsigs
   pairs=: y {every~ sigs }."1@/:~@(#&,~ ,. ($ #: I.@,)@])each masks
   ori=: whichori/pairs
   candidates=:\:~(#/.~,.~.),/(-"1 ori&reorient)/pairs
   assert. ~:2{.{."1 candidates
   ori,}.{.candidates
}}

sigs is a pair of boxes of distances between beacon pairs. (boxes
correspond to scanners, rows and columns within each box correspond to
the sequence of beacons reported by that scanner).

usigs is a pair of boxes of the unique list of distances reported by
both scanners

masks is a pair of boxes of bit tables, selecting the positions in
sigs with values in upsigs (just the upper triangular part).

pairs is a rank 4 array, first dimension is 2 (corresponding to the
two scanners), second dimension corresponds to the sorted upsigs
values, third dimension is 2 and represents the two beacons which were
that distance apart, and the final dimension is 3 (for the x,y,z
values of the reported beacon locations).

Here's an example of some values from 'pairs':

    0 {"3 pairs
_4191 _460 _714
_4150 _469 _734

   654  374 _620
   695  354 _611

This is location information about the same two beacons as reported by
the two different scanners.

Then we pass this to whichori, which gives us the orientation (with
zero offsets) which has the second set of location information in
pairs using the same set of coordinate axes that the first of the pair
used.

With that, we can find the offset between these two scanners.

And that's sufficient to match up beacons across the different scanners.

FYI,

--
Raul



On Sat, Jan 8, 2022 at 9:14 PM Raul Miller <rauldmil...@gmail.com> wrote:
https://adventofcode.com/2021/day/19

Like a variety of my work on previous days, my code here is rather
bulky. Did I mention that Advent of Code was something of a bad habit
for me?

Anyways, here, not only is the code bulky, even the sample data is bulky.

Basically, for this puzzle, we were given a bunch of coordinates
beacons reported by a small set of scanners. These coordinates
contained three "flaws":

(1) They used a coordinate system which was centered on the scanner --
not a universal coordinate system.

(2) Each scanner had limited range, so no single scan had locations
for all beacons.

(3) Each scanner had an undetermined orientation.

Luckily for us, each probe "originally" had the same coordinate
system, and each probe's rotations were multiples of 90 degrees along
each axis. So there were only 24 possible orientations for each
scanner.

And, sample data looked like this:

sample=:{{)n
--- scanner 0 ---
404,-588,-901
528,-643,409
-838,591,734
390,-675,-793
-537,-823,-458
-485,-357,347
-345,-311,381
-661,-816,-575
-876,649,763
-618,-824,-621
553,345,-567
474,580,667
-447,-329,318
-584,868,-557
544,-627,-890
564,392,-477
455,729,728
-892,524,684
-689,845,-530
423,-701,434
7,-33,-71
630,319,-379
443,580,662
-789,900,-551
459,-707,401

--- scanner 1 ---
686,422,578
605,423,415
515,917,-361
-336,658,858
95,138,22
-476,619,847
-340,-569,-846
567,-361,727
-460,603,-452
669,-402,600
729,430,532
-500,-761,534
-322,571,750
-466,-666,-811
-429,-592,574
-355,545,-477
703,-491,-529
-328,-685,520
413,935,-424
-391,539,-444
586,-435,557
-364,-763,-893
807,-499,-711
755,-354,-619
553,889,-390

--- scanner 2 ---
649,640,665
682,-795,504
-784,533,-524
-644,584,-595
-588,-843,648
-30,6,44
-674,560,763
500,723,-460
609,671,-379
-555,-800,653
-675,-892,-343
697,-426,-610
578,704,681
493,664,-388
-671,-858,530
-667,343,800
571,-461,-707
-138,-166,112
-889,563,-600
646,-828,498
640,759,510
-630,509,768
-681,-892,-333
673,-379,-804
-742,-814,-386
577,-820,562

--- scanner 3 ---
-589,542,597
605,-692,669
-500,565,-823
-660,373,557
-458,-679,-417
-488,449,543
-626,468,-788
338,-750,-386
528,-832,-391
562,-778,733
-938,-730,414
543,643,-506
-524,371,-870
407,773,750
-104,29,83
378,-903,-323
-778,-728,485
426,699,580
-438,-605,-362
-469,-447,-387
509,732,623
647,635,-688
-868,-804,481
614,-800,639
595,780,-596

--- scanner 4 ---
727,592,562
-293,-554,779
441,611,-461
-714,465,-776
-743,427,-804
-660,-479,-426
832,-632,460
927,-485,-438
408,393,-506
466,436,-512
110,16,151
-258,-428,682
-393,719,612
-211,-452,876
808,-476,-593
-575,615,604
-485,667,467
-680,325,-822
-627,-443,-432
872,-547,-609
833,512,582
807,604,487
839,-516,451
891,-625,532
-652,-548,-490
30,-46,-14
}}

There will be a quiz later.  (Ok, maybe not...)

So, first thing we need here is a mechanism to parse this into
something J can handle. And, it's not obvious here, but the number of
beacons reported by each scanner was not the same. This means we need
to put the scans in boxes so they are not padded.

I decided I also wanted my parsing routine (which I named 'get') to be
idempotent. This would let me use parsed or unparsed data on the
command line -- if it was parsed already, 'get' would leave it alone.

get=: {{
    if. -.'literal'-:datatype y do. y return. end.
    s=. getscan;._2 rplc&(',';' ';(LF,'---');LF,'!---') y,'!'
    {:"1 s   assert. (i.#s) -: ;{."1 s
}}

digits=: ".@([ -. -.)&(~.":i.10)

getscan=: {{
   (id=. digits 0{::lines); coords=. __&".@>@}. lines=. (<;._2 y)-.a:
}}

I could have made get and getscan a little simpler, by ignoring the
reported scan number. But I was working on this puzzle late at night
and it was easy for me to make mistakes.

The next problem was to piece scans together to form the complete list
of beacons (using the coordinate system of the scanner #0).

There's a variety of approaches which could have been used here. Mine was this:

(a) I would measure the distance between each pair of *beacons*
reported by each scanner. The distance between any beacon pair was
guaranteed to be fixed for every scanner which included both of those
beacons.

(b) For each pair of *scanners*, I would find and count the beacon
distance lists which they had in common. Scanners which were closer
together should have more beacons in common with each other than
scanners which were farther apart.

Looking at the data, I saw that most scanners had at least one other
scanner which reported 67 or 68 of the same beacon distances, the next
most frequent commonality was 17 or 18 of the same beacon distances.
So, in code, I only concerned myself with scanners which had at least
half as many beacons in common as the largest reported commonality.

(c) For these pairs, I would find the orientation of the second based
on the orientation of the first, using the beacons which they had in
common (the ones whose distances were the in each of the pair of
scans).

In particular, I decided that I could ignore two problems:

(*) I was not concerned with "spoofed distances". I had enough
information that I believed that coincidental distance matches would
not be a problem for me.

(*) I was not concerned with "spoofed orientations". I had enough
information that I believed that there would be one and only one
orientation that best fit the beacons for the scanner pairs I was
considering.

That left me with the problem of representing intermediate results.

The first step -- getting distance pairs between beacons reported by a
single scanner -- was straightforward:

signature=:{{+/&.:*: |:-"1/~ y}}

    usigs=: ~.@,@signature each scans
    rough=:  +/@(e.,)&>/~ usigs

The next step - identifying pairs of scanners which were close to each
other - had one minor trick: each scanner was very, very close to
itself. And I needed to ignore that.

In other words, I multiplied by the logical negation of the identity matrix

    rough=:  (* -.@=@i.@#)+/@(e.,)&>/~ usigs

Actually, though, I did not need the counts of close beacons -- I just
wanted to identify the scanners which had a lot of beacons in common

    rough=: (> -:@(>./)@,) (* -.@=@i.@#)+/@(e.,)&>/~ usigs

Next, I wanted to visit those pairs in sequence, starting from scanner
zero. There's a variety of ways of doing this, but one way is to use
rough as a connection matrix. Then (+./ .*.)&rough given a selection
mask would lead us to the connected scanners. These connections are
bidirectional, and we want to know how soon we can visit a scanner. We
can use ^: to iterate some number of times and then </\ to find the
first 1 in each column. My approach here included a lot of extra blank
rows, which I suppressed using ~.

    ~.</\(+./ .*)&rough^:(i.#rough) (#rough){.1

Finally, I can't sort on a list of bitmasks, but I can convert them to
an ordered list of indices that represent the order I want to visit
the scanners.

    order=: ;<@I. ~.</\(+./ .*)&rough^:(i.#rough) (#rough){.1

I'm not quite ready to compare scanner pairs yet, though -- I need to
extract the "close scanner pair" list from rough, and sort them:

    close=: (/: order&i.) ($#:I.@,)rough

But that approach actually lists each scanner pair twice (0 7 and 7
0), and that's not necessary. So, I could do:

    close=: ~. /:~"1 (/: order&i.) ($#:I.@,)rough

or

    close=: (/: order&i.) ($#:I.@,) (* </~@i.@#)rough

With that setup, I am *almost* ready to walk through my scanner pairs.
But I still need to represent their orientation and offset.

Each scanner has both an offset from the global origin (scanner 0),
and an orientation with respect to scanner 0. The offset is easy to
represent -- that's just an x,y,z value. The orientation, though, ...
that I represented as a permutation of the axis along with a sign
(positive or negative) for each axis.

reorient=:{{
   's a'=.8 6#:{.x
   off=. }.4{.x
   sign=. _1^2 2 2#:s
   off+"1 sign*"1 (a A. i.3) {"1 y
}}

Here, x is the orientation and offset for the scanner, and y is the
list of beacon coordinates to be manipulated.

This is not an optimal representation. The problem with this approach
is that it can represent both right handed and left handed coordinate
systems, and I only wanted coordinate systems which could result from
a rotation of the origin coordinate system.

I did not know whether that origin system was right handed or left
handed (since I didn't have any external reference data), all I cared
was that my alternate orientations matched. To solve this I did a dry
run, using an identity matrix in place of beacon coordinates, and 0
offsets. If a cross product of the first two result coordinates
matched the third, I had a viable orientation:

plausible=: I.({:-:cross/@{.~&2)@reorient&(=i.3)"1 ,.i.48

Still with me? I got to this point mostly by trial and error, with
lots of asserts in my code to make me aware of invalid assumptions.  I
have since deleted most of those mistakes and the assertions which
made me aware of those problems. And, I've cleaned up some of my code
a bit from my initial puzzle solution, so that it would be readable.
But I am not sure how well I am explaining my resultant
understanding...

The next stage of puzzle solving was, for each pair of scans:

(1) find the beacon coordinate pairs in each which share a distance
between the scanner pair.

(2) Try each of the plausible orientations (with zero offsets),
subtracting the selected parts of the scanner set. Since the beacon
coordinate pairs are ordered by distance between the pair, they are
ordered the same for each scanner set. And, when I get the orientation
right, distance between pairs from the two sets will mostly be the
same distance apart (this will be the distance between the two
scanners).

My implementation is still a bit sloppy here, but it gets the job done:

whichori=:{{
   d=. #@~.@:(-&x)@reorient&y"1,.plausible
   plausible{~d i.<./d
}}

relori=: {{
   'c0 c1'=. y
   sigs=. signature@>y
   k=. +/4=#/.~,sigs
   ref=. k{.}. (~. \: #/.~),sigs
   ids=. (* k&>) ref i. sigs
   pairs0=. pairs1=. i.0 0
   for_id. 1+i.k-1 do.
     j=. (}.$sigs)#:I.,id=ids
     assert.4=#j
     pairs0=. pairs0,(2{.j) { c0
     pairs1=. pairs1,(2}.j) { c1
   end.
   o=. pairs0 whichori pairs1
   candidates=./:~(#/.~,.~.),/pairs0 -"1 o reorient pairs1
   assert. ~:_2{.{."1 candidates
   off=.}.{:candidates
   o,off
}}

Note that when I wrote this code I was not completely sure that I
would not have "spoofed distances". So I am not actually completely
ignoring that problem -- the assert. 4=#j in the middle of relori
would fail if I somehow had the same distance between more than a pair
of beacons. (It's 4, rather than 2, because each pairing is
represented twice -- I found the distance between each pair of beacons
and beaconA is the same distance from beaconB that beaconB is from
beaconA.)

It would be fun working out how to express this more concisely, but I
haven't thought enough about that issue.

And, that's all the pieces I needed for part A, which was to count the
total number of scanned beacons:

a19=:{{
    scans=: get y
    heritage=:(,;0);(<:#scans)#<i.0 0
    usigs=: ~.@,@signature each scans
    rough=: (> -:@(>./)@,) (* -.@=@i.@#)+/@(e.,)&>/~ usigs
    order=: ;<@I. ~.</\(+./ .*)&rough^:(i.#rough) (#rough){.1
    close=: ~. /:~"1 (/: order&i.) ($#:I.@,)rough
    known=:,:0 0 0 0 0
    for_scanpair.close do.
        'kid jid'=. scanpair /: (0,,scanpair_index{.close) i. scanpair
        kori=. }.(({."1 known) i.kid){known
        known=: known,jid,t=.relori (kori reorient kid{::scans); jid{::scans
   end.
   #~.>,each/(}."1/:~~.known) reorient L:0"_1 scans
}}

I've left a variety of global assignments here, because looking at
example data really helps when understanding the code. Also

For part B, the problem was to find the largest "manhattan distance"
between two scanners.

In other words, given the location of each scanner
    2}."1~.known

take the sum of the absolute values of the difference between each
pair of locations, then find the largest of those sums

b19=:{{
   a19 y
   >./,+/"1|-"1/~2}."1~.known
}}

If there's something here which I should have explained better, and
you can think of a way to phrase a question about it, let me know...

Thanks,

--
Raul
----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm


--
This email has been checked for viruses by Avast antivirus software.
https://www.avast.com/antivirus

----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm

Reply via email to