Spaces in filenames noooo.
This commit is contained in:
parent
507d045a3d
commit
abdece348f
Binary file not shown.
|
|
@ -1,288 +0,0 @@
|
|||
|
||||
Advent of Code 2017
|
||||
===================
|
||||
|
||||
December 1st
|
||||
------------
|
||||
|
||||
[Given] a sequence of digits (your puzzle input) and find the sum of all
|
||||
digits that match the next digit in the list. The list is circular, so
|
||||
the digit after the last digit is the first digit in the list.
|
||||
|
||||
For example:
|
||||
|
||||
- 1122 produces a sum of 3 (1 + 2) because the first digit (1) matches
|
||||
the second digit and the third digit (2) matches the fourth digit.
|
||||
- 1111 produces 4 because each digit (all 1) matches the next.
|
||||
- 1234 produces 0 because no digit matches the next.
|
||||
- 91212129 produces 9 because the only digit that matches the next one
|
||||
is the last digit, 9.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from notebook_preamble import J, V, define
|
||||
|
||||
I'll assume the input is a Joy sequence of integers (as opposed to a
|
||||
string or something else.)
|
||||
|
||||
We might proceed by creating a word that makes a copy of the sequence
|
||||
with the first item moved to the last, and zips it with the original to
|
||||
make a list of pairs, and a another word that adds (one of) each pair to
|
||||
a total if the pair matches.
|
||||
|
||||
::
|
||||
|
||||
AoC2017.1 == pair_up total_matches
|
||||
|
||||
Let's derive ``pair_up``:
|
||||
|
||||
::
|
||||
|
||||
[a b c] pair_up
|
||||
-------------------------
|
||||
[[a b] [b c] [c a]]
|
||||
|
||||
Straightforward (although the order of each pair is reversed, due to the
|
||||
way ``zip`` works, but it doesn't matter for this program):
|
||||
|
||||
::
|
||||
|
||||
[a b c] dup
|
||||
[a b c] [a b c] uncons swap
|
||||
[a b c] [b c] a unit concat
|
||||
[a b c] [b c a] zip
|
||||
[[b a] [c b] [a c]]
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('pair_up == dup uncons swap unit concat zip')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 3] pair_up')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[[2 1] [3 2] [1 3]]
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 2 3] pair_up')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[[2 1] [2 2] [3 2] [1 3]]
|
||||
|
||||
|
||||
Now we need to derive ``total_matches``. It will be a ``step`` function:
|
||||
|
||||
::
|
||||
|
||||
total_matches == 0 swap [F] step
|
||||
|
||||
Where ``F`` will have the pair to work with, and it will basically be a
|
||||
``branch`` or ``ifte``.
|
||||
|
||||
::
|
||||
|
||||
total [n m] F
|
||||
|
||||
It will probably be easier to write if we dequote the pair:
|
||||
|
||||
::
|
||||
|
||||
total [n m] i F′
|
||||
----------------------
|
||||
total n m F′
|
||||
|
||||
Now ``F′`` becomes just:
|
||||
|
||||
::
|
||||
|
||||
total n m [=] [pop +] [popop] ifte
|
||||
|
||||
So:
|
||||
|
||||
::
|
||||
|
||||
F == i [=] [pop +] [popop] ifte
|
||||
|
||||
And thus:
|
||||
|
||||
::
|
||||
|
||||
total_matches == 0 swap [i [=] [pop +] [popop] ifte] step
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('total_matches == 0 swap [i [=] [pop +] [popop] ifte] step')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 3] pair_up total_matches')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
0
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 2 3] pair_up total_matches')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
2
|
||||
|
||||
|
||||
Now we can define our main program and evaluate it on the examples.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('AoC2017.1 == pair_up total_matches')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 1 2 2] AoC2017.1')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
3
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 1 1 1] AoC2017.1')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
4
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 3 4] AoC2017.1')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
0
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[9 1 2 1 2 1 2 9] AoC2017.1')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
9
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[9 1 2 1 2 1 2 9] AoC2017.1')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
9
|
||||
|
||||
|
||||
::
|
||||
|
||||
pair_up == dup uncons swap unit concat zip
|
||||
total_matches == 0 swap [i [=] [pop +] [popop] ifte] step
|
||||
|
||||
AoC2017.1 == pair_up total_matches
|
||||
|
||||
Now the paired digit is "halfway" round.
|
||||
|
||||
::
|
||||
|
||||
[a b c d] dup size 2 / [drop] [take reverse] cleave concat zip
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 3 4] dup size 2 / [drop] [take reverse] cleave concat zip')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[[3 1] [4 2] [1 3] [2 4]]
|
||||
|
||||
|
||||
I realized that each pair is repeated...
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 3 4] dup size 2 / [drop] [take reverse] cleave zip')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[1 2 3 4] [[1 3] [2 4]]
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('AoC2017.1.extra == dup size 2 / [drop] [take reverse] cleave zip swap pop total_matches 2 *')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 1 2] AoC2017.1.extra')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
6
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 2 1] AoC2017.1.extra')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
0
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 3 4 2 5] AoC2017.1.extra')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
4
|
||||
|
||||
|
||||
Refactor FTW
|
||||
============
|
||||
|
||||
With Joy a great deal of the heuristics from Forth programming carry
|
||||
over nicely. For example, refactoring into small, well-scoped commands
|
||||
with mnemonic names...
|
||||
|
||||
::
|
||||
|
||||
rotate_seq == uncons swap unit concat
|
||||
pair_up == dup rotate_seq zip
|
||||
add_if_match == [=] [pop +] [popop] ifte
|
||||
total_matches == [i add_if_match] step_zero
|
||||
|
||||
AoC2017.1 == pair_up total_matches
|
||||
|
||||
half_of_size == dup size 2 /
|
||||
split_at == [drop] [take reverse] cleave
|
||||
pair_up.extra == half_of_size split_at zip swap pop
|
||||
|
||||
AoC2017.1.extra == pair_up.extra total_matches 2 *
|
||||
|
|
@ -1,432 +0,0 @@
|
|||
|
||||
Advent of Code 2017
|
||||
===================
|
||||
|
||||
December 2nd
|
||||
------------
|
||||
|
||||
For each row, determine the difference between the largest value and the
|
||||
smallest value; the checksum is the sum of all of these differences.
|
||||
|
||||
For example, given the following spreadsheet:
|
||||
|
||||
::
|
||||
|
||||
5 1 9 5
|
||||
7 5 3
|
||||
2 4 6 8
|
||||
|
||||
- The first row's largest and smallest values are 9 and 1, and their
|
||||
difference is 8.
|
||||
- The second row's largest and smallest values are 7 and 3, and their
|
||||
difference is 4.
|
||||
- The third row's difference is 6.
|
||||
|
||||
In this example, the spreadsheet's checksum would be 8 + 4 + 6 = 18.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from notebook_preamble import J, V, define
|
||||
|
||||
I'll assume the input is a Joy sequence of sequences of integers.
|
||||
|
||||
::
|
||||
|
||||
[[5 1 9 5]
|
||||
[7 5 3]
|
||||
[2 4 6 8]]
|
||||
|
||||
So, obviously, the initial form will be a ``step`` function:
|
||||
|
||||
::
|
||||
|
||||
AoC2017.2 == 0 swap [F +] step
|
||||
|
||||
This function ``F`` must get the ``max`` and ``min`` of a row of numbers
|
||||
and subtract. We can define a helper function ``maxmin`` which does
|
||||
this:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('maxmin == [max] [min] cleave')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 2 3] maxmin')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
3 1
|
||||
|
||||
|
||||
Then ``F`` just does that then subtracts the min from the max:
|
||||
|
||||
::
|
||||
|
||||
F == maxmin -
|
||||
|
||||
So:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('AoC2017.2 == [maxmin - +] step_zero')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('''
|
||||
|
||||
[[5 1 9 5]
|
||||
[7 5 3]
|
||||
[2 4 6 8]] AoC2017.2
|
||||
|
||||
''')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
18
|
||||
|
||||
|
||||
...find the only two numbers in each row where one evenly divides the
|
||||
other - that is, where the result of the division operation is a whole
|
||||
number. They would like you to find those numbers on each line, divide
|
||||
them, and add up each line's result.
|
||||
|
||||
For example, given the following spreadsheet:
|
||||
|
||||
::
|
||||
|
||||
5 9 2 8
|
||||
9 4 7 3
|
||||
3 8 6 5
|
||||
|
||||
- In the first row, the only two numbers that evenly divide are 8 and
|
||||
2; the result of this division is 4.
|
||||
- In the second row, the two numbers are 9 and 3; the result is 3.
|
||||
- In the third row, the result is 2.
|
||||
|
||||
In this example, the sum of the results would be 4 + 3 + 2 = 9.
|
||||
|
||||
What is the sum of each row's result in your puzzle input?
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[5 9 2 8] sort reverse')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[9 8 5 2]
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[9 8 5 2] uncons [swap [divmod] cons] dupdip')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[8 5 2] [9 divmod] [8 5 2]
|
||||
|
||||
|
||||
::
|
||||
|
||||
[9 8 5 2] uncons [swap [divmod] cons F] dupdip G
|
||||
[8 5 2] [9 divmod] F [8 5 2] G
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
V('[8 5 2] [9 divmod] [uncons swap] dip dup [i not] dip')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
. [8 5 2] [9 divmod] [uncons swap] dip dup [i not] dip
|
||||
[8 5 2] . [9 divmod] [uncons swap] dip dup [i not] dip
|
||||
[8 5 2] [9 divmod] . [uncons swap] dip dup [i not] dip
|
||||
[8 5 2] [9 divmod] [uncons swap] . dip dup [i not] dip
|
||||
[8 5 2] . uncons swap [9 divmod] dup [i not] dip
|
||||
8 [5 2] . swap [9 divmod] dup [i not] dip
|
||||
[5 2] 8 . [9 divmod] dup [i not] dip
|
||||
[5 2] 8 [9 divmod] . dup [i not] dip
|
||||
[5 2] 8 [9 divmod] [9 divmod] . [i not] dip
|
||||
[5 2] 8 [9 divmod] [9 divmod] [i not] . dip
|
||||
[5 2] 8 [9 divmod] . i not [9 divmod]
|
||||
[5 2] 8 . 9 divmod not [9 divmod]
|
||||
[5 2] 8 9 . divmod not [9 divmod]
|
||||
[5 2] 1 1 . not [9 divmod]
|
||||
[5 2] 1 False . [9 divmod]
|
||||
[5 2] 1 False [9 divmod] .
|
||||
|
||||
|
||||
Tricky
|
||||
------
|
||||
|
||||
Let's think.
|
||||
|
||||
Given a *sorted* sequence (from highest to lowest) we want to \* for
|
||||
head, tail in sequence \* for term in tail: \* check if the head % term
|
||||
== 0 \* if so compute head / term and terminate loop \* else continue
|
||||
|
||||
So we want a ``loop`` I think
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
[a b c d] True [Q] loop
|
||||
[a b c d] Q [Q] loop
|
||||
|
||||
``Q`` should either leave the result and False, or the ``rest`` and
|
||||
True.
|
||||
|
||||
::
|
||||
|
||||
[a b c d] Q
|
||||
-----------------
|
||||
result 0
|
||||
|
||||
[a b c d] Q
|
||||
-----------------
|
||||
[b c d] 1
|
||||
|
||||
This suggests that ``Q`` should start with:
|
||||
|
||||
::
|
||||
|
||||
[a b c d] uncons dup roll<
|
||||
[b c d] [b c d] a
|
||||
|
||||
Now we just have to ``pop`` it if we don't need it.
|
||||
|
||||
::
|
||||
|
||||
[b c d] [b c d] a [P] [T] [cons] app2 popdd [E] primrec
|
||||
[b c d] [b c d] [a P] [a T] [E] primrec
|
||||
|
||||
--------------
|
||||
|
||||
::
|
||||
|
||||
w/ Q == [% not] [T] [F] primrec
|
||||
|
||||
[a b c d] uncons
|
||||
a [b c d] tuck
|
||||
[b c d] a [b c d] uncons
|
||||
[b c d] a b [c d] roll>
|
||||
[b c d] [c d] a b Q
|
||||
[b c d] [c d] a b [% not] [T] [F] primrec
|
||||
|
||||
[b c d] [c d] a b T
|
||||
[b c d] [c d] a b / roll> popop 0
|
||||
|
||||
[b c d] [c d] a b F Q
|
||||
[b c d] [c d] a b pop swap uncons ... Q
|
||||
[b c d] [c d] a swap uncons ... Q
|
||||
[b c d] a [c d] uncons ... Q
|
||||
[b c d] a c [d] roll> Q
|
||||
[b c d] [d] a c Q
|
||||
|
||||
Q == [% not] [/ roll> popop 0] [pop swap uncons roll>] primrec
|
||||
|
||||
uncons tuck uncons roll> Q
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[8 5 3 2] 9 [swap] [% not] [cons] app2 popdd')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[8 5 3 2] [9 swap] [9 % not]
|
||||
|
||||
|
||||
--------------
|
||||
|
||||
::
|
||||
|
||||
[a b c d] uncons
|
||||
a [b c d] tuck
|
||||
[b c d] a [b c d] [not] [popop 1] [Q] ifte
|
||||
|
||||
[b c d] a [] popop 1
|
||||
[b c d] 1
|
||||
|
||||
[b c d] a [b c d] Q
|
||||
|
||||
|
||||
a [...] Q
|
||||
---------------
|
||||
result 0
|
||||
|
||||
a [...] Q
|
||||
---------------
|
||||
1
|
||||
|
||||
|
||||
w/ Q == [first % not] [first / 0] [rest [not] [popop 1]] [ifte]
|
||||
|
||||
|
||||
|
||||
a [b c d] [first % not] [first / 0] [rest [not] [popop 1]] [ifte]
|
||||
a [b c d] first % not
|
||||
a b % not
|
||||
a%b not
|
||||
bool(a%b)
|
||||
|
||||
a [b c d] [first % not] [first / 0] [rest [not] [popop 1]] [ifte]
|
||||
a [b c d] first / 0
|
||||
a b / 0
|
||||
a/b 0
|
||||
|
||||
a [b c d] [first % not] [first / 0] [rest [not] [popop 1]] [ifte]
|
||||
a [b c d] rest [not] [popop 1] [Q] ifte
|
||||
a [c d] [not] [popop 1] [Q] ifte
|
||||
a [c d] [not] [popop 1] [Q] ifte
|
||||
|
||||
a [c d] [not] [popop 1] [Q] ifte
|
||||
a [c d] not
|
||||
|
||||
a [] popop 1
|
||||
1
|
||||
|
||||
a [c d] Q
|
||||
|
||||
|
||||
uncons tuck [first % not] [first / 0] [rest [not] [popop 1]] [ifte]
|
||||
|
||||
I finally sat down with a piece of paper and blocked it out.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
First, I made a function ``G`` that expects a number and a sequence of
|
||||
candidates and return the result or zero:
|
||||
|
||||
::
|
||||
|
||||
n [...] G
|
||||
---------------
|
||||
result
|
||||
|
||||
n [...] G
|
||||
---------------
|
||||
0
|
||||
|
||||
It's a recursive function that conditionally executes the recursive part
|
||||
of its recursive branch
|
||||
|
||||
::
|
||||
|
||||
[Pg] [E] [R1 [Pi] [T]] [ifte] genrec
|
||||
|
||||
The recursive branch is the else-part of the inner ``ifte``:
|
||||
|
||||
::
|
||||
|
||||
G == [Pg] [E] [R1 [Pi] [T]] [ifte] genrec
|
||||
== [Pg] [E] [R1 [Pi] [T] [G] ifte] ifte
|
||||
|
||||
But this is in hindsight. Going forward I derived:
|
||||
|
||||
::
|
||||
|
||||
G == [first % not]
|
||||
[first /]
|
||||
[rest [not] [popop 0]]
|
||||
[ifte] genrec
|
||||
|
||||
The predicate detects if the ``n`` can be evenly divided by the
|
||||
``first`` item in the list. If so, the then-part returns the result.
|
||||
Otherwise, we have:
|
||||
|
||||
::
|
||||
|
||||
n [m ...] rest [not] [popop 0] [G] ifte
|
||||
n [...] [not] [popop 0] [G] ifte
|
||||
|
||||
This ``ifte`` guards against empty sequences and returns zero in that
|
||||
case, otherwise it executes ``G``.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('G == [first % not] [first /] [rest [not] [popop 0]] [ifte] genrec')
|
||||
|
||||
Now we need a word that uses ``G`` on each (head, tail) pair of a
|
||||
sequence until it finds a (non-zero) result. It's going to be designed
|
||||
to work on a stack that has some candidate ``n``, a sequence of possible
|
||||
divisors, and a result that is zero to signal to continue (a non-zero
|
||||
value implies that it is the discovered result):
|
||||
|
||||
::
|
||||
|
||||
n [...] p find-result
|
||||
---------------------------
|
||||
result
|
||||
|
||||
It applies ``G`` using ``nullary`` because if it fails with one
|
||||
candidate it needs the list to get the next one (the list is otherwise
|
||||
consumed by ``G``.)
|
||||
|
||||
::
|
||||
|
||||
find-result == [0 >] [roll> popop] [roll< popop uncons [G] nullary] primrec
|
||||
|
||||
n [...] p [0 >] [roll> popop] [roll< popop uncons [G] nullary] primrec
|
||||
|
||||
The base-case is trivial, return the (non-zero) result. The recursive
|
||||
branch...
|
||||
|
||||
::
|
||||
|
||||
n [...] p roll< popop uncons [G] nullary find-result
|
||||
[...] p n popop uncons [G] nullary find-result
|
||||
[...] uncons [G] nullary find-result
|
||||
m [..] [G] nullary find-result
|
||||
m [..] p find-result
|
||||
|
||||
The puzzle states that the input is well-formed, meaning that we can
|
||||
expect a result before the row sequence empties and so do not need to
|
||||
guard the ``uncons``.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('find-result == [0 >] [roll> popop] [roll< popop uncons [G] nullary] primrec')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[11 9 8 7 3 2] 0 tuck find-result')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
3.0
|
||||
|
||||
|
||||
In order to get the thing started, we need to ``sort`` the list in
|
||||
descending order, then prime the ``find-result`` function with a dummy
|
||||
candidate value and zero ("continue") flag.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('prep-row == sort reverse 0 tuck')
|
||||
|
||||
Now we can define our program.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('AoC20017.2.extra == [prep-row find-result +] step_zero')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('''
|
||||
|
||||
[[5 9 2 8]
|
||||
[9 4 7 3]
|
||||
[3 8 6 5]] AoC20017.2.extra
|
||||
|
||||
''')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
9.0
|
||||
|
||||
|
|
@ -1,973 +0,0 @@
|
|||
|
||||
Advent of Code 2017
|
||||
===================
|
||||
|
||||
December 3rd
|
||||
------------
|
||||
|
||||
You come across an experimental new kind of memory stored on an infinite
|
||||
two-dimensional grid.
|
||||
|
||||
Each square on the grid is allocated in a spiral pattern starting at a
|
||||
location marked 1 and then counting up while spiraling outward. For
|
||||
example, the first few squares are allocated like this:
|
||||
|
||||
::
|
||||
|
||||
17 16 15 14 13
|
||||
18 5 4 3 12
|
||||
19 6 1 2 11
|
||||
20 7 8 9 10
|
||||
21 22 23---> ...
|
||||
|
||||
While this is very space-efficient (no squares are skipped), requested
|
||||
data must be carried back to square 1 (the location of the only access
|
||||
port for this memory system) by programs that can only move up, down,
|
||||
left, or right. They always take the shortest path: the Manhattan
|
||||
Distance between the location of the data and square 1.
|
||||
|
||||
For example:
|
||||
|
||||
- Data from square 1 is carried 0 steps, since it's at the access port.
|
||||
- Data from square 12 is carried 3 steps, such as: down, left, left.
|
||||
- Data from square 23 is carried only 2 steps: up twice.
|
||||
- Data from square 1024 must be carried 31 steps.
|
||||
|
||||
How many steps are required to carry the data from the square identified
|
||||
in your puzzle input all the way to the access port?
|
||||
|
||||
Analysis
|
||||
~~~~~~~~
|
||||
|
||||
I freely admit that I worked out the program I wanted to write using
|
||||
graph paper and some Python doodles. There's no point in trying to write
|
||||
a Joy program until I'm sure I understand the problem well enough.
|
||||
|
||||
The first thing I did was to write a column of numbers from 1 to n (32
|
||||
as it happens) and next to them the desired output number, to look for
|
||||
patterns directly:
|
||||
|
||||
::
|
||||
|
||||
1 0
|
||||
2 1
|
||||
3 2
|
||||
4 1
|
||||
5 2
|
||||
6 1
|
||||
7 2
|
||||
8 1
|
||||
9 2
|
||||
10 3
|
||||
11 2
|
||||
12 3
|
||||
13 4
|
||||
14 3
|
||||
15 2
|
||||
16 3
|
||||
17 4
|
||||
18 3
|
||||
19 2
|
||||
20 3
|
||||
21 4
|
||||
22 3
|
||||
23 2
|
||||
24 3
|
||||
25 4
|
||||
26 5
|
||||
27 4
|
||||
28 3
|
||||
29 4
|
||||
30 5
|
||||
31 6
|
||||
32 5
|
||||
|
||||
There are four groups repeating for a given "rank", then the pattern
|
||||
enlarges and four groups repeat again, etc.
|
||||
|
||||
::
|
||||
|
||||
1 2
|
||||
3 2 3 4
|
||||
5 4 3 4 5 6
|
||||
7 6 5 4 5 6 7 8
|
||||
9 8 7 6 5 6 7 8 9 10
|
||||
|
||||
Four of this pyramid interlock to tile the plane extending from the
|
||||
initial "1" square.
|
||||
|
||||
::
|
||||
|
||||
2 3 | 4 5 | 6 7 | 8 9
|
||||
10 11 12 13|14 15 16 17|18 19 20 21|22 23 24 25
|
||||
|
||||
And so on.
|
||||
|
||||
We can figure out the pattern for a row of the pyramid at a given "rank"
|
||||
:math:`k`:
|
||||
|
||||
:math:`2k - 1, 2k - 2, ..., k, k + 1, k + 2, ..., 2k`
|
||||
|
||||
or
|
||||
|
||||
:math:`k + (k - 1), k + (k - 2), ..., k, k + 1, k + 2, ..., k + k`
|
||||
|
||||
This shows that the series consists at each place of :math:`k` plus some
|
||||
number that begins at :math:`k - 1`, decreases to zero, then increases
|
||||
to :math:`k`. Each row has :math:`2k` members.
|
||||
|
||||
Let's figure out how, given an index into a row, we can calculate the
|
||||
value there. The index will be from 0 to :math:`k - 1`.
|
||||
|
||||
Let's look at an example, with :math:`k = 4`:
|
||||
|
||||
::
|
||||
|
||||
0 1 2 3 4 5 6 7
|
||||
7 6 5 4 5 6 7 8
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
k = 4
|
||||
|
||||
Subtract :math:`k` from the index and take the absolute value:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
for n in range(2 * k):
|
||||
print abs(n - k),
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
4 3 2 1 0 1 2 3
|
||||
|
||||
|
||||
Not quite. Subtract :math:`k - 1` from the index and take the absolute
|
||||
value:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
for n in range(2 * k):
|
||||
print abs(n - (k - 1)),
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
3 2 1 0 1 2 3 4
|
||||
|
||||
|
||||
Great, now add :math:`k`...
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
for n in range(2 * k):
|
||||
print abs(n - (k - 1)) + k,
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
7 6 5 4 5 6 7 8
|
||||
|
||||
|
||||
So to write a function that can give us the value of a row at a given
|
||||
index:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
def row_value(k, i):
|
||||
i %= (2 * k) # wrap the index at the row boundary.
|
||||
return abs(i - (k - 1)) + k
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
k = 5
|
||||
for i in range(2 * k):
|
||||
print row_value(k, i),
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
9 8 7 6 5 6 7 8 9 10
|
||||
|
||||
|
||||
(I'm leaving out details of how I figured this all out and just giving
|
||||
the relevent bits. It took a little while to zero in of the aspects of
|
||||
the pattern that were important for the task.)
|
||||
|
||||
Finding the rank and offset of a number.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now that we can compute the desired output value for a given rank and
|
||||
the offset (index) into that rank, we need to determine how to find the
|
||||
rank and offset of a number.
|
||||
|
||||
The rank is easy to find by iteratively stripping off the amount already
|
||||
covered by previous ranks until you find the one that brackets the
|
||||
target number. Because each row is :math:`2k` places and there are
|
||||
:math:`4` per rank each rank contains :math:`8k` places. Counting the
|
||||
initial square we have:
|
||||
|
||||
:math:`corner_k = 1 + \sum_{n=1}^k 8n`
|
||||
|
||||
I'm not mathematically sophisticated enough to turn this directly into a
|
||||
formula (but Sympy is, see below.) I'm going to write a simple Python
|
||||
function to iterate and search:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
def rank_and_offset(n):
|
||||
assert n >= 2 # Guard the domain.
|
||||
n -= 2 # Subtract two,
|
||||
# one for the initial square,
|
||||
# and one because we are counting from 1 instead of 0.
|
||||
k = 1
|
||||
while True:
|
||||
m = 8 * k # The number of places total in this rank, 4(2k).
|
||||
if n < m:
|
||||
return k, n % (2 * k)
|
||||
n -= m # Remove this rank's worth.
|
||||
k += 1
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
for n in range(2, 51):
|
||||
print n, rank_and_offset(n)
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
2 (1, 0)
|
||||
3 (1, 1)
|
||||
4 (1, 0)
|
||||
5 (1, 1)
|
||||
6 (1, 0)
|
||||
7 (1, 1)
|
||||
8 (1, 0)
|
||||
9 (1, 1)
|
||||
10 (2, 0)
|
||||
11 (2, 1)
|
||||
12 (2, 2)
|
||||
13 (2, 3)
|
||||
14 (2, 0)
|
||||
15 (2, 1)
|
||||
16 (2, 2)
|
||||
17 (2, 3)
|
||||
18 (2, 0)
|
||||
19 (2, 1)
|
||||
20 (2, 2)
|
||||
21 (2, 3)
|
||||
22 (2, 0)
|
||||
23 (2, 1)
|
||||
24 (2, 2)
|
||||
25 (2, 3)
|
||||
26 (3, 0)
|
||||
27 (3, 1)
|
||||
28 (3, 2)
|
||||
29 (3, 3)
|
||||
30 (3, 4)
|
||||
31 (3, 5)
|
||||
32 (3, 0)
|
||||
33 (3, 1)
|
||||
34 (3, 2)
|
||||
35 (3, 3)
|
||||
36 (3, 4)
|
||||
37 (3, 5)
|
||||
38 (3, 0)
|
||||
39 (3, 1)
|
||||
40 (3, 2)
|
||||
41 (3, 3)
|
||||
42 (3, 4)
|
||||
43 (3, 5)
|
||||
44 (3, 0)
|
||||
45 (3, 1)
|
||||
46 (3, 2)
|
||||
47 (3, 3)
|
||||
48 (3, 4)
|
||||
49 (3, 5)
|
||||
50 (4, 0)
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
for n in range(2, 51):
|
||||
k, i = rank_and_offset(n)
|
||||
print n, row_value(k, i)
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
2 1
|
||||
3 2
|
||||
4 1
|
||||
5 2
|
||||
6 1
|
||||
7 2
|
||||
8 1
|
||||
9 2
|
||||
10 3
|
||||
11 2
|
||||
12 3
|
||||
13 4
|
||||
14 3
|
||||
15 2
|
||||
16 3
|
||||
17 4
|
||||
18 3
|
||||
19 2
|
||||
20 3
|
||||
21 4
|
||||
22 3
|
||||
23 2
|
||||
24 3
|
||||
25 4
|
||||
26 5
|
||||
27 4
|
||||
28 3
|
||||
29 4
|
||||
30 5
|
||||
31 6
|
||||
32 5
|
||||
33 4
|
||||
34 3
|
||||
35 4
|
||||
36 5
|
||||
37 6
|
||||
38 5
|
||||
39 4
|
||||
40 3
|
||||
41 4
|
||||
42 5
|
||||
43 6
|
||||
44 5
|
||||
45 4
|
||||
46 3
|
||||
47 4
|
||||
48 5
|
||||
49 6
|
||||
50 7
|
||||
|
||||
|
||||
Putting it all together
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
def row_value(k, i):
|
||||
return abs(i - (k - 1)) + k
|
||||
|
||||
|
||||
def rank_and_offset(n):
|
||||
n -= 2 # Subtract two,
|
||||
# one for the initial square,
|
||||
# and one because we are counting from 1 instead of 0.
|
||||
k = 1
|
||||
while True:
|
||||
m = 8 * k # The number of places total in this rank, 4(2k).
|
||||
if n < m:
|
||||
return k, n % (2 * k)
|
||||
n -= m # Remove this rank's worth.
|
||||
k += 1
|
||||
|
||||
|
||||
def aoc20173(n):
|
||||
if n <= 1:
|
||||
return 0
|
||||
k, i = rank_and_offset(n)
|
||||
return row_value(k, i)
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
aoc20173(23)
|
||||
|
||||
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
2
|
||||
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
aoc20173(23000)
|
||||
|
||||
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
105
|
||||
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
aoc20173(23000000000000)
|
||||
|
||||
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
4572225
|
||||
|
||||
|
||||
|
||||
Sympy to the Rescue
|
||||
===================
|
||||
|
||||
Find the rank for large numbers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using e.g. Sympy we can find the rank directly by solving for the roots
|
||||
of an equation. For large numbers this will (eventually) be faster than
|
||||
iterating as ``rank_and_offset()`` does.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from sympy import floor, lambdify, solve, symbols
|
||||
from sympy import init_printing
|
||||
init_printing()
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
k = symbols('k')
|
||||
|
||||
Since
|
||||
|
||||
:math:`1 + 2 + 3 + ... + N = \frac{N(N + 1)}{2}`
|
||||
|
||||
and
|
||||
|
||||
:math:`\sum_{n=1}^k 8n = 8(\sum_{n=1}^k n) = 8\frac{k(k + 1)}{2}`
|
||||
|
||||
We want:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
E = 2 + 8 * k * (k + 1) / 2 # For the reason for adding 2 see above.
|
||||
|
||||
E
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
4 k \left(k + 1\right) + 2
|
||||
|
||||
|
||||
|
||||
We can write a function to solve for :math:`k` given some :math:`n`...
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
def rank_of(n):
|
||||
return floor(max(solve(E - n, k))) + 1
|
||||
|
||||
First ``solve()`` for :math:`E - n = 0` which has two solutions (because
|
||||
the equation is quadratic so it has two roots) and since we only care
|
||||
about the larger one we use ``max()`` to select it. It will generally
|
||||
not be a nice integer (unless :math:`n` is the number of an end-corner
|
||||
of a rank) so we take the ``floor()`` and add 1 to get the integer rank
|
||||
of :math:`n`. (Taking the ``ceiling()`` gives off-by-one errors on the
|
||||
rank boundaries. I don't know why. I'm basically like a monkey doing
|
||||
math here.) =-D
|
||||
|
||||
It gives correct answers:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
for n in (9, 10, 25, 26, 49, 50):
|
||||
print n, rank_of(n)
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
9 1
|
||||
10 2
|
||||
25 2
|
||||
26 3
|
||||
49 3
|
||||
50 4
|
||||
|
||||
|
||||
And it runs much faster (at least for large numbers):
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
%time rank_of(23000000000000) # Compare runtime with rank_and_offset()!
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
CPU times: user 68 ms, sys: 8 ms, total: 76 ms
|
||||
Wall time: 73.8 ms
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
2397916
|
||||
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
%time rank_and_offset(23000000000000)
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
CPU times: user 308 ms, sys: 0 ns, total: 308 ms
|
||||
Wall time: 306 ms
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
\left ( 2397916, \quad 223606\right )
|
||||
|
||||
|
||||
|
||||
After finding the rank you would still have to find the actual value of
|
||||
the rank's first corner and subtract it (plus 2) from the number and
|
||||
compute the offset as above and then the final output, but this overhead
|
||||
is partially shared by the other method, and overshadowed by the time it
|
||||
(the other iterative method) would take for really big inputs.
|
||||
|
||||
The fun thing to do here would be to graph the actual runtime of both
|
||||
methods against each other to find the trade-off point.
|
||||
|
||||
It took me a second to realize I could do this...
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sympy is a *symbolic* math library, and it supports symbolic
|
||||
manipulation of equations. I can put in :math:`y` (instead of a value)
|
||||
and ask it to solve for :math:`k`.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
y = symbols('y')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
g, f = solve(E - y, k)
|
||||
|
||||
The equation is quadratic so there are two roots, we are interested in
|
||||
the greater one...
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
g
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
- \frac{1}{2} \sqrt{y - 1} - \frac{1}{2}
|
||||
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
f
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
\frac{1}{2} \sqrt{y - 1} - \frac{1}{2}
|
||||
|
||||
|
||||
|
||||
Now we can take the ``floor()``, add 1, and ``lambdify()`` the equation
|
||||
to get a Python function that calculates the rank directly.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
floor(f) + 1
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
\lfloor{\frac{1}{2} \sqrt{y - 1} - \frac{1}{2}}\rfloor + 1
|
||||
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
F = lambdify(y, floor(f) + 1)
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
for n in (9, 10, 25, 26, 49, 50):
|
||||
print n, int(F(n))
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
9 1
|
||||
10 2
|
||||
25 2
|
||||
26 3
|
||||
49 3
|
||||
50 4
|
||||
|
||||
|
||||
It's pretty fast.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
%time int(F(23000000000000)) # The clear winner.
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
|
||||
Wall time: 11.9 µs
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
2397916
|
||||
|
||||
|
||||
|
||||
Knowing the equation we could write our own function manually, but the
|
||||
speed is no better.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from math import floor as mfloor, sqrt
|
||||
|
||||
def mrank_of(n):
|
||||
return int(mfloor(sqrt(23000000000000 - 1) / 2 - 0.5) + 1)
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
%time mrank_of(23000000000000)
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
|
||||
Wall time: 12.9 µs
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
2397916
|
||||
|
||||
|
||||
|
||||
Given :math:`n` and a rank, compute the offset.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now that we have a fast way to get the rank, we still need to use it to
|
||||
compute the offset into a pyramid row.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
def offset_of(n, k):
|
||||
return (n - 2 + 4 * k * (k - 1)) % (2 * k)
|
||||
|
||||
(Note the sneaky way the sign changes from :math:`k(k + 1)` to
|
||||
:math:`k(k - 1)`. This is because we want to subract the
|
||||
:math:`(k - 1)`\ th rank's total places (its own and those of lesser
|
||||
rank) from our :math:`n` of rank :math:`k`. Substituting :math:`k - 1`
|
||||
for :math:`k` in :math:`k(k + 1)` gives :math:`(k - 1)(k - 1 + 1)`,
|
||||
which of course simplifies to :math:`k(k - 1)`.)
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
offset_of(23000000000000, 2397916)
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
223606
|
||||
|
||||
|
||||
|
||||
So, we can compute the rank, then the offset, then the row value.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
def rank_of(n):
|
||||
return int(mfloor(sqrt(n - 1) / 2 - 0.5) + 1)
|
||||
|
||||
|
||||
def offset_of(n, k):
|
||||
return (n - 2 + 4 * k * (k - 1)) % (2 * k)
|
||||
|
||||
|
||||
def row_value(k, i):
|
||||
return abs(i - (k - 1)) + k
|
||||
|
||||
|
||||
def aoc20173(n):
|
||||
k = rank_of(n)
|
||||
i = offset_of(n, k)
|
||||
return row_value(k, i)
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
aoc20173(23)
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
2
|
||||
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
aoc20173(23000)
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
105
|
||||
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
aoc20173(23000000000000)
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
4572225
|
||||
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
%time aoc20173(23000000000000000000000000) # Fast for large values.
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
|
||||
Wall time: 20 µs
|
||||
|
||||
|
||||
|
||||
|
||||
.. math::
|
||||
|
||||
2690062495969
|
||||
|
||||
|
||||
|
||||
A Joy Version
|
||||
=============
|
||||
|
||||
At this point I feel confident that I can implement a concise version of
|
||||
this code in Joy. ;-)
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from notebook_preamble import J, V, define
|
||||
|
||||
``rank_of``
|
||||
~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
n rank_of
|
||||
---------------
|
||||
k
|
||||
|
||||
The translation is straightforward.
|
||||
|
||||
::
|
||||
|
||||
int(floor(sqrt(n - 1) / 2 - 0.5) + 1)
|
||||
|
||||
rank_of == -- sqrt 2 / 0.5 - floor ++
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('rank_of == -- sqrt 2 / 0.5 - floor ++')
|
||||
|
||||
``offset_of``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
n k offset_of
|
||||
-------------------
|
||||
i
|
||||
|
||||
(n - 2 + 4 * k * (k - 1)) % (2 * k)
|
||||
|
||||
A little tricky...
|
||||
|
||||
::
|
||||
|
||||
n k dup 2 *
|
||||
n k k 2 *
|
||||
n k k*2 [Q] dip %
|
||||
n k Q k*2 %
|
||||
|
||||
n k dup --
|
||||
n k k --
|
||||
n k k-1 4 * * 2 + -
|
||||
n k*k-1*4 2 + -
|
||||
n k*k-1*4+2 -
|
||||
n-k*k-1*4+2
|
||||
|
||||
n-k*k-1*4+2 k*2 %
|
||||
n-k*k-1*4+2%k*2
|
||||
|
||||
Ergo:
|
||||
|
||||
::
|
||||
|
||||
offset_of == dup 2 * [dup -- 4 * * 2 + -] dip %
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('offset_of == dup 2 * [dup -- 4 * * 2 + -] dip %')
|
||||
|
||||
``row_value``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
k i row_value
|
||||
-------------------
|
||||
n
|
||||
|
||||
abs(i - (k - 1)) + k
|
||||
|
||||
k i over -- - abs +
|
||||
k i k -- - abs +
|
||||
k i k-1 - abs +
|
||||
k i-k-1 abs +
|
||||
k |i-k-1| +
|
||||
k+|i-k-1|
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('row_value == over -- - abs +')
|
||||
|
||||
``aoc2017.3``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
n aoc2017.3
|
||||
-----------------
|
||||
m
|
||||
|
||||
n dup rank_of
|
||||
n k [offset_of] dupdip
|
||||
n k offset_of k
|
||||
i k swap row_value
|
||||
k i row_value
|
||||
m
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('aoc2017.3 == dup rank_of [offset_of] dupdip swap row_value')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('23 aoc2017.3')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
2
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('23000 aoc2017.3')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
105
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
V('23000000000000 aoc2017.3')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
. 23000000000000 aoc2017.3
|
||||
23000000000000 . aoc2017.3
|
||||
23000000000000 . dup rank_of [offset_of] dupdip swap row_value
|
||||
23000000000000 23000000000000 . rank_of [offset_of] dupdip swap row_value
|
||||
23000000000000 23000000000000 . -- sqrt 2 / 0.5 - floor ++ [offset_of] dupdip swap row_value
|
||||
23000000000000 22999999999999 . sqrt 2 / 0.5 - floor ++ [offset_of] dupdip swap row_value
|
||||
23000000000000 4795831.523312615 . 2 / 0.5 - floor ++ [offset_of] dupdip swap row_value
|
||||
23000000000000 4795831.523312615 2 . / 0.5 - floor ++ [offset_of] dupdip swap row_value
|
||||
23000000000000 2397915.7616563076 . 0.5 - floor ++ [offset_of] dupdip swap row_value
|
||||
23000000000000 2397915.7616563076 0.5 . - floor ++ [offset_of] dupdip swap row_value
|
||||
23000000000000 2397915.2616563076 . floor ++ [offset_of] dupdip swap row_value
|
||||
23000000000000 2397915 . ++ [offset_of] dupdip swap row_value
|
||||
23000000000000 2397916 . [offset_of] dupdip swap row_value
|
||||
23000000000000 2397916 [offset_of] . dupdip swap row_value
|
||||
23000000000000 2397916 . offset_of 2397916 swap row_value
|
||||
23000000000000 2397916 . dup 2 * [dup -- 4 * * 2 + -] dip % 2397916 swap row_value
|
||||
23000000000000 2397916 2397916 . 2 * [dup -- 4 * * 2 + -] dip % 2397916 swap row_value
|
||||
23000000000000 2397916 2397916 2 . * [dup -- 4 * * 2 + -] dip % 2397916 swap row_value
|
||||
23000000000000 2397916 4795832 . [dup -- 4 * * 2 + -] dip % 2397916 swap row_value
|
||||
23000000000000 2397916 4795832 [dup -- 4 * * 2 + -] . dip % 2397916 swap row_value
|
||||
23000000000000 2397916 . dup -- 4 * * 2 + - 4795832 % 2397916 swap row_value
|
||||
23000000000000 2397916 2397916 . -- 4 * * 2 + - 4795832 % 2397916 swap row_value
|
||||
23000000000000 2397916 2397915 . 4 * * 2 + - 4795832 % 2397916 swap row_value
|
||||
23000000000000 2397916 2397915 4 . * * 2 + - 4795832 % 2397916 swap row_value
|
||||
23000000000000 2397916 9591660 . * 2 + - 4795832 % 2397916 swap row_value
|
||||
23000000000000 22999994980560 . 2 + - 4795832 % 2397916 swap row_value
|
||||
23000000000000 22999994980560 2 . + - 4795832 % 2397916 swap row_value
|
||||
23000000000000 22999994980562 . - 4795832 % 2397916 swap row_value
|
||||
5019438 . 4795832 % 2397916 swap row_value
|
||||
5019438 4795832 . % 2397916 swap row_value
|
||||
223606 . 2397916 swap row_value
|
||||
223606 2397916 . swap row_value
|
||||
2397916 223606 . row_value
|
||||
2397916 223606 . over -- - abs +
|
||||
2397916 223606 2397916 . -- - abs +
|
||||
2397916 223606 2397915 . - abs +
|
||||
2397916 -2174309 . abs +
|
||||
2397916 2174309 . +
|
||||
4572225 .
|
||||
|
||||
|
||||
::
|
||||
|
||||
rank_of == -- sqrt 2 / 0.5 - floor ++
|
||||
offset_of == dup 2 * [dup -- 4 * * 2 + -] dip %
|
||||
row_value == over -- - abs +
|
||||
|
||||
aoc2017.3 == dup rank_of [offset_of] dupdip swap row_value
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
|
||||
Advent of Code 2017
|
||||
===================
|
||||
|
||||
December 4th
|
||||
------------
|
||||
|
||||
To ensure security, a valid passphrase must contain no duplicate words.
|
||||
|
||||
For example:
|
||||
|
||||
- aa bb cc dd ee is valid.
|
||||
- aa bb cc dd aa is not valid - the word aa appears more than once.
|
||||
- aa bb cc dd aaa is valid - aa and aaa count as different words.
|
||||
|
||||
The system's full passphrase list is available as your puzzle input. How
|
||||
many passphrases are valid?
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from notebook_preamble import J, V, define
|
||||
|
||||
I'll assume the input is a Joy sequence of sequences of integers.
|
||||
|
||||
::
|
||||
|
||||
[[5 1 9 5]
|
||||
[7 5 4 3]
|
||||
[2 4 6 8]]
|
||||
|
||||
So, obviously, the initial form will be a ``step`` function:
|
||||
|
||||
::
|
||||
|
||||
AoC2017.4 == 0 swap [F +] step
|
||||
|
||||
::
|
||||
|
||||
F == [size] [unique size] cleave =
|
||||
|
||||
The ``step_zero`` combinator includes the ``0 swap`` that would normally
|
||||
open one of these definitions:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[step_zero] help')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
0 roll> step
|
||||
|
||||
|
||||
|
||||
::
|
||||
|
||||
AoC2017.4 == [F +] step_zero
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('AoC2017.4 == [[size] [unique size] cleave = +] step_zero')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('''
|
||||
|
||||
[[5 1 9 5]
|
||||
[7 5 4 3]
|
||||
[2 4 6 8]] AoC2017.4
|
||||
|
||||
''')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
2
|
||||
|
||||
|
|
@ -1,324 +0,0 @@
|
|||
|
||||
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.
|
||||
|
|
@ -1,305 +0,0 @@
|
|||
|
||||
Advent of Code 2017
|
||||
===================
|
||||
|
||||
December 6th
|
||||
------------
|
||||
|
||||
::
|
||||
|
||||
[0 2 7 0] dup max
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from notebook_preamble import D, J, V, define
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[0 2 7 0] dup max')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[0 2 7 0] 7
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from joy.library import SimpleFunctionWrapper
|
||||
from joy.utils.stack import list_to_stack
|
||||
|
||||
|
||||
@SimpleFunctionWrapper
|
||||
def index_of(stack):
|
||||
'''Given a sequence and a item, return the index of the item, or -1 if not found.
|
||||
|
||||
E.g.:
|
||||
|
||||
[a b c] a index_of
|
||||
------------------------
|
||||
0
|
||||
|
||||
[a b c] d index_of
|
||||
------------------------
|
||||
-1
|
||||
|
||||
'''
|
||||
item, (sequence, stack) = stack
|
||||
i = 0
|
||||
while sequence:
|
||||
term, sequence = sequence
|
||||
if term == item:
|
||||
break
|
||||
i += 1
|
||||
else:
|
||||
i = -1
|
||||
return i, stack
|
||||
|
||||
|
||||
D['index_of'] = index_of
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[0 2 7 0] 7 index_of')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
2
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[0 2 7 0] 23 index_of')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
-1
|
||||
|
||||
|
||||
Starting at ``index`` distribute ``count`` "blocks" to the "banks" in
|
||||
the sequence.
|
||||
|
||||
::
|
||||
|
||||
[...] count index distribute
|
||||
----------------------------
|
||||
[...]
|
||||
|
||||
This seems like it would be a PITA to implement in Joypy...
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
from joy.utils.stack import iter_stack, list_to_stack
|
||||
|
||||
|
||||
@SimpleFunctionWrapper
|
||||
def distribute(stack):
|
||||
'''Starting at index+1 distribute count "blocks" to the "banks" in the sequence.
|
||||
|
||||
[...] count index distribute
|
||||
----------------------------
|
||||
[...]
|
||||
|
||||
'''
|
||||
index, (count, (sequence, stack)) = stack
|
||||
assert count >= 0
|
||||
cheat = list(iter_stack(sequence))
|
||||
n = len(cheat)
|
||||
assert index < n
|
||||
cheat[index] = 0
|
||||
while count:
|
||||
index += 1
|
||||
index %= n
|
||||
cheat[index] += 1
|
||||
count -= 1
|
||||
return list_to_stack(cheat), stack
|
||||
|
||||
|
||||
D['distribute'] = distribute
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[0 2 7 0] dup max [index_of] nullary distribute')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[2 4 1 2]
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[2 4 1 2] dup max [index_of] nullary distribute')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[3 1 2 3]
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[3 1 2 3] dup max [index_of] nullary distribute')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[0 2 3 4]
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[0 2 3 4] dup max [index_of] nullary distribute')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[1 3 4 1]
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 3 4 1] dup max [index_of] nullary distribute')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[2 4 1 2]
|
||||
|
||||
|
||||
Recalling "Generator Programs"
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
[a F] x
|
||||
[a F] a F
|
||||
|
||||
[a F] a swap [C] dip rest cons
|
||||
a [a F] [C] dip rest cons
|
||||
a C [a F] rest cons
|
||||
a C [F] cons
|
||||
|
||||
w/ C == dup G
|
||||
|
||||
a dup G [F] cons
|
||||
a a G [F] cons
|
||||
|
||||
w/ G == dup max [index_of] nullary distribute
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('direco == dip rest cons')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('G == [direco] cons [swap] swoncat cons')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('make_distributor == [dup dup max [index_of] nullary distribute] G')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[0 2 7 0] make_distributor 6 [x] times pop')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
[0 2 7 0] [2 4 1 2] [3 1 2 3] [0 2 3 4] [1 3 4 1] [2 4 1 2]
|
||||
|
||||
|
||||
A function to drive a generator and count how many states before a repeat.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
First draft:
|
||||
|
||||
::
|
||||
|
||||
[] [GEN] x [pop index_of 0 >=] [pop size --] [[swons] dip x] primrec
|
||||
|
||||
(?)
|
||||
|
||||
::
|
||||
|
||||
[] [GEN] x [pop index_of 0 >=] [pop size --] [[swons] dip x] primrec
|
||||
[] [...] [GEN] [pop index_of 0 >=] [pop size --] [[swons] dip x] primrec
|
||||
[] [...] [GEN] pop index_of 0 >=
|
||||
[] [...] index_of 0 >=
|
||||
-1 0 >=
|
||||
False
|
||||
|
||||
Base case
|
||||
|
||||
::
|
||||
|
||||
[] [...] [GEN] [pop index_of 0 >=] [pop size --] [[swons] dip x] primrec
|
||||
[] [...] [GEN] pop size --
|
||||
[] [...] size --
|
||||
[] [...] size --
|
||||
|
||||
A mistake, ``popop`` and no need for ``--``
|
||||
|
||||
::
|
||||
|
||||
[] [...] [GEN] popop size
|
||||
[] size
|
||||
n
|
||||
|
||||
Recursive case
|
||||
|
||||
::
|
||||
|
||||
[] [...] [GEN] [pop index_of 0 >=] [popop size] [[swons] dip x] primrec
|
||||
[] [...] [GEN] [swons] dip x F
|
||||
[] [...] swons [GEN] x F
|
||||
[[...]] [GEN] x F
|
||||
[[...]] [...] [GEN] F
|
||||
|
||||
[[...]] [...] [GEN] F
|
||||
|
||||
What have we learned?
|
||||
|
||||
::
|
||||
|
||||
F == [pop index_of 0 >=] [popop size] [[swons] dip x] primrec
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('count_states == [] swap x [pop index_of 0 >=] [popop size] [[swons] dip x] primrec')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('AoC2017.6 == make_distributor count_states')
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[0 2 7 0] AoC2017.6')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
5
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[1 1 1] AoC2017.6')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
4
|
||||
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
J('[8 0 0 0 0 0] AoC2017.6')
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
15
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue