ShareMyScreen/AdventOfCode/2022/22/MonkeyMap

From J Wiki
Jump to navigation Jump to search

The Problem << >>

My first thought is to simulate each step, simply, but suppose Part 2 asks me to run it 1000 times? I'll code it efficiently.

I can come up with an efficient implementation. I will have 4 representations of the board, one for each direction of travel. At each cell in the board I will store the number of spaces I can move before hitting a wall or boundary. If the move crosses the boundary, the value there will indicate how far to move back to the other side to finish the move.

NB. convert a horizontal line to the correct format for moves toward the right.  Board starts with
NB. _1=wall, 0=out of bounds, 1=empty in bounds
fmtline =: {{ ((y=0) * +/ y~:0) + ([ _1:^:(_1=[) +)/\. y }}"1
   fmtline 0 0 1 _1 1 1 _1 0 0
5 5 0 _1 1 0 _1 5 5

The left part of fmtline counts the number of active cells on the line (5 here) and adds that to the value in the out-of-bounds cells. The only one of these cells that matters is the one after the last in-bounds cell. The right part does a reverse running sum, resetting to _1 when a wall is encountered.

   fmtboards =: (fmtline , fmtline&.:|: , fmtline&.:(|."1) ,: fmtline&.:(|:@:|.))
   lines =:  0 ,"(1) 0 ,~"(1) 0 , 0 ,~ <: '# .' i. ] onaoclines
   boards =. fmtboards lines
   $boards
4 14 18

I read the lines, surround the area with an out-of-bounds fringe, and translate from characters to the numeric form I have settled on. Then I create the boards to handle moves in all directions. Next I read my move instructions.

     ]lendir =: ((".@}: , 1 _1 0 {~ 'RLS' i. {:);.2~  e.&'RLS') 'S' ,~ wd 'clippaste'
10  1
 5 _1
 5  1
10 _1
 4  1
 5 _1
 5  0

This is a hook, where e.&'RL' marks the end of the sections and (".@}: , _1 2 p. 'RL' i. {:) is applied to each section. 'RL' i. {: converts the last character to 0 or 1 and 1 _1 {~ translates them to 1 and _1. Since the last move doesn't contain a turn direction, I add one, indicating no turn.

With the boards set up, following instructions is easy. I start a script.

dirmove =: _2 ]\ 0 1  1 0  0 _1  _1 0  NB. moves in each direction (0=E, 1=S, 2=W, 3=N)
runlendir =: {{   NB. y is list of instructions, boards and lines are set up
dir =. 0  NB. start looking E
pos =. 1 , 01 i.&1@:= 1 { lines  NB. start on top line, at first open cell
for_ld. y do.  NB. for each len/dist pair
  'l d' =. ld  NB. separate len and dir
  mv =. dir { dirmove  NB. y,x of a move of 1 unit
  while. l do.   NB. till we have finished the move
    mvdist =. l <. (<dir,pos) { boards  NB. get amount we can move from this square
    pos =. pos + mvdist * mv  NB. move the limit
    if. (<pos) { lines do. break. end.  NB. if we are still in the board, stop
    resetdist =. (<dir,pos) { boards  NB. oob - board gives reset distance
    pos =. pos - resetdist * mv  NB. back up to other side
    if. 0 > (<pos) { lines do. pos =. pos + (resetdist-1) * mv break. end.  NB. if line starts with wall, stay at end
    l =. l - mvdist  NB. pick up at start and finish move
  end.
  dir =. dir + d  NB. move finished, turn
end.

}}

I win no prizes for guessing about Part 2. I will have to rewrite the code completely, step by step.

This isn't a programming problem, it's a topology problem! I fill a sheet of paper with sketches of cubes, with numbered faces and arrows for face orientation. After a while I have what I need: for each face and direction, the face entered and the direction, 48 numbers in all.

I also need to find the coordinate on the entered face. I don't want 48 more error-prone expressions, so I will calculate the coordinate based on the directions.

Since I must refer to the unfolded input board to find blockages, I will keep coordinates as coordinates in that board.

dirmove =: _2 ]\ 0 1  1 0  0 _1  _1 0  NB. moves in each direction (0=E, 1=S, 2=W, 3=N)
M =: 4
fdtab =: _2 ]\ 5 2  1 1  2 1  3 1  NB. for each (face,dir), the new face,dir when a boundary is crossed
fdtab =: fdtab ,: _2 ]\ 5 1  4 1  2 2  0 3
fdtab =: fdtab , _2 ]\ 1 0  4 0  3 2  0 0
fdtab =: fdtab , _2 ]\ 2 0  4 3  5 3  0 1
fdtab =: fdtab , _2 ]\ 5 0  3 3  2 3  1 3
fdtab =: fdtab , _2 ]\ 0 2  3 0  4 2  1 2
facebase =: M * _2 ]\ 0 2  1 2   1 1  1 0  2 2  2 3  NB. top-left cell of each face in lines


move =: {{  NB. y is face,dir,ypos,xpos  result is same format after move
'face dir posy posx' =. y
pos =. (dir { dirmove) + oldpos =. posy,posx  NB. advance to new position
if. -. pos -:&(<.@(%&M)) oldpos do.  NB. crossing face boundary?
  NB. face boundary crossed - update everything
  'face dir' =. (<face,olddir =. dir) { fdtab  NB. update face & direction in face
  NB. first, establish the coordinate in the direction of motion in the new face.
  NB. if that coordinate is increasing, start at 0; decreasing, at M-1
  pos =. (_1=dir { dirmove) * (M-1)
  NB. establish the other coordinate, also within the new face
  staticdir =. M | +/ oldpos * 0=olddir{dirmove  NB. the static coordinate in the old face
  if. olddir =&(1&(17 b.)) dir do. compstat =. dir~:olddir
     NB. same direction or 180 degrees diff: complement coord if dir change
  NB. otherwise the move is +-90 degrees.  We start in a cell in normal board
  NB. orientation and move in the direction olddir.  In one of the 90-degree-rotated
  NB. orientations, the static coordinate in the olddir increases with increasing
  NB. static coordinate in the newdir; for the orther rotation the directions oppose.
  NB. compstat is 1 when the directions oppose.
  else. compstat =. olddir { dir = 1 0 3 2
  end.
  pos =. (face{facebase) + pos + (0 = dir{dirmove) * ((M-1)&-)^:compstat staticdir  NB. add the static coord, compld as needed
end.
face,dir,pos  NB. return new stats
}}

runlendir =: {{   NB. y is list of instructions, boards and lines are set up
dir =. 0  NB. start looking E
pos =. 0 , 01 i.&1@:= 0 { lines  NB. start on top line, at first open cell
face =. 0  NB. start in face 0 (good enough)
for_ld. y do.  NB. for each len/turn pair
  'l turn' =. ld  NB. separate len and turn
  for. i. l do.   NB. till we have finished the move
    'nface ndir nposy nposx' =. move face,dir,pos
    if. _1 = (<nposy,nposx) { lines do. break. end.  NB. stop before wall
    if. 0 = (<nposy,nposx) { lines do. 'out of bounds' 13!:8 ] 1 end.  NB. break if ob
    face =. nface [ dir =. ndir [ pos =. nposy,nposx  NB. update pos,dir
  end.
  dir =. 4 | dir + turn  NB. move finished, turn
end.
0 250 4 #. (>:pos),dir
}}

=&(1&(17 b.)) tests for equality in the lowest bit, because (17 b.) is bitwise AND. I could have used =&(2&|).

I should test the move code a bit. I spot-test a few cases and fix the bugs. Eventually I want to test them all.

    ]  pp =. (i. 6) ,"0/  i. 4 
0 0
0 1
0 2
0 3

1 0
...
      ]ppp =. ([ , 2 2 + facebase {~ {.@[)"1 pp
0 0  2 10
0 1  2 10
0 2  2 10
0 3  2 10

1 0  6 10
...
      (] -: move^:(4*M))"1 ppp
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
   

All good. Now to run on the example data. After I fix the result to match the problem's 1-origin indexing, I am pleased to see

   runlendir mlendir
5031