334 lines
7.7 KiB
ReStructuredText
334 lines
7.7 KiB
ReStructuredText
Advent of Code 2017
|
||
===================
|
||
|
||
December 5th
|
||
------------
|
||
|
||
…a list of the offsets for each jump. Jumps are relative: -1 moves to
|
||
the previous instruction, and 2 skips the next one. Start at the first
|
||
instruction in the list. The goal is to follow the jumps until one leads
|
||
outside the list.
|
||
|
||
In addition, these instructions are a little strange; after each jump,
|
||
the offset of that instruction increases by 1. So, if you come across an
|
||
offset of 3, you would move three instructions forward, but change it to
|
||
a 4 for the next time it is encountered.
|
||
|
||
For example, consider the following list of jump offsets:
|
||
|
||
::
|
||
|
||
0
|
||
3
|
||
0
|
||
1
|
||
-3
|
||
|
||
Positive jumps (“forward”) move downward; negative jumps move upward.
|
||
For legibility in this example, these offset values will be written all
|
||
on one line, with the current instruction marked in parentheses. The
|
||
following steps would be taken before an exit is found:
|
||
|
||
-
|
||
|
||
(0) 3 0 1 -3 - before we have taken any steps.
|
||
|
||
-
|
||
|
||
(1) 3 0 1 -3 - jump with offset 0 (that is, don’t jump at all).
|
||
Fortunately, the instruction is then incremented to 1.
|
||
|
||
- ::
|
||
|
||
2 (3) 0 1 -3 - step forward because of the instruction we just modified. The first instruction is incremented again, now to 2.
|
||
|
||
- ::
|
||
|
||
2 4 0 1 (-3) - jump all the way to the end; leave a 4 behind.
|
||
|
||
- ::
|
||
|
||
2 (4) 0 1 -2 - go back to where we just were; increment -3 to -2.
|
||
|
||
- ::
|
||
|
||
2 5 0 1 -2 - jump 4 steps forward, escaping the maze.
|
||
|
||
In this example, the exit is reached in 5 steps.
|
||
|
||
How many steps does it take to reach the exit?
|
||
|
||
Breakdown
|
||
---------
|
||
|
||
For now, I’m going to assume a starting state with the size of the
|
||
sequence pre-computed. We need it to define the exit condition and it is
|
||
a trivial preamble to generate it. We then need and ``index`` and a
|
||
``step-count``, which are both initially zero. Then we have the sequence
|
||
itself, and some recursive function ``F`` that does the work.
|
||
|
||
::
|
||
|
||
size index step-count [...] F
|
||
-----------------------------------
|
||
step-count
|
||
|
||
F == [P] [T] [R1] [R2] genrec
|
||
|
||
Later on I was thinking about it and the Forth heuristic came to mind,
|
||
to wit: four things on the stack are kind of much. Immediately I
|
||
realized that the size properly belongs in the predicate of ``F``! D’oh!
|
||
|
||
::
|
||
|
||
index step-count [...] F
|
||
------------------------------
|
||
step-count
|
||
|
||
So, let’s start by nailing down the predicate:
|
||
|
||
::
|
||
|
||
F == [P] [T] [R1] [R2] genrec
|
||
== [P] [T] [R1 [F] R2] ifte
|
||
|
||
0 0 [0 3 0 1 -3] popop 5 >=
|
||
|
||
P == popop 5 >=
|
||
|
||
Now we need the else-part:
|
||
|
||
::
|
||
|
||
index step-count [0 3 0 1 -3] roll< popop
|
||
|
||
E == roll< popop
|
||
|
||
Last but not least, the recursive branch
|
||
|
||
::
|
||
|
||
0 0 [0 3 0 1 -3] R1 [F] R2
|
||
|
||
The ``R1`` function has a big job:
|
||
|
||
::
|
||
|
||
R1 == get the value at index
|
||
increment the value at the index
|
||
add the value gotten to the index
|
||
increment the step count
|
||
|
||
The only tricky thing there is incrementing an integer in the sequence.
|
||
Joy sequences are not particularly good for random access. We could
|
||
encode the list of jump offsets in a big integer and use math to do the
|
||
processing for a good speed-up, but it still wouldn’t beat the
|
||
performance of e.g. a mutable array. This is just one of those places
|
||
where “plain vanilla” Joypy doesn’t shine (in default performance. The
|
||
legendary *Sufficiently-Smart Compiler* would of course rewrite this
|
||
function to use an array “under the hood”.)
|
||
|
||
In the meantime, I’m going to write a primitive function that just does
|
||
what we need.
|
||
|
||
.. code:: ipython2
|
||
|
||
from notebook_preamble import D, J, V, define
|
||
from joy.library import SimpleFunctionWrapper
|
||
from joy.utils.stack import list_to_stack
|
||
|
||
|
||
@SimpleFunctionWrapper
|
||
def incr_at(stack):
|
||
'''Given a index and a sequence of integers, increment the integer at the index.
|
||
|
||
E.g.:
|
||
|
||
3 [0 1 2 3 4 5] incr_at
|
||
-----------------------------
|
||
[0 1 2 4 4 5]
|
||
|
||
'''
|
||
sequence, (i, stack) = stack
|
||
mem = []
|
||
while i >= 0:
|
||
term, sequence = sequence
|
||
mem.append(term)
|
||
i -= 1
|
||
mem[-1] += 1
|
||
return list_to_stack(mem, sequence), stack
|
||
|
||
|
||
D['incr_at'] = incr_at
|
||
|
||
.. code:: ipython2
|
||
|
||
J('3 [0 1 2 3 4 5] incr_at')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
[0 1 2 4 4 5]
|
||
|
||
|
||
get the value at index
|
||
~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
::
|
||
|
||
3 0 [0 1 2 3 4] [roll< at] nullary
|
||
3 0 [0 1 2 n 4] n
|
||
|
||
increment the value at the index
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
::
|
||
|
||
3 0 [0 1 2 n 4] n [Q] dip
|
||
3 0 [0 1 2 n 4] Q n
|
||
3 0 [0 1 2 n 4] [popd incr_at] unary n
|
||
3 0 [0 1 2 n+1 4] n
|
||
|
||
add the value gotten to the index
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
::
|
||
|
||
3 0 [0 1 2 n+1 4] n [+] cons dipd
|
||
3 0 [0 1 2 n+1 4] [n +] dipd
|
||
3 n + 0 [0 1 2 n+1 4]
|
||
3+n 0 [0 1 2 n+1 4]
|
||
|
||
increment the step count
|
||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
::
|
||
|
||
3+n 0 [0 1 2 n+1 4] [++] dip
|
||
3+n 1 [0 1 2 n+1 4]
|
||
|
||
All together now…
|
||
~~~~~~~~~~~~~~~~~
|
||
|
||
::
|
||
|
||
get_value == [roll< at] nullary
|
||
incr_value == [[popd incr_at] unary] dip
|
||
add_value == [+] cons dipd
|
||
incr_step_count == [++] dip
|
||
|
||
R1 == get_value incr_value add_value incr_step_count
|
||
|
||
F == [P] [T] [R1] primrec
|
||
|
||
F == [popop !size! >=] [roll< pop] [get_value incr_value add_value incr_step_count] primrec
|
||
|
||
.. code:: ipython2
|
||
|
||
from joy.library import DefinitionWrapper
|
||
|
||
|
||
DefinitionWrapper.add_definitions('''
|
||
|
||
get_value == [roll< at] nullary
|
||
incr_value == [[popd incr_at] unary] dip
|
||
add_value == [+] cons dipd
|
||
incr_step_count == [++] dip
|
||
|
||
AoC2017.5.0 == get_value incr_value add_value incr_step_count
|
||
|
||
''', D)
|
||
|
||
.. code:: ipython2
|
||
|
||
define('F == [popop 5 >=] [roll< popop] [AoC2017.5.0] primrec')
|
||
|
||
.. code:: ipython2
|
||
|
||
J('0 0 [0 3 0 1 -3] F')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
5
|
||
|
||
|
||
Preamble for setting up predicate, ``index``, and ``step-count``
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
We want to go from this to this:
|
||
|
||
::
|
||
|
||
[...] AoC2017.5.preamble
|
||
------------------------------
|
||
0 0 [...] [popop n >=]
|
||
|
||
Where ``n`` is the size of the sequence.
|
||
|
||
The first part is obviously ``0 0 roll<``, then ``dup size``:
|
||
|
||
::
|
||
|
||
[...] 0 0 roll< dup size
|
||
0 0 [...] n
|
||
|
||
Then:
|
||
|
||
::
|
||
|
||
0 0 [...] n [>=] cons [popop] swoncat
|
||
|
||
So:
|
||
|
||
::
|
||
|
||
init-index-and-step-count == 0 0 roll<
|
||
prepare-predicate == dup size [>=] cons [popop] swoncat
|
||
|
||
AoC2017.5.preamble == init-index-and-step-count prepare-predicate
|
||
|
||
.. code:: ipython2
|
||
|
||
DefinitionWrapper.add_definitions('''
|
||
|
||
init-index-and-step-count == 0 0 roll<
|
||
prepare-predicate == dup size [>=] cons [popop] swoncat
|
||
|
||
AoC2017.5.preamble == init-index-and-step-count prepare-predicate
|
||
|
||
AoC2017.5 == AoC2017.5.preamble [roll< popop] [AoC2017.5.0] primrec
|
||
|
||
''', D)
|
||
|
||
.. code:: ipython2
|
||
|
||
J('[0 3 0 1 -3] AoC2017.5')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
5
|
||
|
||
|
||
::
|
||
|
||
AoC2017.5 == AoC2017.5.preamble [roll< popop] [AoC2017.5.0] primrec
|
||
|
||
AoC2017.5.0 == get_value incr_value add_value incr_step_count
|
||
AoC2017.5.preamble == init-index-and-step-count prepare-predicate
|
||
|
||
get_value == [roll< at] nullary
|
||
incr_value == [[popd incr_at] unary] dip
|
||
add_value == [+] cons dipd
|
||
incr_step_count == [++] dip
|
||
|
||
init-index-and-step-count == 0 0 roll<
|
||
prepare-predicate == dup size [>=] cons [popop] swoncat
|
||
|
||
This is by far the largest program I have yet written in Joy. Even with
|
||
the ``incr_at`` function it is still a bear. There may be an arrangement
|
||
of the parameters that would permit more elegant definitions, but it
|
||
still wouldn’t be as efficient as something written in assembly, C, or
|
||
even Python.
|