ShareMyScreen/AdventOfCode/2022/17/PyroclasticFlowJP

From J Wiki
Jump to navigation Jump to search

The Problem << >>

Entire solution

i17=: freads '17.txt'
NB. Part 1: how high after 2022 blocks fell?
NB. block el coordinates (x=0 is closest to floor (hence flip |.), y=0 is closest to left wall)
bl=: ($#:I.@,)@:|.@:#:&.> (,15);2 7 2;1 1 7;1 1 1 1;3 3 NB. blocks
tetris=: {{              NB. x: number of blocks to drop; y:i17; straightforward simulation.
  j=. _1 1{~'<>' i. }: y NB. parse jet pushes in coords
  f=. ($#:I.@,) 1 7$1    NB. init field (0 is floor) in coords
  'bn ji'=. 0            NB. set block num & jet index
  bc=. 4 2+"1 bn{::bl    NB. block coord
  while. bn<x do.
    NB. L/R if possible
    if. f (+./@:e.~ +: (0&> +./@:+. 6&<)@:({:"1)@]) bu=. (0,ji{j) +"1 bc do. bc=.bu end.
    NB. down: 
    if. f +./@:e.~ bu=._1 0 +"1 bc do. NB. hit block, stay
      NB. discard all from field below full line if present.
      if. *./ (i. 6) e. f ({:"1@[ #~ e.&:({."1)) bc do.
        f=. f ([ #~ (>: <./)&:({."1)) bc
      end.
      NB. update fl to include block coords
      f=.f,bc NB.f \:~@, bc
      NB. next block, inc block num
      bc=. (bl{::~5|bn=.>:bn)+"1(4+>./{."1 f),2
    else. bc=. bu end.   NB. if not, drop
    ji=. (#j)&|@:>: ji   NB. get next jet direction
  end.
  >./{."1 f              NB. Highest block
}}
NB. Part 2: now for the first 1e12 rocks :/
cyctetris =: {{
NB. do tetris as before, but keep heights and see after which number of blocks we get a repeat in differences 
  j=. _1 1{~'<>' i. }: y NB. jet pushes in coords
  f=. ($#:I.@,) 1 7$1    NB. init field (0-2 is floor)
  'bn ji'=. 0            NB. set block num & jet index
  bc=. 4 2+"1 bn{::bl    NB. init block coord
  height=. 0$0           NB. placeholder for heights
  while. bn<x do.
    NB. L/R: move if not occupied and in bounds
    if. f(+./@:e.~ +: (0&> +./@:+. 6&<)@:({:"1)@]) bu=. (0,ji{j) +"1 bc do. bc=.bu end.
    NB. down: if no block down 
    if. f+./@:e.~ bu=._1 0 +"1 bc do. NB. hit block, stay
      NB. if block closed room entirely (i.e. all columns occupied in at least one location), discard previous block coords
      if. *./ (i.6) e. f({:"1@[ #~ e.&:({."1)) bc do.
        f=. f ([ #~ (>: <./)&:({."1)) bc
      else.
        NB. update f to include current block coords
        f=.f,bc 
      end.
      NB. next block, inc block num
      bc=. (bl{::~5|bn=.>:bn)+"1 (4+>./{."1 f),2

      NB. add height record
      height=. height,>./{."1 f NB. on full f, not fneigh as it might be too deep to capture the top
      NB. every 1000 blocks, check whether cycle
      if. 0=1000|bn do. 
        NB. based on height, find cycles
        for_mu. (#bl) ([ * [: i.&.<: <.@%~) 3<.@%~ #height do. 
          NB. cycle if tailing boxes repeat 2 times or more
          if. 2<:+/hi=.*./\.2-:/\hb=.(,.,~mu)];._3]2-~/\0,height do.
            NB. could find shortest preamble length, but unnecessary.
            NB. height before cycles
            hp=. +/ , hb {.~ >: hi i: 0
            NB. height per cycle
            hc=. +/ hlb=._1 { hb
            NB. # full cycles
            nfc=. mu <.@:%~ np=.x-mu*hi i. 1
            NB. # of blocks in remainder
            nr =. np-mu*nfc
            NB. height of remainder
            hrem=. nr +/@{. hlb
            hp+(hc*nfc)+hrem return.
          end.
        end.
      end.
    else. bc=. bu end. NB. if not stopped, drop
    ji=.(#j)&|@:>: ji  NB. increase jet push counter
  end.
  NB. reached block count before cycle discovered
  {:height
}}
part1=:2022&tetris
part2=:1e12&cyctetris
(part1;part2)i17       NB. get solutions for parts 1 and 2

Part 1

Part 1 asks for simulating a sort of Tetris game, and finding out how high the stack of blocks ends up being. Each block gets pushed left and right by volcanic jets as it falls, left and right (if the block can move). Both the block types and the jet directions wrap around if the end is reached. Part 1 asks for the height after 2022 blocks have dropped, this is well in reach for a simple simulation.

I store the floor and all blocks already fallen as list of occupied coordinates.

bl=: ($#:I.@,)@:|.@:#:&.> (,15);2 7 2;1 1 7;1 1 1 1;3 3 NB. block types

The simulation verb tetris takes as left argument the number of blocks to drop, and as right argument the input:

tetris=: {{              NB. x: number of blocks to drop; y:i17; straightforward simulation.
  j=. _1 1{~'<>' i. }: y NB. parse jet pushes in coords
  f=. ($#:I.@,) 1 7$1    NB. initialise field (0 is floor) in coords height, L/R
  'bn ji'=. 0            NB. set block num & jet index
  bc=. 4 2+"1 bn{::bl    NB. falling block coords
  while. bn<x do.        NB. drop the required number of blocks
    NB. L/R if possible
    if. f (+./@:e.~ +: (0&> +./@:+. 6&<)@:({:"1)@]) bu=. (0,ji{j) +"1 bc do. bc=.bu end.
    NB. down: 
    if. f +./@:e.~ bu=._1 0 +"1 bc do. NB. hit block, stay
      NB. discard all from field below full line if present, and add bc
      if. *./ (i. 6) e. f ({:"1@[ #~ e.&:({."1)) bc do.
        f=. f (],~[ #~ (>: <./)&:({."1)) bc
      else.
        f=. f,bc         NB. add current block to field, as it's done
      end.
      NB. next block, inc block num
      bc=. (bl{::~5|bn=.>:bn)+"1(4+>./{."1 f),2
    else. bc=. bu end.   NB. if not, drop
    ji=. (#j)&|@:>: ji   NB. get next jet direction
  end.
  >./{."1 f              NB. Highest block
}}

The checks in the first if. ... end. construct create a shifted version bu of the falling block coordinates bc and keeps it if none of the following is true (+:):

  1. any updated block coordinate is already in the field of floor and fallen blocks: f +./@:e.~ bu
  2. any updated block second coordinates goes out of the side bounds [0, 6]: f (0&> +./@:+. 6&<)@:({:"1)@] bu

The second if. condition checks whether to stop, or whether the block can move down (else). If the block stops, I check whether it completes an entire line, and if so, discard all coordinates in the field below it, as they cannot be reached anymore. For part 1, this optimisation is not great (like 20% faster), as it's triggered only once, but for part 2 it becomes more relevant (almost 4 times faster). Both cases of this if. add bc to f as the block has finished falling. After this, the block number is incremented, the next block is selected and its coordinates set to show at the correct location, i.e. on the 4th line above the previous, and at square 2 step from the left wall.

In any case, if the block down movement is complete, the next jet index is loaded, and the loop continues. I tried a solution using integers to store blocked squares: the units store the left/right coordinate, and the tens and up store the height. While it was marginally faster for part 1 faster it was a whole lot slower for part 2; it seems faster lookups don't outweigh the cost of splitting numbers in digits.

Part 2

Part 2 asks for doing the same but for 1e12 blocks. As this is clearly too much to simulate, there has to be a cycle: Part 2 follows the same scheme as part 1, but when a block lands and the block number is a multiple of 1000, I check if a cycle is present. I didn't really have a clue how to formally do this, so I settled for the following heuristic, inserted after selecting the next block to be dropped:

...
NB. add height record
height=. height,>./{."1 f 
NB. every 1000 blocks, check whether cycle
if. 0=1000|bn do. 
  for_mu. (#bl) ([ * [: i.&.<: <.@%~) 3<.@%~ #height do. 
    NB. cycle if tailing boxes repeat 2 times or more
      if. 2<:+/hi=.*./\.2-:/\hb=.(,.,~mu)];._3]2-~/\0,height do.
        NB. height before cycles
        hp=. +/ , hb {.~ >: hi i: 0
        NB. height per cycle
        hc=. +/ hlb=._1 { hb
        NB. # full cycles
        nfc=. mu <.@:%~ np=.x-mu*hi i. 1
        NB. # of blocks in remainder
        nr =. np-mu*nfc
        NB. height of remainder
        hrem=. nr +/@{. hlb
        hp+(hc*nfc)+hrem return.
    end.
  end.
end.
...

I keep the heights after every dropped block, and every 1000 dropped blocks, I check whether there are any cycles of lengthsmu of multiples of 5 (since there are 5 blocks) that repeat at least twice, so up to 3 <.@%~ #height. For each, I check whether the pair-wise differences of the height, when packed together in slices of length mu (saved in hb), and check when at least two tailing repetitions are present (the indices of the repetitions being saved as hi for further use), i.e. at least the last 3 length-mu slices are the same. If so, it means we ran into a cycle of length mu. From there I need to figure out the following:

  • pre-cycle height hp, which is the sum of the first mu-length height differences we kept;
  • the height per cycle hc, which is the sum of the last mu-length slice of hb (stored as hlb);
  • number of full cycles nfc;
  • height of the blocks in the remainder hrem, i.e. the last non-complete cycle before arriving at 1e12;

Then, the final height is hp + (nfc * hc) + hrem. While this works in just over 2s, it feels a bit heavy handed, and there is likely a better solution.