delete all the extra notebook conversions
This commit is contained in:
parent
aad6777bb9
commit
68838155dc
File diff suppressed because it is too large
Load Diff
|
|
@ -1,443 +0,0 @@
|
||||||
# Joypy
|
|
||||||
|
|
||||||
## Joy in Python
|
|
||||||
|
|
||||||
This implementation is meant as a tool for exploring the programming model and method of Joy. Python seems like a great implementation language for Joy for several reasons.
|
|
||||||
|
|
||||||
We can lean on the Python immutable types for our basic semantics and types: ints, floats, strings, and tuples, which enforces functional purity. We get garbage collection for free. Compilation via Cython. Glue language with loads of libraries.
|
|
||||||
|
|
||||||
### [Read-Eval-Print Loop (REPL)](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)
|
|
||||||
The main way to interact with the Joy interpreter is through a simple [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) that you start by running the package:
|
|
||||||
|
|
||||||
$ python -m joy
|
|
||||||
Joypy - Copyright © 2017 Simon Forman
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type "warranty".
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type "sharing" for details.
|
|
||||||
Type "words" to see a list of all words, and "[<name>] help" to print the
|
|
||||||
docs for a word.
|
|
||||||
|
|
||||||
|
|
||||||
<-top
|
|
||||||
|
|
||||||
joy? _
|
|
||||||
|
|
||||||
The `<-top` marker points to the top of the (initially empty) stack. You can enter Joy notation at the prompt and a [trace of evaluation](#The-TracePrinter.) will be printed followed by the stack and prompt again:
|
|
||||||
|
|
||||||
joy? 23 sqr 18 +
|
|
||||||
. 23 sqr 18 +
|
|
||||||
23 . sqr 18 +
|
|
||||||
23 . dup mul 18 +
|
|
||||||
23 23 . mul 18 +
|
|
||||||
529 . 18 +
|
|
||||||
529 18 . +
|
|
||||||
547 .
|
|
||||||
|
|
||||||
547 <-top
|
|
||||||
|
|
||||||
joy?
|
|
||||||
|
|
||||||
|
|
||||||
# Stacks (aka list, quote, sequence, etc.)
|
|
||||||
|
|
||||||
In Joy, in addition to the types Boolean, integer, float, and string, there is a single sequence type represented by enclosing a sequence of terms in brackets `[...]`. This sequence type is used to represent both the stack and the expression. It is a [cons list](https://en.wikipedia.org/wiki/Cons#Lists) made from Python tuples.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
import inspect
|
|
||||||
import joy.utils.stack
|
|
||||||
|
|
||||||
|
|
||||||
print(inspect.getdoc(joy.utils.stack))
|
|
||||||
```
|
|
||||||
|
|
||||||
When talking about Joy we use the terms "stack", "quote", "sequence",
|
|
||||||
"list", and others to mean the same thing: a simple linear datatype that
|
|
||||||
permits certain operations such as iterating and pushing and popping
|
|
||||||
values from (at least) one end.
|
|
||||||
|
|
||||||
There is no "Stack" Python class, instead we use the `cons list`_, a
|
|
||||||
venerable two-tuple recursive sequence datastructure, where the
|
|
||||||
empty tuple ``()`` is the empty stack and ``(head, rest)`` gives the
|
|
||||||
recursive form of a stack with one or more items on it::
|
|
||||||
|
|
||||||
stack := () | (item, stack)
|
|
||||||
|
|
||||||
Putting some numbers onto a stack::
|
|
||||||
|
|
||||||
()
|
|
||||||
(1, ())
|
|
||||||
(2, (1, ()))
|
|
||||||
(3, (2, (1, ())))
|
|
||||||
...
|
|
||||||
|
|
||||||
Python has very nice "tuple packing and unpacking" in its syntax which
|
|
||||||
means we can directly "unpack" the expected arguments to a Joy function.
|
|
||||||
|
|
||||||
For example::
|
|
||||||
|
|
||||||
def dup((head, tail)):
|
|
||||||
return head, (head, tail)
|
|
||||||
|
|
||||||
We replace the argument "stack" by the expected structure of the stack,
|
|
||||||
in this case "(head, tail)", and Python takes care of unpacking the
|
|
||||||
incoming tuple and assigning values to the names. (Note that Python
|
|
||||||
syntax doesn't require parentheses around tuples used in expressions
|
|
||||||
where they would be redundant.)
|
|
||||||
|
|
||||||
Unfortunately, the Sphinx documentation generator, which is used to generate this
|
|
||||||
web page, doesn't handle tuples in the function parameters. And in Python 3, this
|
|
||||||
syntax was removed entirely. Instead you would have to write::
|
|
||||||
|
|
||||||
def dup(stack):
|
|
||||||
head, tail = stack
|
|
||||||
return head, (head, tail)
|
|
||||||
|
|
||||||
|
|
||||||
We have two very simple functions, one to build up a stack from a Python
|
|
||||||
iterable and another to iterate through a stack and yield its items
|
|
||||||
one-by-one in order. There are also two functions to generate string representations
|
|
||||||
of stacks. They only differ in that one prints the terms in stack from left-to-right while the other prints from right-to-left. In both functions *internal stacks* are
|
|
||||||
printed left-to-right. These functions are written to support :doc:`../pretty`.
|
|
||||||
|
|
||||||
.. _cons list: https://en.wikipedia.org/wiki/Cons#Lists
|
|
||||||
|
|
||||||
|
|
||||||
### The utility functions maintain order.
|
|
||||||
The 0th item in the list will be on the top of the stack and *vise versa*.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
joy.utils.stack.list_to_stack([1, 2, 3])
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(1, (2, (3, ())))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
list(joy.utils.stack.iter_stack((1, (2, (3, ())))))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[1, 2, 3]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
This requires reversing the sequence (or iterating backwards) otherwise:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack = ()
|
|
||||||
|
|
||||||
for n in [1, 2, 3]:
|
|
||||||
stack = n, stack
|
|
||||||
|
|
||||||
print(stack)
|
|
||||||
print(list(joy.utils.stack.iter_stack(stack)))
|
|
||||||
```
|
|
||||||
|
|
||||||
(3, (2, (1, ())))
|
|
||||||
[3, 2, 1]
|
|
||||||
|
|
||||||
|
|
||||||
### Purely Functional Datastructures.
|
|
||||||
Because Joy lists are made out of Python tuples they are immutable, so all Joy datastructures are *[purely functional](https://en.wikipedia.org/wiki/Purely_functional_data_structure)*.
|
|
||||||
|
|
||||||
# The `joy()` function.
|
|
||||||
## An Interpreter
|
|
||||||
The `joy()` function is extrememly simple. It accepts a stack, an expression, and a dictionary, and it iterates through the expression putting values onto the stack and delegating execution to functions it looks up in the dictionary.
|
|
||||||
|
|
||||||
Each function is passed the stack, expression, and dictionary and returns them. Whatever the function returns becomes the new stack, expression, and dictionary. (The dictionary is passed to enable e.g. writing words that let you enter new words into the dictionary at runtime, which nothing does yet and may be a bad idea, and the `help` command.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
import joy.joy
|
|
||||||
|
|
||||||
print(inspect.getsource(joy.joy.joy))
|
|
||||||
```
|
|
||||||
|
|
||||||
def joy(stack, expression, dictionary, viewer=None):
|
|
||||||
'''Evaluate a Joy expression on a stack.
|
|
||||||
|
|
||||||
This function iterates through a sequence of terms which are either
|
|
||||||
literals (strings, numbers, sequences of terms) or function symbols.
|
|
||||||
Literals are put onto the stack and functions are looked up in the
|
|
||||||
disctionary and executed.
|
|
||||||
|
|
||||||
The viewer is a function that is called with the stack and expression
|
|
||||||
on every iteration, its return value is ignored.
|
|
||||||
|
|
||||||
:param stack stack: The stack.
|
|
||||||
:param stack expression: The expression to evaluate.
|
|
||||||
:param dict dictionary: A ``dict`` mapping names to Joy functions.
|
|
||||||
:param function viewer: Optional viewer function.
|
|
||||||
:rtype: (stack, (), dictionary)
|
|
||||||
|
|
||||||
'''
|
|
||||||
while expression:
|
|
||||||
|
|
||||||
if viewer: viewer(stack, expression)
|
|
||||||
|
|
||||||
term, expression = expression
|
|
||||||
if isinstance(term, Symbol):
|
|
||||||
term = dictionary[term]
|
|
||||||
stack, expression, dictionary = term(stack, expression, dictionary)
|
|
||||||
else:
|
|
||||||
stack = term, stack
|
|
||||||
|
|
||||||
if viewer: viewer(stack, expression)
|
|
||||||
return stack, expression, dictionary
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### View function
|
|
||||||
The `joy()` function accepts a "viewer" function which it calls on each iteration passing the current stack and expression just before evaluation. This can be used for tracing, breakpoints, retrying after exceptions, or interrupting an evaluation and saving to disk or sending over the network to resume later. The stack and expression together contain all the state of the computation at each step.
|
|
||||||
|
|
||||||
### The `TracePrinter`.
|
|
||||||
|
|
||||||
A `viewer` records each step of the evaluation of a Joy program. The `TracePrinter` has a facility for printing out a trace of the evaluation, one line per step. Each step is aligned to the current interpreter position, signified by a period separating the stack on the left from the pending expression ("continuation") on the right.
|
|
||||||
|
|
||||||
### [Continuation-Passing Style](https://en.wikipedia.org/wiki/Continuation-passing_style)
|
|
||||||
One day I thought, What happens if you rewrite Joy to use [CSP](https://en.wikipedia.org/wiki/Continuation-passing_style)? I made all the functions accept and return the expression as well as the stack and found that all the combinators could be rewritten to work by modifying the expression rather than making recursive calls to the `joy()` function.
|
|
||||||
|
|
||||||
# Parser
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
import joy.parser
|
|
||||||
|
|
||||||
print(inspect.getdoc(joy.parser))
|
|
||||||
```
|
|
||||||
|
|
||||||
This module exports a single function for converting text to a joy
|
|
||||||
expression as well as a single Symbol class and a single Exception type.
|
|
||||||
|
|
||||||
The Symbol string class is used by the interpreter to recognize literals
|
|
||||||
by the fact that they are not Symbol objects.
|
|
||||||
|
|
||||||
A crude grammar::
|
|
||||||
|
|
||||||
joy = term*
|
|
||||||
term = int | float | string | '[' joy ']' | symbol
|
|
||||||
|
|
||||||
A Joy expression is a sequence of zero or more terms. A term is a
|
|
||||||
literal value (integer, float, string, or Joy expression) or a function
|
|
||||||
symbol. Function symbols are unquoted strings and cannot contain square
|
|
||||||
brackets. Terms must be separated by blanks, which can be omitted
|
|
||||||
around square brackets.
|
|
||||||
|
|
||||||
|
|
||||||
The parser is extremely simple, the undocumented `re.Scanner` class does most of the tokenizing work and then you just build the tuple structure out of the tokens. There's no Abstract Syntax Tree or anything like that.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print(inspect.getsource(joy.parser._parse))
|
|
||||||
```
|
|
||||||
|
|
||||||
def _parse(tokens):
|
|
||||||
'''
|
|
||||||
Return a stack/list expression of the tokens.
|
|
||||||
'''
|
|
||||||
frame = []
|
|
||||||
stack = []
|
|
||||||
for tok in tokens:
|
|
||||||
if tok == '[':
|
|
||||||
stack.append(frame)
|
|
||||||
frame = []
|
|
||||||
stack[-1].append(frame)
|
|
||||||
elif tok == ']':
|
|
||||||
try:
|
|
||||||
frame = stack.pop()
|
|
||||||
except IndexError:
|
|
||||||
raise ParseError('Extra closing bracket.')
|
|
||||||
frame[-1] = list_to_stack(frame[-1])
|
|
||||||
else:
|
|
||||||
frame.append(tok)
|
|
||||||
if stack:
|
|
||||||
raise ParseError('Unclosed bracket.')
|
|
||||||
return list_to_stack(frame)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
That's pretty much all there is to it.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
joy.parser.text_to_expression('1 2 3 4 5') # A simple sequence.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(1, (2, (3, (4, (5, ())))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
joy.parser.text_to_expression('[1 2 3] 4 5') # Three items, the first is a list with three items
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
((1, (2, (3, ()))), (4, (5, ())))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
joy.parser.text_to_expression('1 23 ["four" [-5.0] cons] 8888') # A mixed bag. cons is
|
|
||||||
# a Symbol, no lookup at
|
|
||||||
# parse-time. Haiku docs.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(1, (23, (('four', ((-5.0, ()), (cons, ()))), (8888, ()))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
joy.parser.text_to_expression('[][][][][]') # Five empty lists.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
((), ((), ((), ((), ((), ())))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
joy.parser.text_to_expression('[[[[[]]]]]') # Five nested lists.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
((((((), ()), ()), ()), ()), ())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Library
|
|
||||||
The Joy library of functions (aka commands, or "words" after Forth usage) encapsulates all the actual functionality (no pun intended) of the Joy system. There are simple functions such as addition `add` (or `+`, the library module supports aliases), and combinators which provide control-flow and higher-order operations.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
import joy.library
|
|
||||||
|
|
||||||
print(' '.join(sorted(joy.library.initialize())))
|
|
||||||
```
|
|
||||||
|
|
||||||
!= % & * *fraction *fraction0 + ++ - -- / // /floor < << <= <> = > >= >> ? ^ _Tree_add_Ee _Tree_delete_R0 _Tree_delete_clear_stuff _Tree_get_E abs add anamorphism and app1 app2 app3 at average b binary bool branch ccons choice clear cleave cmp codireco concat cond cons dinfrirst dip dipd dipdd disenstacken div divmod down_to_zero drop dup dupd dupdd dupdip dupdipd enstacken eq first first_two flatten floor floordiv fork fourth gcd ge genrec getitem gt help i id ifte ii infra inscribe le least_fraction loop lshift lt make_generator map max min mod modulus mul ne neg not nullary of or over pam parse pick pm pop popd popdd popop popopd popopdd pow pred primrec product quoted range range_to_zero rem remainder remove rest reverse roll< roll> rolldown rollup round rrest rshift run second select sharing shunt size sort sqr sqrt stack step step_zero stuncons stununcons sub succ sum swaack swap swoncat swons tailrec take ternary third times truediv truthy tuck unary uncons unique unit unquoted unstack unswons void warranty while words x xor zip •
|
|
||||||
|
|
||||||
|
|
||||||
Many of the functions are defined in Python, like `dip`:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print(inspect.getsource(joy.library.dip))
|
|
||||||
```
|
|
||||||
|
|
||||||
@inscribe
|
|
||||||
@FunctionWrapper
|
|
||||||
def dip(stack, expression, dictionary):
|
|
||||||
'''
|
|
||||||
The dip combinator expects a quoted program on the stack and below it
|
|
||||||
some item, it hoists the item into the expression and runs the program
|
|
||||||
on the rest of the stack.
|
|
||||||
::
|
|
||||||
|
|
||||||
... x [Q] dip
|
|
||||||
-------------------
|
|
||||||
... Q x
|
|
||||||
|
|
||||||
'''
|
|
||||||
(quote, (x, stack)) = stack
|
|
||||||
expression = (x, expression)
|
|
||||||
return stack, concat(quote, expression), dictionary
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Some functions are defined in equations in terms of other functions. When the interpreter executes a definition function that function just pushes its body expression onto the pending expression (the continuation) and returns control to the interpreter.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print(joy.library.definitions)
|
|
||||||
```
|
|
||||||
|
|
||||||
? dup truthy
|
|
||||||
*fraction [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
|
|
||||||
*fraction0 concat [[swap] dip * [*] dip] infra
|
|
||||||
anamorphism [pop []] swap [dip swons] genrec
|
|
||||||
average [sum 1.0 *] [size] cleave /
|
|
||||||
binary nullary [popop] dip
|
|
||||||
cleave fork [popd] dip
|
|
||||||
codireco cons dip rest cons
|
|
||||||
dinfrirst dip infra first
|
|
||||||
unstack ? [uncons ?] loop pop
|
|
||||||
down_to_zero [0 >] [dup --] while
|
|
||||||
dupdipd dup dipd
|
|
||||||
enstacken stack [clear] dip
|
|
||||||
flatten [] swap [concat] step
|
|
||||||
fork [i] app2
|
|
||||||
gcd 1 [tuck modulus dup 0 >] loop pop
|
|
||||||
ifte [nullary not] dipd branch
|
|
||||||
ii [dip] dupdip i
|
|
||||||
least_fraction dup [gcd] infra [div] concat map
|
|
||||||
make_generator [codireco] ccons
|
|
||||||
nullary [stack] dinfrirst
|
|
||||||
of swap at
|
|
||||||
pam [i] map
|
|
||||||
tailrec [i] genrec
|
|
||||||
product 1 swap [*] step
|
|
||||||
quoted [unit] dip
|
|
||||||
range [0 <=] [1 - dup] anamorphism
|
|
||||||
range_to_zero unit [down_to_zero] infra
|
|
||||||
run [] swap infra
|
|
||||||
size 0 swap [pop ++] step
|
|
||||||
sqr dup mul
|
|
||||||
step_zero 0 roll> step
|
|
||||||
swoncat swap concat
|
|
||||||
tailrec [i] genrec
|
|
||||||
ternary unary [popop] dip
|
|
||||||
unary nullary popd
|
|
||||||
unquoted [i] dip
|
|
||||||
while swap [nullary] cons dup dipd concat loop
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Currently, there's no function to add new definitions to the dictionary from "within" Joy code itself. Adding new definitions remains a meta-interpreter action. You have to do it yourself, in Python, and wash your hands afterward.
|
|
||||||
|
|
||||||
It would be simple enough to define one, but it would open the door to *name binding* and break the idea that all state is captured in the stack and expression. There's an implicit *standard dictionary* that defines the actual semantics of the syntactic stack and expression datastructures (which only contain symbols, not the actual functions. Pickle some and see for yourself.)
|
|
||||||
|
|
||||||
#### "There should be only one."
|
|
||||||
|
|
||||||
Which brings me to talking about one of my hopes and dreams for this notation: "There should be only one." What I mean is that there should be one universal standard dictionary of commands, and all bespoke work done in a UI for purposes takes place by direct interaction and macros. There would be a *Grand Refactoring* biannually (two years, not six months, that's semi-annually) where any new definitions factored out of the usage and macros of the previous time, along with new algorithms and such, were entered into the dictionary and posted to e.g. IPFS.
|
|
||||||
|
|
||||||
Code should not burgeon wildly, as it does today. The variety of code should map more-or-less to the well-factored variety of human computably-solvable problems. There shouldn't be dozens of chat apps, JS frameworks, programming languages. It's a waste of time, a [fractal "thundering herd" attack](https://en.wikipedia.org/wiki/Thundering_herd_problem) on human mentality.
|
|
||||||
|
|
||||||
#### Literary Code Library
|
|
||||||
|
|
||||||
If you read over the other notebooks you'll see that developing code in Joy is a lot like doing simple mathematics, and the descriptions of the code resemble math papers. The code also works the first time, no bugs. If you have any experience programming at all, you are probably skeptical, as I was, but it seems to work: deriving code mathematically seems to lead to fewer errors.
|
|
||||||
|
|
||||||
But my point now is that this great ratio of textual explanation to wind up with code that consists of a few equations and could fit on an index card is highly desirable. Less code has fewer errors. The structure of Joy engenders a kind of thinking that seems to be very effective for developing structured processes.
|
|
||||||
|
|
||||||
There seems to be an elegance and power to the notation.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
@ -1,567 +0,0 @@
|
||||||
Joypy
|
|
||||||
=====
|
|
||||||
|
|
||||||
Joy in Python
|
|
||||||
-------------
|
|
||||||
|
|
||||||
This implementation is meant as a tool for exploring the programming
|
|
||||||
model and method of Joy. Python seems like a great implementation
|
|
||||||
language for Joy for several reasons.
|
|
||||||
|
|
||||||
We can lean on the Python immutable types for our basic semantics and
|
|
||||||
types: ints, floats, strings, and tuples, which enforces functional
|
|
||||||
purity. We get garbage collection for free. Compilation via Cython. Glue
|
|
||||||
language with loads of libraries.
|
|
||||||
|
|
||||||
`Read-Eval-Print Loop (REPL) <https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop>`__
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The main way to interact with the Joy interpreter is through a simple
|
|
||||||
`REPL <https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop>`__
|
|
||||||
that you start by running the package:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
$ python -m joy
|
|
||||||
Joypy - Copyright © 2017 Simon Forman
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type "warranty".
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type "sharing" for details.
|
|
||||||
Type "words" to see a list of all words, and "[<name>] help" to print the
|
|
||||||
docs for a word.
|
|
||||||
|
|
||||||
|
|
||||||
<-top
|
|
||||||
|
|
||||||
joy? _
|
|
||||||
|
|
||||||
The ``<-top`` marker points to the top of the (initially empty) stack.
|
|
||||||
You can enter Joy notation at the prompt and a `trace of
|
|
||||||
evaluation <#The-TracePrinter.>`__ will be printed followed by the stack
|
|
||||||
and prompt again:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
joy? 23 sqr 18 +
|
|
||||||
. 23 sqr 18 +
|
|
||||||
23 . sqr 18 +
|
|
||||||
23 . dup mul 18 +
|
|
||||||
23 23 . mul 18 +
|
|
||||||
529 . 18 +
|
|
||||||
529 18 . +
|
|
||||||
547 .
|
|
||||||
|
|
||||||
547 <-top
|
|
||||||
|
|
||||||
joy?
|
|
||||||
|
|
||||||
Stacks (aka list, quote, sequence, etc.)
|
|
||||||
========================================
|
|
||||||
|
|
||||||
In Joy, in addition to the types Boolean, integer, float, and string,
|
|
||||||
there is a single sequence type represented by enclosing a sequence of
|
|
||||||
terms in brackets ``[...]``. This sequence type is used to represent
|
|
||||||
both the stack and the expression. It is a `cons
|
|
||||||
list <https://en.wikipedia.org/wiki/Cons#Lists>`__ made from Python
|
|
||||||
tuples.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import joy.utils.stack
|
|
||||||
|
|
||||||
|
|
||||||
print(inspect.getdoc(joy.utils.stack))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
When talking about Joy we use the terms "stack", "quote", "sequence",
|
|
||||||
"list", and others to mean the same thing: a simple linear datatype that
|
|
||||||
permits certain operations such as iterating and pushing and popping
|
|
||||||
values from (at least) one end.
|
|
||||||
|
|
||||||
There is no "Stack" Python class, instead we use the `cons list`_, a
|
|
||||||
venerable two-tuple recursive sequence datastructure, where the
|
|
||||||
empty tuple ``()`` is the empty stack and ``(head, rest)`` gives the
|
|
||||||
recursive form of a stack with one or more items on it::
|
|
||||||
|
|
||||||
stack := () | (item, stack)
|
|
||||||
|
|
||||||
Putting some numbers onto a stack::
|
|
||||||
|
|
||||||
()
|
|
||||||
(1, ())
|
|
||||||
(2, (1, ()))
|
|
||||||
(3, (2, (1, ())))
|
|
||||||
...
|
|
||||||
|
|
||||||
Python has very nice "tuple packing and unpacking" in its syntax which
|
|
||||||
means we can directly "unpack" the expected arguments to a Joy function.
|
|
||||||
|
|
||||||
For example::
|
|
||||||
|
|
||||||
def dup((head, tail)):
|
|
||||||
return head, (head, tail)
|
|
||||||
|
|
||||||
We replace the argument "stack" by the expected structure of the stack,
|
|
||||||
in this case "(head, tail)", and Python takes care of unpacking the
|
|
||||||
incoming tuple and assigning values to the names. (Note that Python
|
|
||||||
syntax doesn't require parentheses around tuples used in expressions
|
|
||||||
where they would be redundant.)
|
|
||||||
|
|
||||||
Unfortunately, the Sphinx documentation generator, which is used to generate this
|
|
||||||
web page, doesn't handle tuples in the function parameters. And in Python 3, this
|
|
||||||
syntax was removed entirely. Instead you would have to write::
|
|
||||||
|
|
||||||
def dup(stack):
|
|
||||||
head, tail = stack
|
|
||||||
return head, (head, tail)
|
|
||||||
|
|
||||||
|
|
||||||
We have two very simple functions, one to build up a stack from a Python
|
|
||||||
iterable and another to iterate through a stack and yield its items
|
|
||||||
one-by-one in order. There are also two functions to generate string representations
|
|
||||||
of stacks. They only differ in that one prints the terms in stack from left-to-right while the other prints from right-to-left. In both functions *internal stacks* are
|
|
||||||
printed left-to-right. These functions are written to support :doc:`../pretty`.
|
|
||||||
|
|
||||||
.. _cons list: https://en.wikipedia.org/wiki/Cons#Lists
|
|
||||||
|
|
||||||
|
|
||||||
The utility functions maintain order.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The 0th item in the list will be on the top of the stack and *vise
|
|
||||||
versa*.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
joy.utils.stack.list_to_stack([1, 2, 3])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
(1, (2, (3, ())))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
list(joy.utils.stack.iter_stack((1, (2, (3, ())))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1, 2, 3]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
This requires reversing the sequence (or iterating backwards) otherwise:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack = ()
|
|
||||||
|
|
||||||
for n in [1, 2, 3]:
|
|
||||||
stack = n, stack
|
|
||||||
|
|
||||||
print(stack)
|
|
||||||
print(list(joy.utils.stack.iter_stack(stack)))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
(3, (2, (1, ())))
|
|
||||||
[3, 2, 1]
|
|
||||||
|
|
||||||
|
|
||||||
Purely Functional Datastructures.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Because Joy lists are made out of Python tuples they are immutable, so
|
|
||||||
all Joy datastructures are *`purely
|
|
||||||
functional <https://en.wikipedia.org/wiki/Purely_functional_data_structure>`__*.
|
|
||||||
|
|
||||||
The ``joy()`` function.
|
|
||||||
=======================
|
|
||||||
|
|
||||||
An Interpreter
|
|
||||||
--------------
|
|
||||||
|
|
||||||
The ``joy()`` function is extrememly simple. It accepts a stack, an
|
|
||||||
expression, and a dictionary, and it iterates through the expression
|
|
||||||
putting values onto the stack and delegating execution to functions it
|
|
||||||
looks up in the dictionary.
|
|
||||||
|
|
||||||
Each function is passed the stack, expression, and dictionary and
|
|
||||||
returns them. Whatever the function returns becomes the new stack,
|
|
||||||
expression, and dictionary. (The dictionary is passed to enable e.g.
|
|
||||||
writing words that let you enter new words into the dictionary at
|
|
||||||
runtime, which nothing does yet and may be a bad idea, and the ``help``
|
|
||||||
command.)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
import joy.joy
|
|
||||||
|
|
||||||
print(inspect.getsource(joy.joy.joy))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
def joy(stack, expression, dictionary, viewer=None):
|
|
||||||
'''Evaluate a Joy expression on a stack.
|
|
||||||
|
|
||||||
This function iterates through a sequence of terms which are either
|
|
||||||
literals (strings, numbers, sequences of terms) or function symbols.
|
|
||||||
Literals are put onto the stack and functions are looked up in the
|
|
||||||
disctionary and executed.
|
|
||||||
|
|
||||||
The viewer is a function that is called with the stack and expression
|
|
||||||
on every iteration, its return value is ignored.
|
|
||||||
|
|
||||||
:param stack stack: The stack.
|
|
||||||
:param stack expression: The expression to evaluate.
|
|
||||||
:param dict dictionary: A ``dict`` mapping names to Joy functions.
|
|
||||||
:param function viewer: Optional viewer function.
|
|
||||||
:rtype: (stack, (), dictionary)
|
|
||||||
|
|
||||||
'''
|
|
||||||
while expression:
|
|
||||||
|
|
||||||
if viewer: viewer(stack, expression)
|
|
||||||
|
|
||||||
term, expression = expression
|
|
||||||
if isinstance(term, Symbol):
|
|
||||||
term = dictionary[term]
|
|
||||||
stack, expression, dictionary = term(stack, expression, dictionary)
|
|
||||||
else:
|
|
||||||
stack = term, stack
|
|
||||||
|
|
||||||
if viewer: viewer(stack, expression)
|
|
||||||
return stack, expression, dictionary
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
View function
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``joy()`` function accepts a "viewer" function which it calls on
|
|
||||||
each iteration passing the current stack and expression just before
|
|
||||||
evaluation. This can be used for tracing, breakpoints, retrying after
|
|
||||||
exceptions, or interrupting an evaluation and saving to disk or sending
|
|
||||||
over the network to resume later. The stack and expression together
|
|
||||||
contain all the state of the computation at each step.
|
|
||||||
|
|
||||||
The ``TracePrinter``.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
A ``viewer`` records each step of the evaluation of a Joy program. The
|
|
||||||
``TracePrinter`` has a facility for printing out a trace of the
|
|
||||||
evaluation, one line per step. Each step is aligned to the current
|
|
||||||
interpreter position, signified by a period separating the stack on the
|
|
||||||
left from the pending expression ("continuation") on the right.
|
|
||||||
|
|
||||||
`Continuation-Passing Style <https://en.wikipedia.org/wiki/Continuation-passing_style>`__
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
One day I thought, What happens if you rewrite Joy to use
|
|
||||||
`CSP <https://en.wikipedia.org/wiki/Continuation-passing_style>`__? I
|
|
||||||
made all the functions accept and return the expression as well as the
|
|
||||||
stack and found that all the combinators could be rewritten to work by
|
|
||||||
modifying the expression rather than making recursive calls to the
|
|
||||||
``joy()`` function.
|
|
||||||
|
|
||||||
Parser
|
|
||||||
======
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
import joy.parser
|
|
||||||
|
|
||||||
print(inspect.getdoc(joy.parser))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
This module exports a single function for converting text to a joy
|
|
||||||
expression as well as a single Symbol class and a single Exception type.
|
|
||||||
|
|
||||||
The Symbol string class is used by the interpreter to recognize literals
|
|
||||||
by the fact that they are not Symbol objects.
|
|
||||||
|
|
||||||
A crude grammar::
|
|
||||||
|
|
||||||
joy = term*
|
|
||||||
term = int | float | string | '[' joy ']' | symbol
|
|
||||||
|
|
||||||
A Joy expression is a sequence of zero or more terms. A term is a
|
|
||||||
literal value (integer, float, string, or Joy expression) or a function
|
|
||||||
symbol. Function symbols are unquoted strings and cannot contain square
|
|
||||||
brackets. Terms must be separated by blanks, which can be omitted
|
|
||||||
around square brackets.
|
|
||||||
|
|
||||||
|
|
||||||
The parser is extremely simple, the undocumented ``re.Scanner`` class
|
|
||||||
does most of the tokenizing work and then you just build the tuple
|
|
||||||
structure out of the tokens. There's no Abstract Syntax Tree or anything
|
|
||||||
like that.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print(inspect.getsource(joy.parser._parse))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
def _parse(tokens):
|
|
||||||
'''
|
|
||||||
Return a stack/list expression of the tokens.
|
|
||||||
'''
|
|
||||||
frame = []
|
|
||||||
stack = []
|
|
||||||
for tok in tokens:
|
|
||||||
if tok == '[':
|
|
||||||
stack.append(frame)
|
|
||||||
frame = []
|
|
||||||
stack[-1].append(frame)
|
|
||||||
elif tok == ']':
|
|
||||||
try:
|
|
||||||
frame = stack.pop()
|
|
||||||
except IndexError:
|
|
||||||
raise ParseError('Extra closing bracket.')
|
|
||||||
frame[-1] = list_to_stack(frame[-1])
|
|
||||||
else:
|
|
||||||
frame.append(tok)
|
|
||||||
if stack:
|
|
||||||
raise ParseError('Unclosed bracket.')
|
|
||||||
return list_to_stack(frame)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
That's pretty much all there is to it.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
joy.parser.text_to_expression('1 2 3 4 5') # A simple sequence.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
(1, (2, (3, (4, (5, ())))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
joy.parser.text_to_expression('[1 2 3] 4 5') # Three items, the first is a list with three items
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
((1, (2, (3, ()))), (4, (5, ())))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
joy.parser.text_to_expression('1 23 ["four" [-5.0] cons] 8888') # A mixed bag. cons is
|
|
||||||
# a Symbol, no lookup at
|
|
||||||
# parse-time. Haiku docs.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
(1, (23, (('four', ((-5.0, ()), (cons, ()))), (8888, ()))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
joy.parser.text_to_expression('[][][][][]') # Five empty lists.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
((), ((), ((), ((), ((), ())))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
joy.parser.text_to_expression('[[[[[]]]]]') # Five nested lists.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
((((((), ()), ()), ()), ()), ())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Library
|
|
||||||
=======
|
|
||||||
|
|
||||||
The Joy library of functions (aka commands, or "words" after Forth
|
|
||||||
usage) encapsulates all the actual functionality (no pun intended) of
|
|
||||||
the Joy system. There are simple functions such as addition ``add`` (or
|
|
||||||
``+``, the library module supports aliases), and combinators which
|
|
||||||
provide control-flow and higher-order operations.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
import joy.library
|
|
||||||
|
|
||||||
print(' '.join(sorted(joy.library.initialize())))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
!= % & * *fraction *fraction0 + ++ - -- / // /floor < << <= <> = > >= >> ? ^ _Tree_add_Ee _Tree_delete_R0 _Tree_delete_clear_stuff _Tree_get_E abs add anamorphism and app1 app2 app3 at average b binary bool branch ccons choice clear cleave cmp codireco concat cond cons dinfrirst dip dipd dipdd disenstacken div divmod down_to_zero drop dup dupd dupdd dupdip dupdipd enstacken eq first first_two flatten floor floordiv fork fourth gcd ge genrec getitem gt help i id ifte ii infra inscribe le least_fraction loop lshift lt make_generator map max min mod modulus mul ne neg not nullary of or over pam parse pick pm pop popd popdd popop popopd popopdd pow pred primrec product quoted range range_to_zero rem remainder remove rest reverse roll< roll> rolldown rollup round rrest rshift run second select sharing shunt size sort sqr sqrt stack step step_zero stuncons stununcons sub succ sum swaack swap swoncat swons tailrec take ternary third times truediv truthy tuck unary uncons unique unit unquoted unstack unswons void warranty while words x xor zip •
|
|
||||||
|
|
||||||
|
|
||||||
Many of the functions are defined in Python, like ``dip``:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print(inspect.getsource(joy.library.dip))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
@inscribe
|
|
||||||
@FunctionWrapper
|
|
||||||
def dip(stack, expression, dictionary):
|
|
||||||
'''
|
|
||||||
The dip combinator expects a quoted program on the stack and below it
|
|
||||||
some item, it hoists the item into the expression and runs the program
|
|
||||||
on the rest of the stack.
|
|
||||||
::
|
|
||||||
|
|
||||||
... x [Q] dip
|
|
||||||
-------------------
|
|
||||||
... Q x
|
|
||||||
|
|
||||||
'''
|
|
||||||
(quote, (x, stack)) = stack
|
|
||||||
expression = (x, expression)
|
|
||||||
return stack, concat(quote, expression), dictionary
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Some functions are defined in equations in terms of other functions.
|
|
||||||
When the interpreter executes a definition function that function just
|
|
||||||
pushes its body expression onto the pending expression (the
|
|
||||||
continuation) and returns control to the interpreter.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print(joy.library.definitions)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
? dup truthy
|
|
||||||
*fraction [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
|
|
||||||
*fraction0 concat [[swap] dip * [*] dip] infra
|
|
||||||
anamorphism [pop []] swap [dip swons] genrec
|
|
||||||
average [sum 1.0 *] [size] cleave /
|
|
||||||
binary nullary [popop] dip
|
|
||||||
cleave fork [popd] dip
|
|
||||||
codireco cons dip rest cons
|
|
||||||
dinfrirst dip infra first
|
|
||||||
unstack ? [uncons ?] loop pop
|
|
||||||
down_to_zero [0 >] [dup --] while
|
|
||||||
dupdipd dup dipd
|
|
||||||
enstacken stack [clear] dip
|
|
||||||
flatten [] swap [concat] step
|
|
||||||
fork [i] app2
|
|
||||||
gcd 1 [tuck modulus dup 0 >] loop pop
|
|
||||||
ifte [nullary not] dipd branch
|
|
||||||
ii [dip] dupdip i
|
|
||||||
least_fraction dup [gcd] infra [div] concat map
|
|
||||||
make_generator [codireco] ccons
|
|
||||||
nullary [stack] dinfrirst
|
|
||||||
of swap at
|
|
||||||
pam [i] map
|
|
||||||
tailrec [i] genrec
|
|
||||||
product 1 swap [*] step
|
|
||||||
quoted [unit] dip
|
|
||||||
range [0 <=] [1 - dup] anamorphism
|
|
||||||
range_to_zero unit [down_to_zero] infra
|
|
||||||
run [] swap infra
|
|
||||||
size 0 swap [pop ++] step
|
|
||||||
sqr dup mul
|
|
||||||
step_zero 0 roll> step
|
|
||||||
swoncat swap concat
|
|
||||||
tailrec [i] genrec
|
|
||||||
ternary unary [popop] dip
|
|
||||||
unary nullary popd
|
|
||||||
unquoted [i] dip
|
|
||||||
while swap [nullary] cons dup dipd concat loop
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Currently, there's no function to add new definitions to the dictionary
|
|
||||||
from "within" Joy code itself. Adding new definitions remains a
|
|
||||||
meta-interpreter action. You have to do it yourself, in Python, and wash
|
|
||||||
your hands afterward.
|
|
||||||
|
|
||||||
It would be simple enough to define one, but it would open the door to
|
|
||||||
*name binding* and break the idea that all state is captured in the
|
|
||||||
stack and expression. There's an implicit *standard dictionary* that
|
|
||||||
defines the actual semantics of the syntactic stack and expression
|
|
||||||
datastructures (which only contain symbols, not the actual functions.
|
|
||||||
Pickle some and see for yourself.)
|
|
||||||
|
|
||||||
"There should be only one."
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Which brings me to talking about one of my hopes and dreams for this
|
|
||||||
notation: "There should be only one." What I mean is that there should
|
|
||||||
be one universal standard dictionary of commands, and all bespoke work
|
|
||||||
done in a UI for purposes takes place by direct interaction and macros.
|
|
||||||
There would be a *Grand Refactoring* biannually (two years, not six
|
|
||||||
months, that's semi-annually) where any new definitions factored out of
|
|
||||||
the usage and macros of the previous time, along with new algorithms and
|
|
||||||
such, were entered into the dictionary and posted to e.g. IPFS.
|
|
||||||
|
|
||||||
Code should not burgeon wildly, as it does today. The variety of code
|
|
||||||
should map more-or-less to the well-factored variety of human
|
|
||||||
computably-solvable problems. There shouldn't be dozens of chat apps, JS
|
|
||||||
frameworks, programming languages. It's a waste of time, a `fractal
|
|
||||||
"thundering herd"
|
|
||||||
attack <https://en.wikipedia.org/wiki/Thundering_herd_problem>`__ on
|
|
||||||
human mentality.
|
|
||||||
|
|
||||||
Literary Code Library
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
If you read over the other notebooks you'll see that developing code in
|
|
||||||
Joy is a lot like doing simple mathematics, and the descriptions of the
|
|
||||||
code resemble math papers. The code also works the first time, no bugs.
|
|
||||||
If you have any experience programming at all, you are probably
|
|
||||||
skeptical, as I was, but it seems to work: deriving code mathematically
|
|
||||||
seems to lead to fewer errors.
|
|
||||||
|
|
||||||
But my point now is that this great ratio of textual explanation to wind
|
|
||||||
up with code that consists of a few equations and could fit on an index
|
|
||||||
card is highly desirable. Less code has fewer errors. The structure of
|
|
||||||
Joy engenders a kind of thinking that seems to be very effective for
|
|
||||||
developing structured processes.
|
|
||||||
|
|
||||||
There seems to be an elegance and power to the notation.
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,136 +0,0 @@
|
||||||
### Preamble
|
|
||||||
|
|
||||||
First, import what we need.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from joy.joy import run
|
|
||||||
from joy.library import initialize
|
|
||||||
from joy.utils.stack import stack_to_string
|
|
||||||
from joy.utils.pretty_print import TracePrinter
|
|
||||||
```
|
|
||||||
|
|
||||||
Define a dictionary, an initial stack, and two helper functions to run Joy code and print results for us.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
D = initialize()
|
|
||||||
S = ()
|
|
||||||
|
|
||||||
|
|
||||||
def J(text):
|
|
||||||
print(stack_to_string(run(text, S, D)[0]))
|
|
||||||
|
|
||||||
|
|
||||||
def V(text):
|
|
||||||
tp = TracePrinter()
|
|
||||||
run(text, S, D, tp.viewer)
|
|
||||||
tp.print_()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run some simple programs
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('23 18 +')
|
|
||||||
```
|
|
||||||
|
|
||||||
41
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('45 30 gcd')
|
|
||||||
```
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
||||||
### With Viewer
|
|
||||||
|
|
||||||
A `viewer` records each step of the evaluation of a Joy program. The `TracePrinter` has a facility for printing out a trace of the evaluation, one line per step. Each step is aligned to the current interpreter position, signified by a period separating the stack on the left from the pending expression ("continuation") on the right. I find these traces beautiful, like a kind of art.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('23 18 +')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 23 18 +
|
|
||||||
23 • 18 +
|
|
||||||
23 18 • +
|
|
||||||
41 •
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('45 30 gcd')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 45 30 gcd
|
|
||||||
45 • 30 gcd
|
|
||||||
45 30 • gcd
|
|
||||||
45 30 • 1 [tuck modulus dup 0 >] loop pop
|
|
||||||
45 30 1 • [tuck modulus dup 0 >] loop pop
|
|
||||||
45 30 1 [tuck modulus dup 0 >] • loop pop
|
|
||||||
45 30 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 45 30 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 15 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 15 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 True • [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 True [tuck modulus dup 0 >] • loop pop
|
|
||||||
30 15 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 30 15 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 0 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 0 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 False • [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 False [tuck modulus dup 0 >] • loop pop
|
|
||||||
15 0 • pop
|
|
||||||
15 •
|
|
||||||
|
|
||||||
|
|
||||||
Here's a longer trace.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('96 27 gcd')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 96 27 gcd
|
|
||||||
96 • 27 gcd
|
|
||||||
96 27 • gcd
|
|
||||||
96 27 • 1 [tuck modulus dup 0 >] loop pop
|
|
||||||
96 27 1 • [tuck modulus dup 0 >] loop pop
|
|
||||||
96 27 1 [tuck modulus dup 0 >] • loop pop
|
|
||||||
96 27 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 96 27 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 15 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 15 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 True • [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 True [tuck modulus dup 0 >] • loop pop
|
|
||||||
27 15 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 27 15 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 12 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 12 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 True • [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 True [tuck modulus dup 0 >] • loop pop
|
|
||||||
15 12 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 15 12 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 3 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 3 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 True • [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 True [tuck modulus dup 0 >] • loop pop
|
|
||||||
12 3 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 12 3 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 0 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 0 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 False • [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 False [tuck modulus dup 0 >] • loop pop
|
|
||||||
3 0 • pop
|
|
||||||
3 •
|
|
||||||
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
Preamble
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
First, import what we need.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from joy.joy import run
|
|
||||||
from joy.library import initialize
|
|
||||||
from joy.utils.stack import stack_to_string
|
|
||||||
from joy.utils.pretty_print import TracePrinter
|
|
||||||
|
|
||||||
Define a dictionary, an initial stack, and two helper functions to run
|
|
||||||
Joy code and print results for us.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
D = initialize()
|
|
||||||
S = ()
|
|
||||||
|
|
||||||
|
|
||||||
def J(text):
|
|
||||||
print(stack_to_string(run(text, S, D)[0]))
|
|
||||||
|
|
||||||
|
|
||||||
def V(text):
|
|
||||||
tp = TracePrinter()
|
|
||||||
run(text, S, D, tp.viewer)
|
|
||||||
tp.print_()
|
|
||||||
|
|
||||||
Run some simple programs
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('23 18 +')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
41
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('45 30 gcd')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
||||||
With Viewer
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
A ``viewer`` records each step of the evaluation of a Joy program. The
|
|
||||||
``TracePrinter`` has a facility for printing out a trace of the
|
|
||||||
evaluation, one line per step. Each step is aligned to the current
|
|
||||||
interpreter position, signified by a period separating the stack on the
|
|
||||||
left from the pending expression ("continuation") on the right. I find
|
|
||||||
these traces beautiful, like a kind of art.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('23 18 +')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 23 18 +
|
|
||||||
23 • 18 +
|
|
||||||
23 18 • +
|
|
||||||
41 •
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('45 30 gcd')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 45 30 gcd
|
|
||||||
45 • 30 gcd
|
|
||||||
45 30 • gcd
|
|
||||||
45 30 • 1 [tuck modulus dup 0 >] loop pop
|
|
||||||
45 30 1 • [tuck modulus dup 0 >] loop pop
|
|
||||||
45 30 1 [tuck modulus dup 0 >] • loop pop
|
|
||||||
45 30 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 45 30 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 15 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 15 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 True • [tuck modulus dup 0 >] loop pop
|
|
||||||
30 15 True [tuck modulus dup 0 >] • loop pop
|
|
||||||
30 15 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 30 15 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 0 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 0 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 False • [tuck modulus dup 0 >] loop pop
|
|
||||||
15 0 False [tuck modulus dup 0 >] • loop pop
|
|
||||||
15 0 • pop
|
|
||||||
15 •
|
|
||||||
|
|
||||||
|
|
||||||
Here's a longer trace.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('96 27 gcd')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 96 27 gcd
|
|
||||||
96 • 27 gcd
|
|
||||||
96 27 • gcd
|
|
||||||
96 27 • 1 [tuck modulus dup 0 >] loop pop
|
|
||||||
96 27 1 • [tuck modulus dup 0 >] loop pop
|
|
||||||
96 27 1 [tuck modulus dup 0 >] • loop pop
|
|
||||||
96 27 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 96 27 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 15 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 15 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 True • [tuck modulus dup 0 >] loop pop
|
|
||||||
27 15 True [tuck modulus dup 0 >] • loop pop
|
|
||||||
27 15 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 27 15 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 12 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 12 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 True • [tuck modulus dup 0 >] loop pop
|
|
||||||
15 12 True [tuck modulus dup 0 >] • loop pop
|
|
||||||
15 12 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 15 12 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 3 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 3 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 True • [tuck modulus dup 0 >] loop pop
|
|
||||||
12 3 True [tuck modulus dup 0 >] • loop pop
|
|
||||||
12 3 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 12 3 • modulus dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 • dup 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 0 • 0 > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 0 0 • > [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 False • [tuck modulus dup 0 >] loop pop
|
|
||||||
3 0 False [tuck modulus dup 0 >] • loop pop
|
|
||||||
3 0 • pop
|
|
||||||
3 •
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,686 +0,0 @@
|
||||||
# [Project Euler, first problem: "Multiples of 3 and 5"](https://projecteuler.net/problem=1)
|
|
||||||
|
|
||||||
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
|
|
||||||
|
|
||||||
Find the sum of all the multiples of 3 or 5 below 1000.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's create a predicate that returns `True` if a number is a multiple of 3 or 5 and `False` otherwise.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('P [3 % not] dupdip 5 % not or')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('80 P')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 80 P
|
|
||||||
80 • P
|
|
||||||
80 • [3 % not] dupdip 5 % not or
|
|
||||||
80 [3 % not] • dupdip 5 % not or
|
|
||||||
80 • 3 % not 80 5 % not or
|
|
||||||
80 3 • % not 80 5 % not or
|
|
||||||
2 • not 80 5 % not or
|
|
||||||
False • 80 5 % not or
|
|
||||||
False 80 • 5 % not or
|
|
||||||
False 80 5 • % not or
|
|
||||||
False 0 • not or
|
|
||||||
False True • or
|
|
||||||
True •
|
|
||||||
|
|
||||||
|
|
||||||
Given the predicate function `P` a suitable program is:
|
|
||||||
|
|
||||||
PE1 == 1000 range [P] filter sum
|
|
||||||
|
|
||||||
This function generates a list of the integers from 0 to 999, filters
|
|
||||||
that list by `P`, and then sums the result.
|
|
||||||
|
|
||||||
Logically this is fine, but pragmatically we are doing more work than we
|
|
||||||
should be; we generate one thousand integers but actually use less than
|
|
||||||
half of them. A better solution would be to generate just the multiples
|
|
||||||
we want to sum, and to add them as we go rather than storing them and
|
|
||||||
adding summing them at the end.
|
|
||||||
|
|
||||||
At first I had the idea to use two counters and increase them by three
|
|
||||||
and five, respectively. This way we only generate the terms that we
|
|
||||||
actually want to sum. We have to proceed by incrementing the counter
|
|
||||||
that is lower, or if they are equal, the three counter, and we have to
|
|
||||||
take care not to double add numbers like 15 that are multiples of both
|
|
||||||
three and five.
|
|
||||||
|
|
||||||
This seemed a little clunky, so I tried a different approach.
|
|
||||||
|
|
||||||
Consider the first few terms in the series:
|
|
||||||
|
|
||||||
3 5 6 9 10 12 15 18 20 21 ...
|
|
||||||
|
|
||||||
Subtract each number from the one after it (subtracting 0 from 3):
|
|
||||||
|
|
||||||
3 5 6 9 10 12 15 18 20 21 24 25 27 30 ...
|
|
||||||
0 3 5 6 9 10 12 15 18 20 21 24 25 27 ...
|
|
||||||
-------------------------------------------
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 ...
|
|
||||||
|
|
||||||
You get this lovely repeating palindromic sequence:
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3
|
|
||||||
|
|
||||||
To make a counter that increments by factors of 3 and 5 you just add
|
|
||||||
these differences to the counter one-by-one in a loop.
|
|
||||||
|
|
||||||
|
|
||||||
To make use of this sequence to increment a counter and sum terms as we
|
|
||||||
go we need a function that will accept the sum, the counter, and the next
|
|
||||||
term to add, and that adds the term to the counter and a copy of the
|
|
||||||
counter to the running sum. This function will do that:
|
|
||||||
|
|
||||||
PE1.1 == + [+] dupdip
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1.1 + [+] dupdip')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('0 0 3 PE1.1')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 0 0 3 PE1.1
|
|
||||||
0 • 0 3 PE1.1
|
|
||||||
0 0 • 3 PE1.1
|
|
||||||
0 0 3 • PE1.1
|
|
||||||
0 0 3 • + [+] dupdip
|
|
||||||
0 3 • [+] dupdip
|
|
||||||
0 3 [+] • dupdip
|
|
||||||
0 3 • + 3
|
|
||||||
3 • 3
|
|
||||||
3 3 •
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('0 0 [3 2 1 3 1 2 3] [PE1.1] step')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 0 0 [3 2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 • 0 [3 2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 0 • [3 2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 0 [3 2 1 3 1 2 3] • [PE1.1] step
|
|
||||||
0 0 [3 2 1 3 1 2 3] [PE1.1] • step
|
|
||||||
0 0 3 [PE1.1] • i [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 0 3 • PE1.1 [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 0 3 • + [+] dupdip [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 3 • [+] dupdip [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 3 [+] • dupdip [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 3 • + 3 [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
3 • 3 [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
3 3 • [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
3 3 [2 1 3 1 2 3] • [PE1.1] step
|
|
||||||
3 3 [2 1 3 1 2 3] [PE1.1] • step
|
|
||||||
3 3 2 [PE1.1] • i [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 3 2 • PE1.1 [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 3 2 • + [+] dupdip [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 5 • [+] dupdip [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 5 [+] • dupdip [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 5 • + 5 [1 3 1 2 3] [PE1.1] step
|
|
||||||
8 • 5 [1 3 1 2 3] [PE1.1] step
|
|
||||||
8 5 • [1 3 1 2 3] [PE1.1] step
|
|
||||||
8 5 [1 3 1 2 3] • [PE1.1] step
|
|
||||||
8 5 [1 3 1 2 3] [PE1.1] • step
|
|
||||||
8 5 1 [PE1.1] • i [3 1 2 3] [PE1.1] step
|
|
||||||
8 5 1 • PE1.1 [3 1 2 3] [PE1.1] step
|
|
||||||
8 5 1 • + [+] dupdip [3 1 2 3] [PE1.1] step
|
|
||||||
8 6 • [+] dupdip [3 1 2 3] [PE1.1] step
|
|
||||||
8 6 [+] • dupdip [3 1 2 3] [PE1.1] step
|
|
||||||
8 6 • + 6 [3 1 2 3] [PE1.1] step
|
|
||||||
14 • 6 [3 1 2 3] [PE1.1] step
|
|
||||||
14 6 • [3 1 2 3] [PE1.1] step
|
|
||||||
14 6 [3 1 2 3] • [PE1.1] step
|
|
||||||
14 6 [3 1 2 3] [PE1.1] • step
|
|
||||||
14 6 3 [PE1.1] • i [1 2 3] [PE1.1] step
|
|
||||||
14 6 3 • PE1.1 [1 2 3] [PE1.1] step
|
|
||||||
14 6 3 • + [+] dupdip [1 2 3] [PE1.1] step
|
|
||||||
14 9 • [+] dupdip [1 2 3] [PE1.1] step
|
|
||||||
14 9 [+] • dupdip [1 2 3] [PE1.1] step
|
|
||||||
14 9 • + 9 [1 2 3] [PE1.1] step
|
|
||||||
23 • 9 [1 2 3] [PE1.1] step
|
|
||||||
23 9 • [1 2 3] [PE1.1] step
|
|
||||||
23 9 [1 2 3] • [PE1.1] step
|
|
||||||
23 9 [1 2 3] [PE1.1] • step
|
|
||||||
23 9 1 [PE1.1] • i [2 3] [PE1.1] step
|
|
||||||
23 9 1 • PE1.1 [2 3] [PE1.1] step
|
|
||||||
23 9 1 • + [+] dupdip [2 3] [PE1.1] step
|
|
||||||
23 10 • [+] dupdip [2 3] [PE1.1] step
|
|
||||||
23 10 [+] • dupdip [2 3] [PE1.1] step
|
|
||||||
23 10 • + 10 [2 3] [PE1.1] step
|
|
||||||
33 • 10 [2 3] [PE1.1] step
|
|
||||||
33 10 • [2 3] [PE1.1] step
|
|
||||||
33 10 [2 3] • [PE1.1] step
|
|
||||||
33 10 [2 3] [PE1.1] • step
|
|
||||||
33 10 2 [PE1.1] • i [3] [PE1.1] step
|
|
||||||
33 10 2 • PE1.1 [3] [PE1.1] step
|
|
||||||
33 10 2 • + [+] dupdip [3] [PE1.1] step
|
|
||||||
33 12 • [+] dupdip [3] [PE1.1] step
|
|
||||||
33 12 [+] • dupdip [3] [PE1.1] step
|
|
||||||
33 12 • + 12 [3] [PE1.1] step
|
|
||||||
45 • 12 [3] [PE1.1] step
|
|
||||||
45 12 • [3] [PE1.1] step
|
|
||||||
45 12 [3] • [PE1.1] step
|
|
||||||
45 12 [3] [PE1.1] • step
|
|
||||||
45 12 3 [PE1.1] • i
|
|
||||||
45 12 3 • PE1.1
|
|
||||||
45 12 3 • + [+] dupdip
|
|
||||||
45 15 • [+] dupdip
|
|
||||||
45 15 [+] • dupdip
|
|
||||||
45 15 • + 15
|
|
||||||
60 • 15
|
|
||||||
60 15 •
|
|
||||||
|
|
||||||
|
|
||||||
So one `step` through all seven terms brings the counter to 15 and the total to 60.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
1000 / 15
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
66.66666666666667
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
66 * 15
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
990
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
1000 - 990
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
10
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
We only want the terms *less than* 1000.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
999 - 990
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
9
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
That means we want to run the full list of numbers sixty-six times to get to 990 and then the first four numbers 3 2 1 3 to get to 999.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1 0 0 66 [[3 2 1 3 1 2 3] [PE1.1] step] times [3 2 1 3] [PE1.1] step pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('PE1')
|
|
||||||
```
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
This form uses no extra storage and produces no unused summands. It's
|
|
||||||
good but there's one more trick we can apply. The list of seven terms
|
|
||||||
takes up at least seven bytes. But notice that all of the terms are less
|
|
||||||
than four, and so each can fit in just two bits. We could store all
|
|
||||||
seven terms in just fourteen bits and use masking and shifts to pick out
|
|
||||||
each term as we go. This will use less space and save time loading whole
|
|
||||||
integer terms from the list.
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3
|
|
||||||
0b 11 10 01 11 01 10 11 == 14811
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
0b11100111011011
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
14811
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1.2 [3 & PE1.1] dupdip 2 >>')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('0 0 14811 PE1.2')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 0 0 14811 PE1.2
|
|
||||||
0 • 0 14811 PE1.2
|
|
||||||
0 0 • 14811 PE1.2
|
|
||||||
0 0 14811 • PE1.2
|
|
||||||
0 0 14811 • [3 & PE1.1] dupdip 2 >>
|
|
||||||
0 0 14811 [3 & PE1.1] • dupdip 2 >>
|
|
||||||
0 0 14811 • 3 & PE1.1 14811 2 >>
|
|
||||||
0 0 14811 3 • & PE1.1 14811 2 >>
|
|
||||||
0 0 3 • PE1.1 14811 2 >>
|
|
||||||
0 0 3 • + [+] dupdip 14811 2 >>
|
|
||||||
0 3 • [+] dupdip 14811 2 >>
|
|
||||||
0 3 [+] • dupdip 14811 2 >>
|
|
||||||
0 3 • + 3 14811 2 >>
|
|
||||||
3 • 3 14811 2 >>
|
|
||||||
3 3 • 14811 2 >>
|
|
||||||
3 3 14811 • 2 >>
|
|
||||||
3 3 14811 2 • >>
|
|
||||||
3 3 3702 •
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('3 3 3702 PE1.2')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 3 3 3702 PE1.2
|
|
||||||
3 • 3 3702 PE1.2
|
|
||||||
3 3 • 3702 PE1.2
|
|
||||||
3 3 3702 • PE1.2
|
|
||||||
3 3 3702 • [3 & PE1.1] dupdip 2 >>
|
|
||||||
3 3 3702 [3 & PE1.1] • dupdip 2 >>
|
|
||||||
3 3 3702 • 3 & PE1.1 3702 2 >>
|
|
||||||
3 3 3702 3 • & PE1.1 3702 2 >>
|
|
||||||
3 3 2 • PE1.1 3702 2 >>
|
|
||||||
3 3 2 • + [+] dupdip 3702 2 >>
|
|
||||||
3 5 • [+] dupdip 3702 2 >>
|
|
||||||
3 5 [+] • dupdip 3702 2 >>
|
|
||||||
3 5 • + 5 3702 2 >>
|
|
||||||
8 • 5 3702 2 >>
|
|
||||||
8 5 • 3702 2 >>
|
|
||||||
8 5 3702 • 2 >>
|
|
||||||
8 5 3702 2 • >>
|
|
||||||
8 5 925 •
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('0 0 14811 7 [PE1.2] times pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 0 0 14811 7 [PE1.2] times pop
|
|
||||||
0 • 0 14811 7 [PE1.2] times pop
|
|
||||||
0 0 • 14811 7 [PE1.2] times pop
|
|
||||||
0 0 14811 • 7 [PE1.2] times pop
|
|
||||||
0 0 14811 7 • [PE1.2] times pop
|
|
||||||
0 0 14811 7 [PE1.2] • times pop
|
|
||||||
0 0 14811 • PE1.2 6 [PE1.2] times pop
|
|
||||||
0 0 14811 • [3 & PE1.1] dupdip 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 14811 [3 & PE1.1] • dupdip 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 14811 • 3 & PE1.1 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 14811 3 • & PE1.1 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 3 • PE1.1 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 3 • + [+] dupdip 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 3 • [+] dupdip 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 3 [+] • dupdip 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 3 • + 3 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
3 • 3 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
3 3 • 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
3 3 14811 • 2 >> 6 [PE1.2] times pop
|
|
||||||
3 3 14811 2 • >> 6 [PE1.2] times pop
|
|
||||||
3 3 3702 • 6 [PE1.2] times pop
|
|
||||||
3 3 3702 6 • [PE1.2] times pop
|
|
||||||
3 3 3702 6 [PE1.2] • times pop
|
|
||||||
3 3 3702 • PE1.2 5 [PE1.2] times pop
|
|
||||||
3 3 3702 • [3 & PE1.1] dupdip 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 3702 [3 & PE1.1] • dupdip 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 3702 • 3 & PE1.1 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 3702 3 • & PE1.1 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 2 • PE1.1 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 2 • + [+] dupdip 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 5 • [+] dupdip 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 5 [+] • dupdip 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 5 • + 5 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
8 • 5 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
8 5 • 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
8 5 3702 • 2 >> 5 [PE1.2] times pop
|
|
||||||
8 5 3702 2 • >> 5 [PE1.2] times pop
|
|
||||||
8 5 925 • 5 [PE1.2] times pop
|
|
||||||
8 5 925 5 • [PE1.2] times pop
|
|
||||||
8 5 925 5 [PE1.2] • times pop
|
|
||||||
8 5 925 • PE1.2 4 [PE1.2] times pop
|
|
||||||
8 5 925 • [3 & PE1.1] dupdip 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 925 [3 & PE1.1] • dupdip 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 925 • 3 & PE1.1 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 925 3 • & PE1.1 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 1 • PE1.1 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 1 • + [+] dupdip 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 6 • [+] dupdip 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 6 [+] • dupdip 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 6 • + 6 925 2 >> 4 [PE1.2] times pop
|
|
||||||
14 • 6 925 2 >> 4 [PE1.2] times pop
|
|
||||||
14 6 • 925 2 >> 4 [PE1.2] times pop
|
|
||||||
14 6 925 • 2 >> 4 [PE1.2] times pop
|
|
||||||
14 6 925 2 • >> 4 [PE1.2] times pop
|
|
||||||
14 6 231 • 4 [PE1.2] times pop
|
|
||||||
14 6 231 4 • [PE1.2] times pop
|
|
||||||
14 6 231 4 [PE1.2] • times pop
|
|
||||||
14 6 231 • PE1.2 3 [PE1.2] times pop
|
|
||||||
14 6 231 • [3 & PE1.1] dupdip 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 231 [3 & PE1.1] • dupdip 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 231 • 3 & PE1.1 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 231 3 • & PE1.1 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 3 • PE1.1 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 3 • + [+] dupdip 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 9 • [+] dupdip 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 9 [+] • dupdip 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 9 • + 9 231 2 >> 3 [PE1.2] times pop
|
|
||||||
23 • 9 231 2 >> 3 [PE1.2] times pop
|
|
||||||
23 9 • 231 2 >> 3 [PE1.2] times pop
|
|
||||||
23 9 231 • 2 >> 3 [PE1.2] times pop
|
|
||||||
23 9 231 2 • >> 3 [PE1.2] times pop
|
|
||||||
23 9 57 • 3 [PE1.2] times pop
|
|
||||||
23 9 57 3 • [PE1.2] times pop
|
|
||||||
23 9 57 3 [PE1.2] • times pop
|
|
||||||
23 9 57 • PE1.2 2 [PE1.2] times pop
|
|
||||||
23 9 57 • [3 & PE1.1] dupdip 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 57 [3 & PE1.1] • dupdip 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 57 • 3 & PE1.1 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 57 3 • & PE1.1 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 1 • PE1.1 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 1 • + [+] dupdip 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 10 • [+] dupdip 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 10 [+] • dupdip 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 10 • + 10 57 2 >> 2 [PE1.2] times pop
|
|
||||||
33 • 10 57 2 >> 2 [PE1.2] times pop
|
|
||||||
33 10 • 57 2 >> 2 [PE1.2] times pop
|
|
||||||
33 10 57 • 2 >> 2 [PE1.2] times pop
|
|
||||||
33 10 57 2 • >> 2 [PE1.2] times pop
|
|
||||||
33 10 14 • 2 [PE1.2] times pop
|
|
||||||
33 10 14 2 • [PE1.2] times pop
|
|
||||||
33 10 14 2 [PE1.2] • times pop
|
|
||||||
33 10 14 • PE1.2 1 [PE1.2] times pop
|
|
||||||
33 10 14 • [3 & PE1.1] dupdip 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 14 [3 & PE1.1] • dupdip 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 14 • 3 & PE1.1 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 14 3 • & PE1.1 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 2 • PE1.1 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 2 • + [+] dupdip 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 12 • [+] dupdip 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 12 [+] • dupdip 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 12 • + 12 14 2 >> 1 [PE1.2] times pop
|
|
||||||
45 • 12 14 2 >> 1 [PE1.2] times pop
|
|
||||||
45 12 • 14 2 >> 1 [PE1.2] times pop
|
|
||||||
45 12 14 • 2 >> 1 [PE1.2] times pop
|
|
||||||
45 12 14 2 • >> 1 [PE1.2] times pop
|
|
||||||
45 12 3 • 1 [PE1.2] times pop
|
|
||||||
45 12 3 1 • [PE1.2] times pop
|
|
||||||
45 12 3 1 [PE1.2] • times pop
|
|
||||||
45 12 3 • PE1.2 pop
|
|
||||||
45 12 3 • [3 & PE1.1] dupdip 2 >> pop
|
|
||||||
45 12 3 [3 & PE1.1] • dupdip 2 >> pop
|
|
||||||
45 12 3 • 3 & PE1.1 3 2 >> pop
|
|
||||||
45 12 3 3 • & PE1.1 3 2 >> pop
|
|
||||||
45 12 3 • PE1.1 3 2 >> pop
|
|
||||||
45 12 3 • + [+] dupdip 3 2 >> pop
|
|
||||||
45 15 • [+] dupdip 3 2 >> pop
|
|
||||||
45 15 [+] • dupdip 3 2 >> pop
|
|
||||||
45 15 • + 15 3 2 >> pop
|
|
||||||
60 • 15 3 2 >> pop
|
|
||||||
60 15 • 3 2 >> pop
|
|
||||||
60 15 3 • 2 >> pop
|
|
||||||
60 15 3 2 • >> pop
|
|
||||||
60 15 0 • pop
|
|
||||||
60 15 •
|
|
||||||
|
|
||||||
|
|
||||||
And so we have at last:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1 0 0 66 [14811 7 [PE1.2] times pop] times 14811 4 [PE1.2] times popop')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('PE1')
|
|
||||||
```
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
Let's refactor.
|
|
||||||
|
|
||||||
14811 7 [PE1.2] times pop
|
|
||||||
14811 4 [PE1.2] times pop
|
|
||||||
14811 n [PE1.2] times pop
|
|
||||||
n 14811 swap [PE1.2] times pop
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1.3 14811 swap [PE1.2] times pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can simplify the definition above:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1 0 0 66 [7 PE1.3] times 4 PE1.3 pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('PE1')
|
|
||||||
```
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
Here's our joy program all in one place. It doesn't make so much sense, but if you have read through the above description of how it was derived I hope it's clear.
|
|
||||||
|
|
||||||
PE1.1 == + [+] dupdip
|
|
||||||
PE1.2 == [3 & PE1.1] dupdip 2 >>
|
|
||||||
PE1.3 == 14811 swap [PE1.2] times pop
|
|
||||||
PE1 == 0 0 66 [7 PE1.3] times 4 PE1.3 pop
|
|
||||||
|
|
||||||
# Generator Version
|
|
||||||
It's a little clunky iterating sixty-six times though the seven numbers then four more. In the _Generator Programs_ notebook we derive a generator that can be repeatedly driven by the `x` combinator to produce a stream of the seven numbers repeating over and over again.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1.terms [0 swap [dup [pop 14811] [] branch [3 &] dupdip 2 >>] dip rest cons]')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('PE1.terms 21 [x] times')
|
|
||||||
```
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 [0 swap [dup [pop 14811] [] branch [3 &] dupdip 2 >>] dip rest cons]
|
|
||||||
|
|
||||||
|
|
||||||
We know from above that we need sixty-six times seven then four more terms to reach up to but not over one thousand.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('7 66 * 4 +')
|
|
||||||
```
|
|
||||||
|
|
||||||
466
|
|
||||||
|
|
||||||
|
|
||||||
### Here they are...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('PE1.terms 466 [x] times pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3
|
|
||||||
|
|
||||||
|
|
||||||
### ...and they do sum to 999.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[PE1.terms 466 [x] times pop] run sum')
|
|
||||||
```
|
|
||||||
|
|
||||||
999
|
|
||||||
|
|
||||||
|
|
||||||
Now we can use `PE1.1` to accumulate the terms as we go, and then `pop` the generator and the counter from the stack when we're done, leaving just the sum.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('0 0 PE1.terms 466 [x [PE1.1] dip] times popop')
|
|
||||||
```
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
# A little further analysis renders iteration unnecessary.
|
|
||||||
Consider finding the sum of the positive integers less than or equal to ten.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[10 9 8 7 6 5 4 3 2 1] sum')
|
|
||||||
```
|
|
||||||
|
|
||||||
55
|
|
||||||
|
|
||||||
|
|
||||||
Instead of summing them, [observe](https://en.wikipedia.org/wiki/File:Animated_proof_for_the_formula_giving_the_sum_of_the_first_integers_1%2B2%2B...%2Bn.gif):
|
|
||||||
|
|
||||||
10 9 8 7 6
|
|
||||||
+ 1 2 3 4 5
|
|
||||||
---- -- -- -- --
|
|
||||||
11 11 11 11 11
|
|
||||||
|
|
||||||
11 * 5 = 55
|
|
||||||
|
|
||||||
From the above example we can deduce that the sum of the first N positive integers is:
|
|
||||||
|
|
||||||
(N + 1) * N / 2
|
|
||||||
|
|
||||||
(The formula also works for odd values of N, I'll leave that to you if you want to work it out or you can take my word for it.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('F dup ++ * 2 floordiv')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('10 F')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 10 F
|
|
||||||
10 • F
|
|
||||||
10 • dup ++ * 2 floordiv
|
|
||||||
10 10 • ++ * 2 floordiv
|
|
||||||
10 11 • * 2 floordiv
|
|
||||||
110 • 2 floordiv
|
|
||||||
110 2 • floordiv
|
|
||||||
55 •
|
|
||||||
|
|
||||||
|
|
||||||
## Generalizing to Blocks of Terms
|
|
||||||
We can apply the same reasoning to the PE1 problem.
|
|
||||||
|
|
||||||
Between 0 and 990 inclusive there are sixty-six "blocks" of seven terms each, starting with:
|
|
||||||
|
|
||||||
[3 5 6 9 10 12 15]
|
|
||||||
|
|
||||||
And ending with:
|
|
||||||
|
|
||||||
[978 980 981 984 985 987 990]
|
|
||||||
|
|
||||||
If we reverse one of these two blocks and sum pairs...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip')
|
|
||||||
```
|
|
||||||
|
|
||||||
[[978 15] [980 12] [981 10] [984 9] [985 6] [987 5] [990 3]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip [sum] map')
|
|
||||||
```
|
|
||||||
|
|
||||||
[993 992 991 993 991 992 993]
|
|
||||||
|
|
||||||
|
|
||||||
(Interesting that the sequence of seven numbers appears again in the rightmost digit of each term.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[ 3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip [sum] map sum')
|
|
||||||
```
|
|
||||||
|
|
||||||
6945
|
|
||||||
|
|
||||||
|
|
||||||
Since there are sixty-six blocks and we are pairing them up, there must be thirty-three pairs, each of which sums to 6945. We also have these additional unpaired terms between 990 and 1000:
|
|
||||||
|
|
||||||
993 995 996 999
|
|
||||||
|
|
||||||
So we can give the "sum of all the multiples of 3 or 5 below 1000" like so:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('6945 33 * [993 995 996 999] cons sum')
|
|
||||||
```
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
It's worth noting, I think, that this same reasoning holds for any two numbers $n$ and $m$ the multiples of which we hope to sum. The multiples would have a cycle of differences of length $k$ and so we could compute the sum of $Nk$ multiples as above.
|
|
||||||
|
|
||||||
The sequence of differences will always be a palidrome. Consider an interval spanning the least common multiple of $n$ and $m$:
|
|
||||||
|
|
||||||
| | | | | | | |
|
|
||||||
| | | | |
|
|
||||||
|
|
||||||
Here we have 4 and 7, and you can read off the sequence of differences directly from the diagram: 4 3 1 4 2 2 4 1 3 4.
|
|
||||||
|
|
||||||
Geometrically, the actual values of $n$ and $m$ and their *lcm* don't matter, the pattern they make will always be symmetrical around its midpoint. The same reasoning holds for multiples of more than two numbers.
|
|
||||||
|
|
||||||
# The Simplest Program
|
|
||||||
|
|
||||||
Of course, the simplest joy program for the first Project Euler problem is just:
|
|
||||||
|
|
||||||
PE1 == 233168
|
|
||||||
|
|
||||||
Fin.
|
|
||||||
|
|
@ -1,791 +0,0 @@
|
||||||
`Project Euler, first problem: "Multiples of 3 and 5" <https://projecteuler.net/problem=1>`__
|
|
||||||
=============================================================================================
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
|
|
||||||
|
|
||||||
Find the sum of all the multiples of 3 or 5 below 1000.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
|
|
||||||
Let's create a predicate that returns ``True`` if a number is a multiple
|
|
||||||
of 3 or 5 and ``False`` otherwise.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('P [3 % not] dupdip 5 % not or')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('80 P')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 80 P
|
|
||||||
80 • P
|
|
||||||
80 • [3 % not] dupdip 5 % not or
|
|
||||||
80 [3 % not] • dupdip 5 % not or
|
|
||||||
80 • 3 % not 80 5 % not or
|
|
||||||
80 3 • % not 80 5 % not or
|
|
||||||
2 • not 80 5 % not or
|
|
||||||
False • 80 5 % not or
|
|
||||||
False 80 • 5 % not or
|
|
||||||
False 80 5 • % not or
|
|
||||||
False 0 • not or
|
|
||||||
False True • or
|
|
||||||
True •
|
|
||||||
|
|
||||||
|
|
||||||
Given the predicate function ``P`` a suitable program is:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
PE1 == 1000 range [P] filter sum
|
|
||||||
|
|
||||||
This function generates a list of the integers from 0 to 999, filters
|
|
||||||
that list by ``P``, and then sums the result.
|
|
||||||
|
|
||||||
Logically this is fine, but pragmatically we are doing more work than we
|
|
||||||
should be; we generate one thousand integers but actually use less than
|
|
||||||
half of them. A better solution would be to generate just the multiples
|
|
||||||
we want to sum, and to add them as we go rather than storing them and
|
|
||||||
adding summing them at the end.
|
|
||||||
|
|
||||||
At first I had the idea to use two counters and increase them by three
|
|
||||||
and five, respectively. This way we only generate the terms that we
|
|
||||||
actually want to sum. We have to proceed by incrementing the counter
|
|
||||||
that is lower, or if they are equal, the three counter, and we have to
|
|
||||||
take care not to double add numbers like 15 that are multiples of both
|
|
||||||
three and five.
|
|
||||||
|
|
||||||
This seemed a little clunky, so I tried a different approach.
|
|
||||||
|
|
||||||
Consider the first few terms in the series:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
3 5 6 9 10 12 15 18 20 21 ...
|
|
||||||
|
|
||||||
Subtract each number from the one after it (subtracting 0 from 3):
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
3 5 6 9 10 12 15 18 20 21 24 25 27 30 ...
|
|
||||||
0 3 5 6 9 10 12 15 18 20 21 24 25 27 ...
|
|
||||||
-------------------------------------------
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 ...
|
|
||||||
|
|
||||||
You get this lovely repeating palindromic sequence:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3
|
|
||||||
|
|
||||||
To make a counter that increments by factors of 3 and 5 you just add
|
|
||||||
these differences to the counter one-by-one in a loop.
|
|
||||||
|
|
||||||
To make use of this sequence to increment a counter and sum terms as we
|
|
||||||
go we need a function that will accept the sum, the counter, and the
|
|
||||||
next term to add, and that adds the term to the counter and a copy of
|
|
||||||
the counter to the running sum. This function will do that:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
PE1.1 == + [+] dupdip
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('PE1.1 + [+] dupdip')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('0 0 3 PE1.1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 0 0 3 PE1.1
|
|
||||||
0 • 0 3 PE1.1
|
|
||||||
0 0 • 3 PE1.1
|
|
||||||
0 0 3 • PE1.1
|
|
||||||
0 0 3 • + [+] dupdip
|
|
||||||
0 3 • [+] dupdip
|
|
||||||
0 3 [+] • dupdip
|
|
||||||
0 3 • + 3
|
|
||||||
3 • 3
|
|
||||||
3 3 •
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('0 0 [3 2 1 3 1 2 3] [PE1.1] step')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 0 0 [3 2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 • 0 [3 2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 0 • [3 2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 0 [3 2 1 3 1 2 3] • [PE1.1] step
|
|
||||||
0 0 [3 2 1 3 1 2 3] [PE1.1] • step
|
|
||||||
0 0 3 [PE1.1] • i [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 0 3 • PE1.1 [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 0 3 • + [+] dupdip [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 3 • [+] dupdip [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 3 [+] • dupdip [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
0 3 • + 3 [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
3 • 3 [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
3 3 • [2 1 3 1 2 3] [PE1.1] step
|
|
||||||
3 3 [2 1 3 1 2 3] • [PE1.1] step
|
|
||||||
3 3 [2 1 3 1 2 3] [PE1.1] • step
|
|
||||||
3 3 2 [PE1.1] • i [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 3 2 • PE1.1 [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 3 2 • + [+] dupdip [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 5 • [+] dupdip [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 5 [+] • dupdip [1 3 1 2 3] [PE1.1] step
|
|
||||||
3 5 • + 5 [1 3 1 2 3] [PE1.1] step
|
|
||||||
8 • 5 [1 3 1 2 3] [PE1.1] step
|
|
||||||
8 5 • [1 3 1 2 3] [PE1.1] step
|
|
||||||
8 5 [1 3 1 2 3] • [PE1.1] step
|
|
||||||
8 5 [1 3 1 2 3] [PE1.1] • step
|
|
||||||
8 5 1 [PE1.1] • i [3 1 2 3] [PE1.1] step
|
|
||||||
8 5 1 • PE1.1 [3 1 2 3] [PE1.1] step
|
|
||||||
8 5 1 • + [+] dupdip [3 1 2 3] [PE1.1] step
|
|
||||||
8 6 • [+] dupdip [3 1 2 3] [PE1.1] step
|
|
||||||
8 6 [+] • dupdip [3 1 2 3] [PE1.1] step
|
|
||||||
8 6 • + 6 [3 1 2 3] [PE1.1] step
|
|
||||||
14 • 6 [3 1 2 3] [PE1.1] step
|
|
||||||
14 6 • [3 1 2 3] [PE1.1] step
|
|
||||||
14 6 [3 1 2 3] • [PE1.1] step
|
|
||||||
14 6 [3 1 2 3] [PE1.1] • step
|
|
||||||
14 6 3 [PE1.1] • i [1 2 3] [PE1.1] step
|
|
||||||
14 6 3 • PE1.1 [1 2 3] [PE1.1] step
|
|
||||||
14 6 3 • + [+] dupdip [1 2 3] [PE1.1] step
|
|
||||||
14 9 • [+] dupdip [1 2 3] [PE1.1] step
|
|
||||||
14 9 [+] • dupdip [1 2 3] [PE1.1] step
|
|
||||||
14 9 • + 9 [1 2 3] [PE1.1] step
|
|
||||||
23 • 9 [1 2 3] [PE1.1] step
|
|
||||||
23 9 • [1 2 3] [PE1.1] step
|
|
||||||
23 9 [1 2 3] • [PE1.1] step
|
|
||||||
23 9 [1 2 3] [PE1.1] • step
|
|
||||||
23 9 1 [PE1.1] • i [2 3] [PE1.1] step
|
|
||||||
23 9 1 • PE1.1 [2 3] [PE1.1] step
|
|
||||||
23 9 1 • + [+] dupdip [2 3] [PE1.1] step
|
|
||||||
23 10 • [+] dupdip [2 3] [PE1.1] step
|
|
||||||
23 10 [+] • dupdip [2 3] [PE1.1] step
|
|
||||||
23 10 • + 10 [2 3] [PE1.1] step
|
|
||||||
33 • 10 [2 3] [PE1.1] step
|
|
||||||
33 10 • [2 3] [PE1.1] step
|
|
||||||
33 10 [2 3] • [PE1.1] step
|
|
||||||
33 10 [2 3] [PE1.1] • step
|
|
||||||
33 10 2 [PE1.1] • i [3] [PE1.1] step
|
|
||||||
33 10 2 • PE1.1 [3] [PE1.1] step
|
|
||||||
33 10 2 • + [+] dupdip [3] [PE1.1] step
|
|
||||||
33 12 • [+] dupdip [3] [PE1.1] step
|
|
||||||
33 12 [+] • dupdip [3] [PE1.1] step
|
|
||||||
33 12 • + 12 [3] [PE1.1] step
|
|
||||||
45 • 12 [3] [PE1.1] step
|
|
||||||
45 12 • [3] [PE1.1] step
|
|
||||||
45 12 [3] • [PE1.1] step
|
|
||||||
45 12 [3] [PE1.1] • step
|
|
||||||
45 12 3 [PE1.1] • i
|
|
||||||
45 12 3 • PE1.1
|
|
||||||
45 12 3 • + [+] dupdip
|
|
||||||
45 15 • [+] dupdip
|
|
||||||
45 15 [+] • dupdip
|
|
||||||
45 15 • + 15
|
|
||||||
60 • 15
|
|
||||||
60 15 •
|
|
||||||
|
|
||||||
|
|
||||||
So one ``step`` through all seven terms brings the counter to 15 and the
|
|
||||||
total to 60.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
1000 / 15
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
66.66666666666667
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
66 * 15
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
990
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
1000 - 990
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
10
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
We only want the terms *less than* 1000.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
999 - 990
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
9
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
That means we want to run the full list of numbers sixty-six times to
|
|
||||||
get to 990 and then the first four numbers 3 2 1 3 to get to 999.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('PE1 0 0 66 [[3 2 1 3 1 2 3] [PE1.1] step] times [3 2 1 3] [PE1.1] step pop')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('PE1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
This form uses no extra storage and produces no unused summands. It's
|
|
||||||
good but there's one more trick we can apply. The list of seven terms
|
|
||||||
takes up at least seven bytes. But notice that all of the terms are less
|
|
||||||
than four, and so each can fit in just two bits. We could store all
|
|
||||||
seven terms in just fourteen bits and use masking and shifts to pick out
|
|
||||||
each term as we go. This will use less space and save time loading whole
|
|
||||||
integer terms from the list.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3
|
|
||||||
0b 11 10 01 11 01 10 11 == 14811
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
0b11100111011011
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
14811
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('PE1.2 [3 & PE1.1] dupdip 2 >>')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('0 0 14811 PE1.2')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 0 0 14811 PE1.2
|
|
||||||
0 • 0 14811 PE1.2
|
|
||||||
0 0 • 14811 PE1.2
|
|
||||||
0 0 14811 • PE1.2
|
|
||||||
0 0 14811 • [3 & PE1.1] dupdip 2 >>
|
|
||||||
0 0 14811 [3 & PE1.1] • dupdip 2 >>
|
|
||||||
0 0 14811 • 3 & PE1.1 14811 2 >>
|
|
||||||
0 0 14811 3 • & PE1.1 14811 2 >>
|
|
||||||
0 0 3 • PE1.1 14811 2 >>
|
|
||||||
0 0 3 • + [+] dupdip 14811 2 >>
|
|
||||||
0 3 • [+] dupdip 14811 2 >>
|
|
||||||
0 3 [+] • dupdip 14811 2 >>
|
|
||||||
0 3 • + 3 14811 2 >>
|
|
||||||
3 • 3 14811 2 >>
|
|
||||||
3 3 • 14811 2 >>
|
|
||||||
3 3 14811 • 2 >>
|
|
||||||
3 3 14811 2 • >>
|
|
||||||
3 3 3702 •
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('3 3 3702 PE1.2')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 3 3 3702 PE1.2
|
|
||||||
3 • 3 3702 PE1.2
|
|
||||||
3 3 • 3702 PE1.2
|
|
||||||
3 3 3702 • PE1.2
|
|
||||||
3 3 3702 • [3 & PE1.1] dupdip 2 >>
|
|
||||||
3 3 3702 [3 & PE1.1] • dupdip 2 >>
|
|
||||||
3 3 3702 • 3 & PE1.1 3702 2 >>
|
|
||||||
3 3 3702 3 • & PE1.1 3702 2 >>
|
|
||||||
3 3 2 • PE1.1 3702 2 >>
|
|
||||||
3 3 2 • + [+] dupdip 3702 2 >>
|
|
||||||
3 5 • [+] dupdip 3702 2 >>
|
|
||||||
3 5 [+] • dupdip 3702 2 >>
|
|
||||||
3 5 • + 5 3702 2 >>
|
|
||||||
8 • 5 3702 2 >>
|
|
||||||
8 5 • 3702 2 >>
|
|
||||||
8 5 3702 • 2 >>
|
|
||||||
8 5 3702 2 • >>
|
|
||||||
8 5 925 •
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('0 0 14811 7 [PE1.2] times pop')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 0 0 14811 7 [PE1.2] times pop
|
|
||||||
0 • 0 14811 7 [PE1.2] times pop
|
|
||||||
0 0 • 14811 7 [PE1.2] times pop
|
|
||||||
0 0 14811 • 7 [PE1.2] times pop
|
|
||||||
0 0 14811 7 • [PE1.2] times pop
|
|
||||||
0 0 14811 7 [PE1.2] • times pop
|
|
||||||
0 0 14811 • PE1.2 6 [PE1.2] times pop
|
|
||||||
0 0 14811 • [3 & PE1.1] dupdip 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 14811 [3 & PE1.1] • dupdip 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 14811 • 3 & PE1.1 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 14811 3 • & PE1.1 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 3 • PE1.1 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 0 3 • + [+] dupdip 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 3 • [+] dupdip 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 3 [+] • dupdip 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
0 3 • + 3 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
3 • 3 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
3 3 • 14811 2 >> 6 [PE1.2] times pop
|
|
||||||
3 3 14811 • 2 >> 6 [PE1.2] times pop
|
|
||||||
3 3 14811 2 • >> 6 [PE1.2] times pop
|
|
||||||
3 3 3702 • 6 [PE1.2] times pop
|
|
||||||
3 3 3702 6 • [PE1.2] times pop
|
|
||||||
3 3 3702 6 [PE1.2] • times pop
|
|
||||||
3 3 3702 • PE1.2 5 [PE1.2] times pop
|
|
||||||
3 3 3702 • [3 & PE1.1] dupdip 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 3702 [3 & PE1.1] • dupdip 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 3702 • 3 & PE1.1 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 3702 3 • & PE1.1 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 2 • PE1.1 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 3 2 • + [+] dupdip 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 5 • [+] dupdip 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 5 [+] • dupdip 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
3 5 • + 5 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
8 • 5 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
8 5 • 3702 2 >> 5 [PE1.2] times pop
|
|
||||||
8 5 3702 • 2 >> 5 [PE1.2] times pop
|
|
||||||
8 5 3702 2 • >> 5 [PE1.2] times pop
|
|
||||||
8 5 925 • 5 [PE1.2] times pop
|
|
||||||
8 5 925 5 • [PE1.2] times pop
|
|
||||||
8 5 925 5 [PE1.2] • times pop
|
|
||||||
8 5 925 • PE1.2 4 [PE1.2] times pop
|
|
||||||
8 5 925 • [3 & PE1.1] dupdip 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 925 [3 & PE1.1] • dupdip 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 925 • 3 & PE1.1 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 925 3 • & PE1.1 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 1 • PE1.1 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 5 1 • + [+] dupdip 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 6 • [+] dupdip 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 6 [+] • dupdip 925 2 >> 4 [PE1.2] times pop
|
|
||||||
8 6 • + 6 925 2 >> 4 [PE1.2] times pop
|
|
||||||
14 • 6 925 2 >> 4 [PE1.2] times pop
|
|
||||||
14 6 • 925 2 >> 4 [PE1.2] times pop
|
|
||||||
14 6 925 • 2 >> 4 [PE1.2] times pop
|
|
||||||
14 6 925 2 • >> 4 [PE1.2] times pop
|
|
||||||
14 6 231 • 4 [PE1.2] times pop
|
|
||||||
14 6 231 4 • [PE1.2] times pop
|
|
||||||
14 6 231 4 [PE1.2] • times pop
|
|
||||||
14 6 231 • PE1.2 3 [PE1.2] times pop
|
|
||||||
14 6 231 • [3 & PE1.1] dupdip 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 231 [3 & PE1.1] • dupdip 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 231 • 3 & PE1.1 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 231 3 • & PE1.1 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 3 • PE1.1 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 6 3 • + [+] dupdip 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 9 • [+] dupdip 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 9 [+] • dupdip 231 2 >> 3 [PE1.2] times pop
|
|
||||||
14 9 • + 9 231 2 >> 3 [PE1.2] times pop
|
|
||||||
23 • 9 231 2 >> 3 [PE1.2] times pop
|
|
||||||
23 9 • 231 2 >> 3 [PE1.2] times pop
|
|
||||||
23 9 231 • 2 >> 3 [PE1.2] times pop
|
|
||||||
23 9 231 2 • >> 3 [PE1.2] times pop
|
|
||||||
23 9 57 • 3 [PE1.2] times pop
|
|
||||||
23 9 57 3 • [PE1.2] times pop
|
|
||||||
23 9 57 3 [PE1.2] • times pop
|
|
||||||
23 9 57 • PE1.2 2 [PE1.2] times pop
|
|
||||||
23 9 57 • [3 & PE1.1] dupdip 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 57 [3 & PE1.1] • dupdip 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 57 • 3 & PE1.1 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 57 3 • & PE1.1 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 1 • PE1.1 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 9 1 • + [+] dupdip 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 10 • [+] dupdip 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 10 [+] • dupdip 57 2 >> 2 [PE1.2] times pop
|
|
||||||
23 10 • + 10 57 2 >> 2 [PE1.2] times pop
|
|
||||||
33 • 10 57 2 >> 2 [PE1.2] times pop
|
|
||||||
33 10 • 57 2 >> 2 [PE1.2] times pop
|
|
||||||
33 10 57 • 2 >> 2 [PE1.2] times pop
|
|
||||||
33 10 57 2 • >> 2 [PE1.2] times pop
|
|
||||||
33 10 14 • 2 [PE1.2] times pop
|
|
||||||
33 10 14 2 • [PE1.2] times pop
|
|
||||||
33 10 14 2 [PE1.2] • times pop
|
|
||||||
33 10 14 • PE1.2 1 [PE1.2] times pop
|
|
||||||
33 10 14 • [3 & PE1.1] dupdip 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 14 [3 & PE1.1] • dupdip 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 14 • 3 & PE1.1 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 14 3 • & PE1.1 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 2 • PE1.1 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 10 2 • + [+] dupdip 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 12 • [+] dupdip 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 12 [+] • dupdip 14 2 >> 1 [PE1.2] times pop
|
|
||||||
33 12 • + 12 14 2 >> 1 [PE1.2] times pop
|
|
||||||
45 • 12 14 2 >> 1 [PE1.2] times pop
|
|
||||||
45 12 • 14 2 >> 1 [PE1.2] times pop
|
|
||||||
45 12 14 • 2 >> 1 [PE1.2] times pop
|
|
||||||
45 12 14 2 • >> 1 [PE1.2] times pop
|
|
||||||
45 12 3 • 1 [PE1.2] times pop
|
|
||||||
45 12 3 1 • [PE1.2] times pop
|
|
||||||
45 12 3 1 [PE1.2] • times pop
|
|
||||||
45 12 3 • PE1.2 pop
|
|
||||||
45 12 3 • [3 & PE1.1] dupdip 2 >> pop
|
|
||||||
45 12 3 [3 & PE1.1] • dupdip 2 >> pop
|
|
||||||
45 12 3 • 3 & PE1.1 3 2 >> pop
|
|
||||||
45 12 3 3 • & PE1.1 3 2 >> pop
|
|
||||||
45 12 3 • PE1.1 3 2 >> pop
|
|
||||||
45 12 3 • + [+] dupdip 3 2 >> pop
|
|
||||||
45 15 • [+] dupdip 3 2 >> pop
|
|
||||||
45 15 [+] • dupdip 3 2 >> pop
|
|
||||||
45 15 • + 15 3 2 >> pop
|
|
||||||
60 • 15 3 2 >> pop
|
|
||||||
60 15 • 3 2 >> pop
|
|
||||||
60 15 3 • 2 >> pop
|
|
||||||
60 15 3 2 • >> pop
|
|
||||||
60 15 0 • pop
|
|
||||||
60 15 •
|
|
||||||
|
|
||||||
|
|
||||||
And so we have at last:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('PE1 0 0 66 [14811 7 [PE1.2] times pop] times 14811 4 [PE1.2] times popop')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('PE1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
Let's refactor.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
14811 7 [PE1.2] times pop
|
|
||||||
14811 4 [PE1.2] times pop
|
|
||||||
14811 n [PE1.2] times pop
|
|
||||||
n 14811 swap [PE1.2] times pop
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('PE1.3 14811 swap [PE1.2] times pop')
|
|
||||||
|
|
||||||
Now we can simplify the definition above:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('PE1 0 0 66 [7 PE1.3] times 4 PE1.3 pop')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('PE1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
Here's our joy program all in one place. It doesn't make so much sense,
|
|
||||||
but if you have read through the above description of how it was derived
|
|
||||||
I hope it's clear.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
PE1.1 == + [+] dupdip
|
|
||||||
PE1.2 == [3 & PE1.1] dupdip 2 >>
|
|
||||||
PE1.3 == 14811 swap [PE1.2] times pop
|
|
||||||
PE1 == 0 0 66 [7 PE1.3] times 4 PE1.3 pop
|
|
||||||
|
|
||||||
Generator Version
|
|
||||||
=================
|
|
||||||
|
|
||||||
It's a little clunky iterating sixty-six times though the seven numbers
|
|
||||||
then four more. In the *Generator Programs* notebook we derive a
|
|
||||||
generator that can be repeatedly driven by the ``x`` combinator to
|
|
||||||
produce a stream of the seven numbers repeating over and over again.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('PE1.terms [0 swap [dup [pop 14811] [] branch [3 &] dupdip 2 >>] dip rest cons]')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('PE1.terms 21 [x] times')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 [0 swap [dup [pop 14811] [] branch [3 &] dupdip 2 >>] dip rest cons]
|
|
||||||
|
|
||||||
|
|
||||||
We know from above that we need sixty-six times seven then four more
|
|
||||||
terms to reach up to but not over one thousand.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('7 66 * 4 +')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
466
|
|
||||||
|
|
||||||
|
|
||||||
Here they are...
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('PE1.terms 466 [x] times pop')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3
|
|
||||||
|
|
||||||
|
|
||||||
...and they do sum to 999.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[PE1.terms 466 [x] times pop] run sum')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
999
|
|
||||||
|
|
||||||
|
|
||||||
Now we can use ``PE1.1`` to accumulate the terms as we go, and then
|
|
||||||
``pop`` the generator and the counter from the stack when we're done,
|
|
||||||
leaving just the sum.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('0 0 PE1.terms 466 [x [PE1.1] dip] times popop')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
A little further analysis renders iteration unnecessary.
|
|
||||||
========================================================
|
|
||||||
|
|
||||||
Consider finding the sum of the positive integers less than or equal to
|
|
||||||
ten.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[10 9 8 7 6 5 4 3 2 1] sum')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
55
|
|
||||||
|
|
||||||
|
|
||||||
Instead of summing them,
|
|
||||||
`observe <https://en.wikipedia.org/wiki/File:Animated_proof_for_the_formula_giving_the_sum_of_the_first_integers_1%2B2%2B...%2Bn.gif>`__:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
10 9 8 7 6
|
|
||||||
+ 1 2 3 4 5
|
|
||||||
---- -- -- -- --
|
|
||||||
11 11 11 11 11
|
|
||||||
|
|
||||||
11 * 5 = 55
|
|
||||||
|
|
||||||
From the above example we can deduce that the sum of the first N
|
|
||||||
positive integers is:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
(N + 1) * N / 2
|
|
||||||
|
|
||||||
(The formula also works for odd values of N, I'll leave that to you if
|
|
||||||
you want to work it out or you can take my word for it.)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('F dup ++ * 2 floordiv')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('10 F')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 10 F
|
|
||||||
10 • F
|
|
||||||
10 • dup ++ * 2 floordiv
|
|
||||||
10 10 • ++ * 2 floordiv
|
|
||||||
10 11 • * 2 floordiv
|
|
||||||
110 • 2 floordiv
|
|
||||||
110 2 • floordiv
|
|
||||||
55 •
|
|
||||||
|
|
||||||
|
|
||||||
Generalizing to Blocks of Terms
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
We can apply the same reasoning to the PE1 problem.
|
|
||||||
|
|
||||||
Between 0 and 990 inclusive there are sixty-six "blocks" of seven terms
|
|
||||||
each, starting with:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[3 5 6 9 10 12 15]
|
|
||||||
|
|
||||||
And ending with:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[978 980 981 984 985 987 990]
|
|
||||||
|
|
||||||
If we reverse one of these two blocks and sum pairs...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[[978 15] [980 12] [981 10] [984 9] [985 6] [987 5] [990 3]]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip [sum] map')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[993 992 991 993 991 992 993]
|
|
||||||
|
|
||||||
|
|
||||||
(Interesting that the sequence of seven numbers appears again in the
|
|
||||||
rightmost digit of each term.)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[ 3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip [sum] map sum')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
6945
|
|
||||||
|
|
||||||
|
|
||||||
Since there are sixty-six blocks and we are pairing them up, there must
|
|
||||||
be thirty-three pairs, each of which sums to 6945. We also have these
|
|
||||||
additional unpaired terms between 990 and 1000:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
993 995 996 999
|
|
||||||
|
|
||||||
So we can give the "sum of all the multiples of 3 or 5 below 1000" like
|
|
||||||
so:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('6945 33 * [993 995 996 999] cons sum')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
It's worth noting, I think, that this same reasoning holds for any two
|
|
||||||
numbers :math:`n` and :math:`m` the multiples of which we hope to sum.
|
|
||||||
The multiples would have a cycle of differences of length :math:`k` and
|
|
||||||
so we could compute the sum of :math:`Nk` multiples as above.
|
|
||||||
|
|
||||||
The sequence of differences will always be a palidrome. Consider an
|
|
||||||
interval spanning the least common multiple of :math:`n` and :math:`m`:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
| | | | | | | |
|
|
||||||
| | | | |
|
|
||||||
|
|
||||||
Here we have 4 and 7, and you can read off the sequence of differences
|
|
||||||
directly from the diagram: 4 3 1 4 2 2 4 1 3 4.
|
|
||||||
|
|
||||||
Geometrically, the actual values of :math:`n` and :math:`m` and their
|
|
||||||
*lcm* don't matter, the pattern they make will always be symmetrical
|
|
||||||
around its midpoint. The same reasoning holds for multiples of more than
|
|
||||||
two numbers.
|
|
||||||
|
|
||||||
The Simplest Program
|
|
||||||
====================
|
|
||||||
|
|
||||||
Of course, the simplest joy program for the first Project Euler problem
|
|
||||||
is just:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
PE1 == 233168
|
|
||||||
|
|
||||||
Fin.
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,232 +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.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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]]
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('pair_up dup uncons swap unit concat zip')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3] pair_up')
|
|
||||||
```
|
|
||||||
|
|
||||||
[[2 1] [3 2] [1 3]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 2 3] pair_up')
|
|
||||||
```
|
|
||||||
|
|
||||||
[[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
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('total_matches 0 swap [i [=] [pop +] [popop] ifte] step')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3] pair_up total_matches')
|
|
||||||
```
|
|
||||||
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 2 3] pair_up total_matches')
|
|
||||||
```
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
|
|
||||||
Now we can define our main program and evaluate it on the examples.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('AoC2017.1 pair_up total_matches')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 1 2 2] AoC2017.1')
|
|
||||||
```
|
|
||||||
|
|
||||||
3
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 1 1 1] AoC2017.1')
|
|
||||||
```
|
|
||||||
|
|
||||||
4
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3 4] AoC2017.1')
|
|
||||||
```
|
|
||||||
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[9 1 2 1 2 1 2 9] AoC2017.1')
|
|
||||||
```
|
|
||||||
|
|
||||||
9
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[9 1 2 1 2 1 2 9] AoC2017.1')
|
|
||||||
```
|
|
||||||
|
|
||||||
9
|
|
||||||
|
|
||||||
|
|
||||||
pair_up == dup uncons swap unit concat zip
|
|
||||||
total_matches == 0 swap [i [=] [pop +] [popop] ifte] step
|
|
||||||
|
|
||||||
AoC2017.1 == pair_up total_matches
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Now the paired digit is "halfway" round.
|
|
||||||
|
|
||||||
[a b c d] dup size 2 / [drop] [take reverse] cleave concat zip
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3 4] dup size 2 / [drop] [take reverse] cleave concat zip')
|
|
||||||
```
|
|
||||||
|
|
||||||
[[3 1] [4 2] [1 3] [2 4]]
|
|
||||||
|
|
||||||
|
|
||||||
I realized that each pair is repeated...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3 4] dup size 2 / [drop] [take reverse] cleave zip')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 2 3 4] [[1 3] [2 4]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('AoC2017.1.extra dup size 2 / [drop] [take reverse] cleave zip swap pop total_matches 2 *')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 1 2] AoC2017.1.extra')
|
|
||||||
```
|
|
||||||
|
|
||||||
6
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 2 1] AoC2017.1.extra')
|
|
||||||
```
|
|
||||||
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3 4 2 5] AoC2017.1.extra')
|
|
||||||
```
|
|
||||||
|
|
||||||
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,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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('pair_up dup uncons swap unit concat zip')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3] pair_up')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[[2 1] [3 2] [1 3]]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('total_matches 0 swap [i [=] [pop +] [popop] ifte] step')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3] pair_up total_matches')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('AoC2017.1 pair_up total_matches')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 1 2 2] AoC2017.1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 1 1 1] AoC2017.1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
4
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3 4] AoC2017.1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[9 1 2 1 2 1 2 9] AoC2017.1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
9
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3 4] dup size 2 / [drop] [take reverse] cleave zip')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 2 3 4] [[1 3] [2 4]]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('AoC2017.1.extra dup size 2 / [drop] [take reverse] cleave zip swap pop total_matches 2 *')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 1 2] AoC2017.1.extra')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
6
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 2 1] AoC2017.1.extra')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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 *
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,360 +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.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('maxmin [max] [min] cleave')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3] maxmin')
|
|
||||||
```
|
|
||||||
|
|
||||||
3 1
|
|
||||||
|
|
||||||
|
|
||||||
Then `F` just does that then subtracts the min from the max:
|
|
||||||
|
|
||||||
F == maxmin -
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('AoC2017.2 [maxmin - +] step_zero')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('''
|
|
||||||
|
|
||||||
[[5 1 9 5]
|
|
||||||
[7 5 3]
|
|
||||||
[2 4 6 8]] AoC2017.2
|
|
||||||
|
|
||||||
''')
|
|
||||||
```
|
|
||||||
|
|
||||||
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?
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[5 9 2 8] sort reverse')
|
|
||||||
```
|
|
||||||
|
|
||||||
[9 8 5 2]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[9 8 5 2] uncons [swap [divmod] cons] dupdip')
|
|
||||||
```
|
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[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] [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
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[8 5 3 2] 9 [swap] [% not] [cons] app2 popdd')
|
|
||||||
```
|
|
||||||
|
|
||||||
[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`.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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] tailrec
|
|
||||||
|
|
||||||
n [...] p [0 >] [roll> popop] [roll< popop uncons [G] nullary] tailrec
|
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('find-result [0 >] [roll> popop] [roll< popop uncons [G] nullary] tailrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[11 9 8 7 3 2] 0 tuck find-result')
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('prep-row sort reverse 0 tuck')
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can define our program.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('AoC20017.2.extra [prep-row find-result +] step_zero')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('''
|
|
||||||
|
|
||||||
[[5 9 2 8]
|
|
||||||
[9 4 7 3]
|
|
||||||
[3 8 6 5]] AoC20017.2.extra
|
|
||||||
|
|
||||||
''')
|
|
||||||
```
|
|
||||||
|
|
||||||
9.0
|
|
||||||
|
|
||||||
|
|
@ -1,431 +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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('maxmin [max] [min] cleave')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('AoC2017.2 [maxmin - +] step_zero')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
J('[5 9 2 8] sort reverse')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[9 8 5 2]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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] tailrec
|
|
||||||
|
|
||||||
n [...] p [0 >] [roll> popop] [roll< popop uncons [G] nullary] tailrec
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('find-result [0 >] [roll> popop] [roll< popop uncons [G] nullary] tailrec')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('prep-row sort reverse 0 tuck')
|
|
||||||
|
|
||||||
Now we can define our program.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('AoC20017.2.extra [prep-row find-result +] step_zero')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('''
|
|
||||||
|
|
||||||
[[5 9 2 8]
|
|
||||||
[9 4 7 3]
|
|
||||||
[3 8 6 5]] AoC20017.2.extra
|
|
||||||
|
|
||||||
''')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
9.0
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,846 +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" $k$:
|
|
||||||
|
|
||||||
$2k - 1, 2k - 2, ..., k, k + 1, k + 2, ..., 2k$
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
$k + (k - 1), k + (k - 2), ..., k, k + 1, k + 2, ..., k + k$
|
|
||||||
|
|
||||||
This shows that the series consists at each place of $k$ plus some number that begins at $k - 1$, decreases to zero, then increases to $k$. Each row has $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 $k - 1$.
|
|
||||||
|
|
||||||
Let's look at an example, with $k = 4$:
|
|
||||||
|
|
||||||
0 1 2 3 4 5 6 7
|
|
||||||
7 6 5 4 5 6 7 8
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
k = 4
|
|
||||||
```
|
|
||||||
|
|
||||||
Subtract $k$ from the index and take the absolute value:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for n in range(2 * k):
|
|
||||||
print(abs(n - k),)
|
|
||||||
```
|
|
||||||
|
|
||||||
4
|
|
||||||
3
|
|
||||||
2
|
|
||||||
1
|
|
||||||
0
|
|
||||||
1
|
|
||||||
2
|
|
||||||
3
|
|
||||||
|
|
||||||
|
|
||||||
Not quite. Subtract $k - 1$ from the index and take the absolute value:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for n in range(2 * k):
|
|
||||||
print(abs(n - (k - 1)), end=' ')
|
|
||||||
```
|
|
||||||
|
|
||||||
3 2 1 0 1 2 3 4
|
|
||||||
|
|
||||||
Great, now add $k$...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for n in range(2 * k):
|
|
||||||
print(abs(n - (k - 1)) + k, end=' ')
|
|
||||||
```
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def row_value(k, i):
|
|
||||||
i %= (2 * k) # wrap the index at the row boundary.
|
|
||||||
return abs(i - (k - 1)) + k
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
k = 5
|
|
||||||
for i in range(2 * k):
|
|
||||||
print(row_value(k, i), end=' ')
|
|
||||||
```
|
|
||||||
|
|
||||||
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 $2k$ places and there are $4$ per rank each rank contains $8k$ places. Counting the initial square we have:
|
|
||||||
|
|
||||||
$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:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for n in range(2, 51):
|
|
||||||
print(n, rank_and_offset(n))
|
|
||||||
```
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for n in range(2, 51):
|
|
||||||
k, i = rank_and_offset(n)
|
|
||||||
print(n, row_value(k, i))
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
aoc20173(23)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
aoc20173(23000)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
105
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
aoc20173(23000000000000)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from sympy import floor, lambdify, solve, symbols
|
|
||||||
from sympy import init_printing
|
|
||||||
init_printing()
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
k = symbols('k')
|
|
||||||
```
|
|
||||||
|
|
||||||
Since
|
|
||||||
|
|
||||||
$1 + 2 + 3 + ... + N = \frac{N(N + 1)}{2}$
|
|
||||||
|
|
||||||
and
|
|
||||||
|
|
||||||
$\sum_{n=1}^k 8n = 8(\sum_{n=1}^k n) = 8\frac{k(k + 1)}{2}$
|
|
||||||
|
|
||||||
We want:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
E = 2 + 8 * k * (k + 1) / 2 # For the reason for adding 2 see above.
|
|
||||||
|
|
||||||
E
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 4 k \left(k + 1\right) + 2$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
We can write a function to solve for $k$ given some $n$...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def rank_of(n):
|
|
||||||
return floor(max(solve(E - n, k))) + 1
|
|
||||||
```
|
|
||||||
|
|
||||||
First `solve()` for $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 $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 $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:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for n in (9, 10, 25, 26, 49, 50):
|
|
||||||
print(n, rank_of(n))
|
|
||||||
```
|
|
||||||
|
|
||||||
9 1
|
|
||||||
10 2
|
|
||||||
25 2
|
|
||||||
26 3
|
|
||||||
49 3
|
|
||||||
50 4
|
|
||||||
|
|
||||||
|
|
||||||
And it runs much faster (at least for large numbers):
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
%time rank_of(23000000000000) # Compare runtime with rank_and_offset()!
|
|
||||||
```
|
|
||||||
|
|
||||||
CPU times: user 27.8 ms, sys: 5 µs, total: 27.8 ms
|
|
||||||
Wall time: 27.3 ms
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 2397916$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
%time rank_and_offset(23000000000000)
|
|
||||||
```
|
|
||||||
|
|
||||||
CPU times: user 216 ms, sys: 89 µs, total: 216 ms
|
|
||||||
Wall time: 215 ms
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle \left( 2397916, \ 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 $y$ (instead of a value) and ask it to solve for $k$.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
y = symbols('y')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
g, f = solve(E - y, k)
|
|
||||||
```
|
|
||||||
|
|
||||||
The equation is quadratic so there are two roots, we are interested in the greater one...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
g
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle - \frac{\sqrt{y - 1}}{2} - \frac{1}{2}$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
f
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle \frac{\sqrt{y - 1}}{2} - \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.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
floor(f) + 1
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle \left\lfloor{\frac{\sqrt{y - 1}}{2} - \frac{1}{2}}\right\rfloor + 1$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
F = lambdify(y, floor(f) + 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for n in (9, 10, 25, 26, 49, 50):
|
|
||||||
print(n, int(F(n)))
|
|
||||||
```
|
|
||||||
|
|
||||||
9 1
|
|
||||||
10 2
|
|
||||||
25 2
|
|
||||||
26 3
|
|
||||||
49 3
|
|
||||||
50 4
|
|
||||||
|
|
||||||
|
|
||||||
It's pretty fast.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
%time int(F(23000000000000)) # The clear winner.
|
|
||||||
```
|
|
||||||
|
|
||||||
CPU times: user 60 µs, sys: 4 µs, total: 64 µs
|
|
||||||
Wall time: 67 µs
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 2397916$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Knowing the equation we could write our own function manually, but the speed is no better.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from math import floor as mfloor, sqrt
|
|
||||||
|
|
||||||
def mrank_of(n):
|
|
||||||
return int(mfloor(sqrt(n - 1) / 2 - 0.5) + 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
%time mrank_of(23000000000000)
|
|
||||||
```
|
|
||||||
|
|
||||||
CPU times: user 7 µs, sys: 1 µs, total: 8 µs
|
|
||||||
Wall time: 10 µs
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 2397916$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Given $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.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def offset_of(n, k):
|
|
||||||
return (n - 2 + 4 * k * (k - 1)) % (2 * k)
|
|
||||||
```
|
|
||||||
|
|
||||||
(Note the sneaky way the sign changes from $k(k + 1)$ to $k(k - 1)$. This is because we want to subract the $(k - 1)$th rank's total places (its own and those of lesser rank) from our $n$ of rank $k$. Substituting $k - 1$ for $k$ in $k(k + 1)$ gives $(k - 1)(k - 1 + 1)$, which of course simplifies to $k(k - 1)$.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
offset_of(23000000000000, 2397916)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 223606$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
So, we can compute the rank, then the offset, then the row value.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
aoc20173(23)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 2$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
aoc20173(23000)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 105$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
aoc20173(23000000000000)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 4572225$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
%time aoc20173(23000000000000000000000000) # Fast for large values.
|
|
||||||
```
|
|
||||||
|
|
||||||
CPU times: user 22 µs, sys: 2 µs, total: 24 µs
|
|
||||||
Wall time: 26.7 µs
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$\displaystyle 2690062495969$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# A Joy Version
|
|
||||||
At this point I feel confident that I can implement a concise version of this code in Joy. ;-)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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 ++
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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 %
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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|
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('aoc2017.3 dup rank_of [offset_of] dupdip swap row_value')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('23 aoc2017.3')
|
|
||||||
```
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('23000 aoc2017.3')
|
|
||||||
```
|
|
||||||
|
|
||||||
105
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('23000000000000 aoc2017.3')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 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,976 +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:: ipython3
|
|
||||||
|
|
||||||
k = 4
|
|
||||||
|
|
||||||
Subtract :math:`k` from the index and take the absolute value:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
for n in range(2 * k):
|
|
||||||
print(abs(n - (k - 1)), end=' ')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3 2 1 0 1 2 3 4
|
|
||||||
|
|
||||||
Great, now add :math:`k`...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
for n in range(2 * k):
|
|
||||||
print(abs(n - (k - 1)) + k, end=' ')
|
|
||||||
|
|
||||||
|
|
||||||
.. 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:: ipython3
|
|
||||||
|
|
||||||
def row_value(k, i):
|
|
||||||
i %= (2 * k) # wrap the index at the row boundary.
|
|
||||||
return abs(i - (k - 1)) + k
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
k = 5
|
|
||||||
for i in range(2 * k):
|
|
||||||
print(row_value(k, i), end=' ')
|
|
||||||
|
|
||||||
|
|
||||||
.. 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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
aoc20173(23)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
aoc20173(23000)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
105
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
from sympy import floor, lambdify, solve, symbols
|
|
||||||
from sympy import init_printing
|
|
||||||
init_printing()
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
E = 2 + 8 * k * (k + 1) / 2 # For the reason for adding 2 see above.
|
|
||||||
|
|
||||||
E
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 4 k \left(k + 1\right) + 2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
We can write a function to solve for :math:`k` given some :math:`n`...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
%time rank_of(23000000000000) # Compare runtime with rank_and_offset()!
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
CPU times: user 27.8 ms, sys: 5 µs, total: 27.8 ms
|
|
||||||
Wall time: 27.3 ms
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 2397916
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
%time rank_and_offset(23000000000000)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
CPU times: user 216 ms, sys: 89 µs, total: 216 ms
|
|
||||||
Wall time: 215 ms
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle \left( 2397916, \ 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:: ipython3
|
|
||||||
|
|
||||||
y = symbols('y')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
g, f = solve(E - y, k)
|
|
||||||
|
|
||||||
The equation is quadratic so there are two roots, we are interested in
|
|
||||||
the greater one...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
g
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle - \frac{\sqrt{y - 1}}{2} - \frac{1}{2}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
f
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle \frac{\sqrt{y - 1}}{2} - \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:: ipython3
|
|
||||||
|
|
||||||
floor(f) + 1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle \left\lfloor{\frac{\sqrt{y - 1}}{2} - \frac{1}{2}}\right\rfloor + 1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
F = lambdify(y, floor(f) + 1)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
%time int(F(23000000000000)) # The clear winner.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
CPU times: user 60 µs, sys: 4 µs, total: 64 µs
|
|
||||||
Wall time: 67 µs
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 2397916
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Knowing the equation we could write our own function manually, but the
|
|
||||||
speed is no better.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from math import floor as mfloor, sqrt
|
|
||||||
|
|
||||||
def mrank_of(n):
|
|
||||||
return int(mfloor(sqrt(n - 1) / 2 - 0.5) + 1)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
%time mrank_of(23000000000000)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
CPU times: user 7 µs, sys: 1 µs, total: 8 µs
|
|
||||||
Wall time: 10 µs
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
offset_of(23000000000000, 2397916)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 223606
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
So, we can compute the rank, then the offset, then the row value.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
aoc20173(23)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
aoc20173(23000)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 105
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
aoc20173(23000000000000)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 4572225
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
%time aoc20173(23000000000000000000000000) # Fast for large values.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
CPU times: user 22 µs, sys: 2 µs, total: 24 µs
|
|
||||||
Wall time: 26.7 µs
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
|
||||||
|
|
||||||
\displaystyle 2690062495969
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A Joy Version
|
|
||||||
=============
|
|
||||||
|
|
||||||
At this point I feel confident that I can implement a concise version of
|
|
||||||
this code in Joy. ;-)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('aoc2017.3 dup rank_of [offset_of] dupdip swap row_value')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('23 aoc2017.3')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('23000 aoc2017.3')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
105
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,69 +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?
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[step_zero] help')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
==== Help on step_zero ====
|
|
||||||
|
|
||||||
0 roll> step
|
|
||||||
|
|
||||||
---- end (step_zero)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
AoC2017.4 == [F +] step_zero
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('AoC2017.4 [[size] [unique size] cleave = +] step_zero')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('''
|
|
||||||
|
|
||||||
[[5 1 9 5]
|
|
||||||
[7 5 4 3]
|
|
||||||
[2 4 6 8]] AoC2017.4
|
|
||||||
|
|
||||||
''')
|
|
||||||
```
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
|
|
@ -1,82 +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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
J('[step_zero] help')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
==== Help on step_zero ====
|
|
||||||
|
|
||||||
0 roll> step
|
|
||||||
|
|
||||||
---- end (step_zero)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
AoC2017.4 == [F +] step_zero
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('AoC2017.4 [[size] [unique size] cleave = +] step_zero')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('''
|
|
||||||
|
|
||||||
[[5 1 9 5]
|
|
||||||
[7 5 4 3]
|
|
||||||
[2 4 6 8]] AoC2017.4
|
|
||||||
|
|
||||||
''')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,260 +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.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('3 [0 1 2 3 4 5] incr_at')
|
|
||||||
```
|
|
||||||
|
|
||||||
[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] tailrec
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('F [popop 5 >=] [roll< popop] [AoC2017.5.0] tailrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('0 0 [0 3 0 1 -3] F')
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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] tailrec
|
|
||||||
|
|
||||||
''', D)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 3 0 1 -3] AoC2017.5')
|
|
||||||
```
|
|
||||||
|
|
||||||
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,339 +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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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] tailrec
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('F [popop 5 >=] [roll< popop] [AoC2017.5.0] tailrec')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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] tailrec
|
|
||||||
|
|
||||||
''', D)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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.
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,266 +0,0 @@
|
||||||
# Advent of Code 2017
|
|
||||||
|
|
||||||
## December 6th
|
|
||||||
|
|
||||||
|
|
||||||
[0 2 7 0] dup max
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from notebook_preamble import D, J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 2 7 0] dup max')
|
|
||||||
```
|
|
||||||
|
|
||||||
[0 2 7 0] 7
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 2 7 0] 7 index_of')
|
|
||||||
```
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 2 7 0] 23 index_of')
|
|
||||||
```
|
|
||||||
|
|
||||||
-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...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 2 7 0] dup max [index_of] nullary distribute')
|
|
||||||
```
|
|
||||||
|
|
||||||
[2 4 1 2]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[2 4 1 2] dup max [index_of] nullary distribute')
|
|
||||||
```
|
|
||||||
|
|
||||||
[3 1 2 3]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[3 1 2 3] dup max [index_of] nullary distribute')
|
|
||||||
```
|
|
||||||
|
|
||||||
[0 2 3 4]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 2 3 4] dup max [index_of] nullary distribute')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 3 4 1]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 3 4 1] dup max [index_of] nullary distribute')
|
|
||||||
```
|
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('direco dip rest cons')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('G [direco] cons [swap] swoncat cons')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('make_distributor [dup dup max [index_of] nullary distribute] G')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 2 7 0] make_distributor 6 [x] times pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
[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] tailrec
|
|
||||||
|
|
||||||
(?)
|
|
||||||
|
|
||||||
[] [GEN] x [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec
|
|
||||||
[] [...] [GEN] [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec
|
|
||||||
[] [...] [GEN] pop index_of 0 >=
|
|
||||||
[] [...] index_of 0 >=
|
|
||||||
-1 0 >=
|
|
||||||
False
|
|
||||||
|
|
||||||
Base case
|
|
||||||
|
|
||||||
[] [...] [GEN] [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec
|
|
||||||
[] [...] [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] tailrec
|
|
||||||
[] [...] [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] tailrec
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('count_states [] swap x [pop index_of 0 >=] [popop size] [[swons] dip x] tailrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('AoC2017.6 make_distributor count_states')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 2 7 0] AoC2017.6')
|
|
||||||
```
|
|
||||||
|
|
||||||
5
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 1 1] AoC2017.6')
|
|
||||||
```
|
|
||||||
|
|
||||||
4
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[8 0 0 0 0 0] AoC2017.6')
|
|
||||||
```
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
@ -1,304 +0,0 @@
|
||||||
Advent of Code 2017
|
|
||||||
===================
|
|
||||||
|
|
||||||
December 6th
|
|
||||||
------------
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[0 2 7 0] dup max
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from notebook_preamble import D, J, V, define
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[0 2 7 0] dup max')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[0 2 7 0] 7
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
J('[0 2 7 0] 7 index_of')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
J('[0 2 7 0] dup max [index_of] nullary distribute')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[2 4 1 2]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[2 4 1 2] dup max [index_of] nullary distribute')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[3 1 2 3]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[3 1 2 3] dup max [index_of] nullary distribute')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[0 2 3 4]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[0 2 3 4] dup max [index_of] nullary distribute')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 3 4 1]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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:: ipython3
|
|
||||||
|
|
||||||
define('direco dip rest cons')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('G [direco] cons [swap] swoncat cons')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('make_distributor [dup dup max [index_of] nullary distribute] G')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
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] tailrec
|
|
||||||
|
|
||||||
(?)
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[] [GEN] x [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec
|
|
||||||
[] [...] [GEN] [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec
|
|
||||||
[] [...] [GEN] pop index_of 0 >=
|
|
||||||
[] [...] index_of 0 >=
|
|
||||||
-1 0 >=
|
|
||||||
False
|
|
||||||
|
|
||||||
Base case
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[] [...] [GEN] [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec
|
|
||||||
[] [...] [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] tailrec
|
|
||||||
[] [...] [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] tailrec
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('count_states [] swap x [pop index_of 0 >=] [popop size] [[swons] dip x] tailrec')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('AoC2017.6 make_distributor count_states')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[0 2 7 0] AoC2017.6')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
5
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 1 1] AoC2017.6')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
4
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[8 0 0 0 0 0] AoC2017.6')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,629 +0,0 @@
|
||||||
```python
|
|
||||||
from notebook_preamble import D, J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
# Compiling Joy
|
|
||||||
|
|
||||||
Given a Joy program like:
|
|
||||||
|
|
||||||
sqr == dup mul
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('23 sqr')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 23 sqr
|
|
||||||
23 • sqr
|
|
||||||
23 • dup mul
|
|
||||||
23 23 • mul
|
|
||||||
529 •
|
|
||||||
|
|
||||||
|
|
||||||
How would we go about compiling this code (to Python for now)?
|
|
||||||
|
|
||||||
## Naive Call Chaining
|
|
||||||
The simplest thing would be to compose the functions from the library:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
dup, mul = D['dup'], D['mul']
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def sqr(stack, expression, dictionary):
|
|
||||||
return mul(*dup(stack, expression, dictionary))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
old_sqr = D['sqr']
|
|
||||||
D['sqr'] = sqr
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('23 sqr')
|
|
||||||
```
|
|
||||||
|
|
||||||
• 23 sqr
|
|
||||||
23 • sqr
|
|
||||||
529 •
|
|
||||||
|
|
||||||
|
|
||||||
It's simple to write a function to emit this kind of crude "compiled" code.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def compile_joy(name, expression):
|
|
||||||
term, expression = expression
|
|
||||||
code = term +'(stack, expression, dictionary)'
|
|
||||||
format_ = '%s(*%s)'
|
|
||||||
while expression:
|
|
||||||
term, expression = expression
|
|
||||||
code = format_ % (term, code)
|
|
||||||
return '''\
|
|
||||||
def %s(stack, expression, dictionary):
|
|
||||||
return %s
|
|
||||||
''' % (name, code)
|
|
||||||
|
|
||||||
|
|
||||||
def compile_joy_definition(defi):
|
|
||||||
return compile_joy(defi.name, defi.body)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print(compile_joy_definition(old_sqr))
|
|
||||||
```
|
|
||||||
|
|
||||||
def sqr(stack, expression, dictionary):
|
|
||||||
return mul(*dup(stack, expression, dictionary))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
But what about literals?
|
|
||||||
|
|
||||||
quoted == [unit] dip
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
unit, dip = D['unit'], D['dip']
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
# print compile_joy_definition(D['quoted'])
|
|
||||||
# raises
|
|
||||||
# TypeError: can only concatenate tuple (not "str") to tuple
|
|
||||||
```
|
|
||||||
|
|
||||||
For a program like `foo == bar baz 23 99 baq lerp barp` we would want something like:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def foo(stack, expression, dictionary):
|
|
||||||
stack, expression, dictionary = baz(*bar(stack, expression, dictionary))
|
|
||||||
return barp(*lerp(*baq((99, (23, stack)), expression, dictionary)))
|
|
||||||
```
|
|
||||||
|
|
||||||
You have to have a little discontinuity when going from a symbol to a literal, because you have to pick out the stack from the arguments to push the literal(s) onto it before you continue chaining function calls.
|
|
||||||
|
|
||||||
## Compiling Yin Functions
|
|
||||||
Call-chaining results in code that does too much work. For functions that operate on stacks and only rearrange values, what I like to call "Yin Functions", we can do better.
|
|
||||||
|
|
||||||
We can infer the stack effects of these functions (or "expressions" or "programs") automatically, and the stack effects completely define the semantics of the functions, so we can directly write out a two-line Python function for them. This is already implemented in the `joy.utils.types.compile_()` function.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from joy.utils.types import compile_, doc_from_stack_effect, infer_string
|
|
||||||
from joy.library import SimpleFunctionWrapper
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
ModuleNotFoundError Traceback (most recent call last)
|
|
||||||
|
|
||||||
<ipython-input-14-d5ef3c7560be> in <module>
|
|
||||||
----> 1 from joy.utils.types import compile_, doc_from_stack_effect, infer_string
|
|
||||||
2 from joy.library import SimpleFunctionWrapper
|
|
||||||
|
|
||||||
|
|
||||||
ModuleNotFoundError: No module named 'joy.utils.types'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack_effects = infer_string('tuck over dup')
|
|
||||||
```
|
|
||||||
|
|
||||||
Yin functions have only a single stack effect, they do not branch or loop.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for fi, fo in stack_effects:
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
source = compile_('foo', stack_effects[0])
|
|
||||||
```
|
|
||||||
|
|
||||||
All Yin functions can be described in Python as a tuple-unpacking (or "-destructuring") of the stack datastructure followed by building up the new stack structure.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print source
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
exec compile(source, '__main__', 'single')
|
|
||||||
|
|
||||||
D['foo'] = SimpleFunctionWrapper(foo)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
File "<ipython-input-9-1a7e90bf2d7b>", line 1
|
|
||||||
exec compile(source, '__main__', 'single')
|
|
||||||
^
|
|
||||||
SyntaxError: invalid syntax
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('23 18 foo')
|
|
||||||
```
|
|
||||||
|
|
||||||
## Compiling from Stack Effects
|
|
||||||
|
|
||||||
There are times when you're deriving a Joy program when you have a stack effect for a Yin function and you need to define it. For example, in the Ordered Binary Trees notebook there is a point where we must derive a function `Ee`:
|
|
||||||
|
|
||||||
[key old_value left right] new_value key [Tree-add] Ee
|
|
||||||
------------------------------------------------------------
|
|
||||||
[key new_value left right]
|
|
||||||
|
|
||||||
While it is not hard to come up with this function manually, there is no necessity. This function can be defined (in Python) directly from its stack effect:
|
|
||||||
|
|
||||||
[a b c d] e a [f] Ee
|
|
||||||
--------------------------
|
|
||||||
[a e c d]
|
|
||||||
|
|
||||||
(I haven't yet implemented a simple interface for this yet. What follow is an exploration of how to do it.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from joy.parser import text_to_expression
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
Ein = '[a b c d] e a [f]' # The terms should be reversed here but I don't realize that until later.
|
|
||||||
Eout = '[a e c d]'
|
|
||||||
E = '[%s] [%s]' % (Ein, Eout)
|
|
||||||
|
|
||||||
print E
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
(fi, (fo, _)) = text_to_expression(E)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
fi, fo
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
Ein = '[a1 a2 a3 a4] a5 a6 a7'
|
|
||||||
Eout = '[a1 a5 a3 a4]'
|
|
||||||
E = '[%s] [%s]' % (Ein, Eout)
|
|
||||||
|
|
||||||
print E
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
(fi, (fo, _)) = text_to_expression(E)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
fi, fo
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def type_vars():
|
|
||||||
from joy.library import a1, a2, a3, a4, a5, a6, a7, s0, s1
|
|
||||||
return locals()
|
|
||||||
|
|
||||||
tv = type_vars()
|
|
||||||
tv
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from joy.utils.types import reify
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack_effect = reify(tv, (fi, fo))
|
|
||||||
print doc_from_stack_effect(*stack_effect)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print stack_effect
|
|
||||||
```
|
|
||||||
|
|
||||||
Almost, but what we really want is something like this:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack_effect = eval('(((a1, (a2, (a3, (a4, s1)))), (a5, (a6, (a7, s0)))), ((a1, (a5, (a3, (a4, s1)))), s0))', tv)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note the change of `()` to `JoyStackType` type variables.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print doc_from_stack_effect(*stack_effect)
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can omit `a3` and `a4` if we like:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack_effect = eval('(((a1, (a2, s1)), (a5, (a6, (a7, s0)))), ((a1, (a5, s1)), s0))', tv)
|
|
||||||
```
|
|
||||||
|
|
||||||
The `right` and `left` parts of the ordered binary tree node are subsumed in the tail of the node's stack/list.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print doc_from_stack_effect(*stack_effect)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
source = compile_('Ee', stack_effect)
|
|
||||||
print source
|
|
||||||
```
|
|
||||||
|
|
||||||
Oops! The input stack is backwards...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack_effect = eval('((a7, (a6, (a5, ((a1, (a2, s1)), s0)))), ((a1, (a5, s1)), s0))', tv)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print doc_from_stack_effect(*stack_effect)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
source = compile_('Ee', stack_effect)
|
|
||||||
print source
|
|
||||||
```
|
|
||||||
|
|
||||||
Compare:
|
|
||||||
|
|
||||||
[key old_value left right] new_value key [Tree-add] Ee
|
|
||||||
------------------------------------------------------------
|
|
||||||
[key new_value left right]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
eval(compile(source, '__main__', 'single'))
|
|
||||||
D['Ee'] = SimpleFunctionWrapper(Ee)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[a b c d] 1 2 [f] Ee')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Working with Yang Functions
|
|
||||||
|
|
||||||
Consider the compiled code of `dup`:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
def dup(stack):
|
|
||||||
(a1, s23) = stack
|
|
||||||
return (a1, (a1, s23))
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
To compile `sqr == dup mul` we can compute the stack effect:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack_effects = infer_string('dup mul')
|
|
||||||
for fi, fo in stack_effects:
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
```
|
|
||||||
|
|
||||||
Then we would want something like this:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
def sqr(stack):
|
|
||||||
(n1, s23) = stack
|
|
||||||
n2 = mul(n1, n1)
|
|
||||||
return (n2, s23)
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
How about...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack_effects = infer_string('mul mul sub')
|
|
||||||
for fi, fo in stack_effects:
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
def foo(stack):
|
|
||||||
(n1, (n2, (n3, (n4, s23)))) = stack
|
|
||||||
n5 = mul(n1, n2)
|
|
||||||
n6 = mul(n5, n3)
|
|
||||||
n7 = sub(n6, n4)
|
|
||||||
return (n7, s23)
|
|
||||||
|
|
||||||
|
|
||||||
# or
|
|
||||||
|
|
||||||
def foo(stack):
|
|
||||||
(n1, (n2, (n3, (n4, s23)))) = stack
|
|
||||||
n5 = sub(mul(mul(n1, n2), n3), n4)
|
|
||||||
return (n5, s23)
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
stack_effects = infer_string('tuck')
|
|
||||||
for fi, fo in stack_effects:
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Compiling Yin~Yang Functions
|
|
||||||
|
|
||||||
First, we need a source of Python identifiers. I'm going to reuse `Symbol` class for this.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from joy.parser import Symbol
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def _names():
|
|
||||||
n = 0
|
|
||||||
while True:
|
|
||||||
yield Symbol('a' + str(n))
|
|
||||||
n += 1
|
|
||||||
|
|
||||||
names = _names().next
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we need an object that represents a Yang function that accepts two args and return one result (we'll implement other kinds a little later.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Foo(object):
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def __call__(self, stack, expression, code):
|
|
||||||
in1, (in0, stack) = stack
|
|
||||||
out = names()
|
|
||||||
code.append(('call', out, self.name, (in0, in1)))
|
|
||||||
return (out, stack), expression, code
|
|
||||||
```
|
|
||||||
|
|
||||||
A crude "interpreter" that translates expressions of args and Yin and Yang functions into a kind of simple dataflow graph.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def I(stack, expression, code):
|
|
||||||
while expression:
|
|
||||||
term, expression = expression
|
|
||||||
if callable(term):
|
|
||||||
stack, expression, _ = term(stack, expression, code)
|
|
||||||
else:
|
|
||||||
stack = term, stack
|
|
||||||
code.append(('pop', term))
|
|
||||||
|
|
||||||
s = []
|
|
||||||
while stack:
|
|
||||||
term, stack = stack
|
|
||||||
s.insert(0, term)
|
|
||||||
if s:
|
|
||||||
code.append(('push',) + tuple(s))
|
|
||||||
return code
|
|
||||||
```
|
|
||||||
|
|
||||||
Something to convert the graph into Python code.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
strtup = lambda a, b: '(%s, %s)' % (b, a)
|
|
||||||
strstk = lambda rest: reduce(strtup, rest, 'stack')
|
|
||||||
|
|
||||||
|
|
||||||
def code_gen(code):
|
|
||||||
coalesce_pops(code)
|
|
||||||
lines = []
|
|
||||||
for t in code:
|
|
||||||
tag, rest = t[0], t[1:]
|
|
||||||
|
|
||||||
if tag == 'pop':
|
|
||||||
lines.append(strstk(rest) + ' = stack')
|
|
||||||
|
|
||||||
elif tag == 'push':
|
|
||||||
lines.append('stack = ' + strstk(rest))
|
|
||||||
|
|
||||||
elif tag == 'call':
|
|
||||||
#out, name, in_ = rest
|
|
||||||
lines.append('%s = %s%s' % rest)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ValueError(tag)
|
|
||||||
|
|
||||||
return '\n'.join(' ' + line for line in lines)
|
|
||||||
|
|
||||||
|
|
||||||
def coalesce_pops(code):
|
|
||||||
index = [i for i, t in enumerate(code) if t[0] == 'pop']
|
|
||||||
for start, end in yield_groups(index):
|
|
||||||
code[start:end] = \
|
|
||||||
[tuple(['pop'] + [t for _, t in code[start:end][::-1]])]
|
|
||||||
|
|
||||||
|
|
||||||
def yield_groups(index):
|
|
||||||
'''
|
|
||||||
Yield slice indices for each group of contiguous ints in the
|
|
||||||
index list.
|
|
||||||
'''
|
|
||||||
k = 0
|
|
||||||
for i, (a, b) in enumerate(zip(index, index[1:])):
|
|
||||||
if b - a > 1:
|
|
||||||
if k != i:
|
|
||||||
yield index[k], index[i] + 1
|
|
||||||
k = i + 1
|
|
||||||
if k < len(index):
|
|
||||||
yield index[k], index[-1] + 1
|
|
||||||
|
|
||||||
|
|
||||||
def compile_yinyang(name, expression):
|
|
||||||
return '''\
|
|
||||||
def %s(stack):
|
|
||||||
%s
|
|
||||||
return stack
|
|
||||||
''' % (name, code_gen(I((), expression, [])))
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
A few functions to try it with...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
mul = Foo('mul')
|
|
||||||
sub = Foo('sub')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def import_yin():
|
|
||||||
from joy.utils.generated_library import *
|
|
||||||
return locals()
|
|
||||||
|
|
||||||
yin_dict = {name: SimpleFunctionWrapper(func) for name, func in import_yin().iteritems()}
|
|
||||||
|
|
||||||
yin_dict
|
|
||||||
|
|
||||||
dup = yin_dict['dup']
|
|
||||||
|
|
||||||
#def dup(stack, expression, code):
|
|
||||||
# n, stack = stack
|
|
||||||
# return (n, (n, stack)), expression
|
|
||||||
```
|
|
||||||
|
|
||||||
... and there we are.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print compile_yinyang('mul_', (names(), (names(), (mul, ()))))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
e = (names(), (dup, (mul, ())))
|
|
||||||
print compile_yinyang('sqr', e)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
e = (names(), (dup, (names(), (sub, (mul, ())))))
|
|
||||||
print compile_yinyang('foo', e)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
e = (names(), (names(), (mul, (dup, (sub, (dup, ()))))))
|
|
||||||
print compile_yinyang('bar', e)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
e = (names(), (dup, (dup, (mul, (dup, (mul, (mul, ())))))))
|
|
||||||
print compile_yinyang('to_the_fifth_power', e)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
@ -1,592 +0,0 @@
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from notebook_preamble import D, J, V, define
|
|
||||||
|
|
||||||
Compiling Joy
|
|
||||||
=============
|
|
||||||
|
|
||||||
Given a Joy program like:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
sqr == dup mul
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('23 sqr')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 23 sqr
|
|
||||||
23 • sqr
|
|
||||||
23 • dup mul
|
|
||||||
23 23 • mul
|
|
||||||
529 •
|
|
||||||
|
|
||||||
|
|
||||||
How would we go about compiling this code (to Python for now)?
|
|
||||||
|
|
||||||
Naive Call Chaining
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
The simplest thing would be to compose the functions from the library:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
dup, mul = D['dup'], D['mul']
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
def sqr(stack, expression, dictionary):
|
|
||||||
return mul(*dup(stack, expression, dictionary))
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
old_sqr = D['sqr']
|
|
||||||
D['sqr'] = sqr
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('23 sqr')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
• 23 sqr
|
|
||||||
23 • sqr
|
|
||||||
529 •
|
|
||||||
|
|
||||||
|
|
||||||
It's simple to write a function to emit this kind of crude "compiled"
|
|
||||||
code.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
def compile_joy(name, expression):
|
|
||||||
term, expression = expression
|
|
||||||
code = term +'(stack, expression, dictionary)'
|
|
||||||
format_ = '%s(*%s)'
|
|
||||||
while expression:
|
|
||||||
term, expression = expression
|
|
||||||
code = format_ % (term, code)
|
|
||||||
return '''\
|
|
||||||
def %s(stack, expression, dictionary):
|
|
||||||
return %s
|
|
||||||
''' % (name, code)
|
|
||||||
|
|
||||||
|
|
||||||
def compile_joy_definition(defi):
|
|
||||||
return compile_joy(defi.name, defi.body)
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print(compile_joy_definition(old_sqr))
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
def sqr(stack, expression, dictionary):
|
|
||||||
return mul(*dup(stack, expression, dictionary))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
But what about literals?
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
quoted == [unit] dip
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
unit, dip = D['unit'], D['dip']
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
# print compile_joy_definition(D['quoted'])
|
|
||||||
# raises
|
|
||||||
# TypeError: can only concatenate tuple (not "str") to tuple
|
|
||||||
|
|
||||||
For a program like ``foo == bar baz 23 99 baq lerp barp`` we would want
|
|
||||||
something like:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
def foo(stack, expression, dictionary):
|
|
||||||
stack, expression, dictionary = baz(*bar(stack, expression, dictionary))
|
|
||||||
return barp(*lerp(*baq((99, (23, stack)), expression, dictionary)))
|
|
||||||
|
|
||||||
You have to have a little discontinuity when going from a symbol to a
|
|
||||||
literal, because you have to pick out the stack from the arguments to
|
|
||||||
push the literal(s) onto it before you continue chaining function calls.
|
|
||||||
|
|
||||||
Compiling Yin Functions
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Call-chaining results in code that does too much work. For functions
|
|
||||||
that operate on stacks and only rearrange values, what I like to call
|
|
||||||
"Yin Functions", we can do better.
|
|
||||||
|
|
||||||
We can infer the stack effects of these functions (or "expressions" or
|
|
||||||
"programs") automatically, and the stack effects completely define the
|
|
||||||
semantics of the functions, so we can directly write out a two-line
|
|
||||||
Python function for them. This is already implemented in the
|
|
||||||
``joy.utils.types.compile_()`` function.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from joy.utils.types import compile_, doc_from_stack_effect, infer_string
|
|
||||||
from joy.library import SimpleFunctionWrapper
|
|
||||||
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
|
|
||||||
---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
ModuleNotFoundError Traceback (most recent call last)
|
|
||||||
|
|
||||||
<ipython-input-14-d5ef3c7560be> in <module>
|
|
||||||
----> 1 from joy.utils.types import compile_, doc_from_stack_effect, infer_string
|
|
||||||
2 from joy.library import SimpleFunctionWrapper
|
|
||||||
|
|
||||||
|
|
||||||
ModuleNotFoundError: No module named 'joy.utils.types'
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack_effects = infer_string('tuck over dup')
|
|
||||||
|
|
||||||
Yin functions have only a single stack effect, they do not branch or
|
|
||||||
loop.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
for fi, fo in stack_effects:
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
source = compile_('foo', stack_effects[0])
|
|
||||||
|
|
||||||
All Yin functions can be described in Python as a tuple-unpacking (or
|
|
||||||
"-destructuring") of the stack datastructure followed by building up the
|
|
||||||
new stack structure.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print source
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
exec compile(source, '__main__', 'single')
|
|
||||||
|
|
||||||
D['foo'] = SimpleFunctionWrapper(foo)
|
|
||||||
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
|
|
||||||
File "<ipython-input-9-1a7e90bf2d7b>", line 1
|
|
||||||
exec compile(source, '__main__', 'single')
|
|
||||||
^
|
|
||||||
SyntaxError: invalid syntax
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('23 18 foo')
|
|
||||||
|
|
||||||
Compiling from Stack Effects
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
There are times when you're deriving a Joy program when you have a stack
|
|
||||||
effect for a Yin function and you need to define it. For example, in the
|
|
||||||
Ordered Binary Trees notebook there is a point where we must derive a
|
|
||||||
function ``Ee``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[key old_value left right] new_value key [Tree-add] Ee
|
|
||||||
------------------------------------------------------------
|
|
||||||
[key new_value left right]
|
|
||||||
|
|
||||||
While it is not hard to come up with this function manually, there is no
|
|
||||||
necessity. This function can be defined (in Python) directly from its
|
|
||||||
stack effect:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[a b c d] e a [f] Ee
|
|
||||||
--------------------------
|
|
||||||
[a e c d]
|
|
||||||
|
|
||||||
(I haven't yet implemented a simple interface for this yet. What follow
|
|
||||||
is an exploration of how to do it.)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from joy.parser import text_to_expression
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
Ein = '[a b c d] e a [f]' # The terms should be reversed here but I don't realize that until later.
|
|
||||||
Eout = '[a e c d]'
|
|
||||||
E = '[%s] [%s]' % (Ein, Eout)
|
|
||||||
|
|
||||||
print E
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
(fi, (fo, _)) = text_to_expression(E)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
fi, fo
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
Ein = '[a1 a2 a3 a4] a5 a6 a7'
|
|
||||||
Eout = '[a1 a5 a3 a4]'
|
|
||||||
E = '[%s] [%s]' % (Ein, Eout)
|
|
||||||
|
|
||||||
print E
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
(fi, (fo, _)) = text_to_expression(E)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
fi, fo
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
def type_vars():
|
|
||||||
from joy.library import a1, a2, a3, a4, a5, a6, a7, s0, s1
|
|
||||||
return locals()
|
|
||||||
|
|
||||||
tv = type_vars()
|
|
||||||
tv
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from joy.utils.types import reify
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack_effect = reify(tv, (fi, fo))
|
|
||||||
print doc_from_stack_effect(*stack_effect)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print stack_effect
|
|
||||||
|
|
||||||
Almost, but what we really want is something like this:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack_effect = eval('(((a1, (a2, (a3, (a4, s1)))), (a5, (a6, (a7, s0)))), ((a1, (a5, (a3, (a4, s1)))), s0))', tv)
|
|
||||||
|
|
||||||
Note the change of ``()`` to ``JoyStackType`` type variables.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print doc_from_stack_effect(*stack_effect)
|
|
||||||
|
|
||||||
Now we can omit ``a3`` and ``a4`` if we like:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack_effect = eval('(((a1, (a2, s1)), (a5, (a6, (a7, s0)))), ((a1, (a5, s1)), s0))', tv)
|
|
||||||
|
|
||||||
The ``right`` and ``left`` parts of the ordered binary tree node are
|
|
||||||
subsumed in the tail of the node's stack/list.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print doc_from_stack_effect(*stack_effect)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
source = compile_('Ee', stack_effect)
|
|
||||||
print source
|
|
||||||
|
|
||||||
Oops! The input stack is backwards...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack_effect = eval('((a7, (a6, (a5, ((a1, (a2, s1)), s0)))), ((a1, (a5, s1)), s0))', tv)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print doc_from_stack_effect(*stack_effect)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
source = compile_('Ee', stack_effect)
|
|
||||||
print source
|
|
||||||
|
|
||||||
Compare:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[key old_value left right] new_value key [Tree-add] Ee
|
|
||||||
------------------------------------------------------------
|
|
||||||
[key new_value left right]
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
eval(compile(source, '__main__', 'single'))
|
|
||||||
D['Ee'] = SimpleFunctionWrapper(Ee)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
V('[a b c d] 1 2 [f] Ee')
|
|
||||||
|
|
||||||
|
|
||||||
Working with Yang Functions
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
Consider the compiled code of ``dup``:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
|
|
||||||
def dup(stack):
|
|
||||||
(a1, s23) = stack
|
|
||||||
return (a1, (a1, s23))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
To compile ``sqr == dup mul`` we can compute the stack effect:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack_effects = infer_string('dup mul')
|
|
||||||
for fi, fo in stack_effects:
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
|
|
||||||
Then we would want something like this:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
|
|
||||||
def sqr(stack):
|
|
||||||
(n1, s23) = stack
|
|
||||||
n2 = mul(n1, n1)
|
|
||||||
return (n2, s23)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
How about...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack_effects = infer_string('mul mul sub')
|
|
||||||
for fi, fo in stack_effects:
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
|
|
||||||
def foo(stack):
|
|
||||||
(n1, (n2, (n3, (n4, s23)))) = stack
|
|
||||||
n5 = mul(n1, n2)
|
|
||||||
n6 = mul(n5, n3)
|
|
||||||
n7 = sub(n6, n4)
|
|
||||||
return (n7, s23)
|
|
||||||
|
|
||||||
|
|
||||||
# or
|
|
||||||
|
|
||||||
def foo(stack):
|
|
||||||
(n1, (n2, (n3, (n4, s23)))) = stack
|
|
||||||
n5 = sub(mul(mul(n1, n2), n3), n4)
|
|
||||||
return (n5, s23)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
stack_effects = infer_string('tuck')
|
|
||||||
for fi, fo in stack_effects:
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
|
|
||||||
|
|
||||||
Compiling Yin~Yang Functions
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
First, we need a source of Python identifiers. I'm going to reuse
|
|
||||||
``Symbol`` class for this.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from joy.parser import Symbol
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
def _names():
|
|
||||||
n = 0
|
|
||||||
while True:
|
|
||||||
yield Symbol('a' + str(n))
|
|
||||||
n += 1
|
|
||||||
|
|
||||||
names = _names().next
|
|
||||||
|
|
||||||
Now we need an object that represents a Yang function that accepts two
|
|
||||||
args and return one result (we'll implement other kinds a little later.)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
class Foo(object):
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def __call__(self, stack, expression, code):
|
|
||||||
in1, (in0, stack) = stack
|
|
||||||
out = names()
|
|
||||||
code.append(('call', out, self.name, (in0, in1)))
|
|
||||||
return (out, stack), expression, code
|
|
||||||
|
|
||||||
A crude "interpreter" that translates expressions of args and Yin and
|
|
||||||
Yang functions into a kind of simple dataflow graph.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
def I(stack, expression, code):
|
|
||||||
while expression:
|
|
||||||
term, expression = expression
|
|
||||||
if callable(term):
|
|
||||||
stack, expression, _ = term(stack, expression, code)
|
|
||||||
else:
|
|
||||||
stack = term, stack
|
|
||||||
code.append(('pop', term))
|
|
||||||
|
|
||||||
s = []
|
|
||||||
while stack:
|
|
||||||
term, stack = stack
|
|
||||||
s.insert(0, term)
|
|
||||||
if s:
|
|
||||||
code.append(('push',) + tuple(s))
|
|
||||||
return code
|
|
||||||
|
|
||||||
Something to convert the graph into Python code.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
strtup = lambda a, b: '(%s, %s)' % (b, a)
|
|
||||||
strstk = lambda rest: reduce(strtup, rest, 'stack')
|
|
||||||
|
|
||||||
|
|
||||||
def code_gen(code):
|
|
||||||
coalesce_pops(code)
|
|
||||||
lines = []
|
|
||||||
for t in code:
|
|
||||||
tag, rest = t[0], t[1:]
|
|
||||||
|
|
||||||
if tag == 'pop':
|
|
||||||
lines.append(strstk(rest) + ' = stack')
|
|
||||||
|
|
||||||
elif tag == 'push':
|
|
||||||
lines.append('stack = ' + strstk(rest))
|
|
||||||
|
|
||||||
elif tag == 'call':
|
|
||||||
#out, name, in_ = rest
|
|
||||||
lines.append('%s = %s%s' % rest)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ValueError(tag)
|
|
||||||
|
|
||||||
return '\n'.join(' ' + line for line in lines)
|
|
||||||
|
|
||||||
|
|
||||||
def coalesce_pops(code):
|
|
||||||
index = [i for i, t in enumerate(code) if t[0] == 'pop']
|
|
||||||
for start, end in yield_groups(index):
|
|
||||||
code[start:end] = \
|
|
||||||
[tuple(['pop'] + [t for _, t in code[start:end][::-1]])]
|
|
||||||
|
|
||||||
|
|
||||||
def yield_groups(index):
|
|
||||||
'''
|
|
||||||
Yield slice indices for each group of contiguous ints in the
|
|
||||||
index list.
|
|
||||||
'''
|
|
||||||
k = 0
|
|
||||||
for i, (a, b) in enumerate(zip(index, index[1:])):
|
|
||||||
if b - a > 1:
|
|
||||||
if k != i:
|
|
||||||
yield index[k], index[i] + 1
|
|
||||||
k = i + 1
|
|
||||||
if k < len(index):
|
|
||||||
yield index[k], index[-1] + 1
|
|
||||||
|
|
||||||
|
|
||||||
def compile_yinyang(name, expression):
|
|
||||||
return '''\
|
|
||||||
def %s(stack):
|
|
||||||
%s
|
|
||||||
return stack
|
|
||||||
''' % (name, code_gen(I((), expression, [])))
|
|
||||||
|
|
||||||
|
|
||||||
A few functions to try it with...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
mul = Foo('mul')
|
|
||||||
sub = Foo('sub')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
def import_yin():
|
|
||||||
from joy.utils.generated_library import *
|
|
||||||
return locals()
|
|
||||||
|
|
||||||
yin_dict = {name: SimpleFunctionWrapper(func) for name, func in import_yin().iteritems()}
|
|
||||||
|
|
||||||
yin_dict
|
|
||||||
|
|
||||||
dup = yin_dict['dup']
|
|
||||||
|
|
||||||
#def dup(stack, expression, code):
|
|
||||||
# n, stack = stack
|
|
||||||
# return (n, (n, stack)), expression
|
|
||||||
|
|
||||||
... and there we are.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
print compile_yinyang('mul_', (names(), (names(), (mul, ()))))
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
e = (names(), (dup, (mul, ())))
|
|
||||||
print compile_yinyang('sqr', e)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
e = (names(), (dup, (names(), (sub, (mul, ())))))
|
|
||||||
print compile_yinyang('foo', e)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
e = (names(), (names(), (mul, (dup, (sub, (dup, ()))))))
|
|
||||||
print compile_yinyang('bar', e)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
e = (names(), (dup, (dup, (mul, (dup, (mul, (mul, ())))))))
|
|
||||||
print compile_yinyang('to_the_fifth_power', e)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -1,817 +0,0 @@
|
||||||
# ∂RE
|
|
||||||
|
|
||||||
## Brzozowski's Derivatives of Regular Expressions
|
|
||||||
|
|
||||||
Legend:
|
|
||||||
|
|
||||||
∧ intersection
|
|
||||||
∨ union
|
|
||||||
∘ concatenation (see below)
|
|
||||||
¬ complement
|
|
||||||
ϕ empty set (aka ∅)
|
|
||||||
λ singleton set containing just the empty string
|
|
||||||
I set of all letters in alphabet
|
|
||||||
|
|
||||||
Derivative of a set `R` of strings and a string `a`:
|
|
||||||
|
|
||||||
∂a(R)
|
|
||||||
|
|
||||||
∂a(a) → λ
|
|
||||||
∂a(λ) → ϕ
|
|
||||||
∂a(ϕ) → ϕ
|
|
||||||
∂a(¬a) → ϕ
|
|
||||||
∂a(R*) → ∂a(R)∘R*
|
|
||||||
∂a(¬R) → ¬∂a(R)
|
|
||||||
∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
|
|
||||||
∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
|
|
||||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
|
|
||||||
∂ab(R) = ∂b(∂a(R))
|
|
||||||
|
|
||||||
Auxiliary predicate function `δ` (I call it `nully`) returns either `λ` if `λ ⊆ R` or `ϕ` otherwise:
|
|
||||||
|
|
||||||
δ(a) → ϕ
|
|
||||||
δ(λ) → λ
|
|
||||||
δ(ϕ) → ϕ
|
|
||||||
δ(R*) → λ
|
|
||||||
δ(¬R) δ(R)≟ϕ → λ
|
|
||||||
δ(¬R) δ(R)≟λ → ϕ
|
|
||||||
δ(R∘S) → δ(R) ∧ δ(S)
|
|
||||||
δ(R ∧ S) → δ(R) ∧ δ(S)
|
|
||||||
δ(R ∨ S) → δ(R) ∨ δ(S)
|
|
||||||
|
|
||||||
Some rules we will use later for "compaction":
|
|
||||||
|
|
||||||
R ∧ ϕ = ϕ ∧ R = ϕ
|
|
||||||
|
|
||||||
R ∧ I = I ∧ R = R
|
|
||||||
|
|
||||||
R ∨ ϕ = ϕ ∨ R = R
|
|
||||||
|
|
||||||
R ∨ I = I ∨ R = I
|
|
||||||
|
|
||||||
R∘ϕ = ϕ∘R = ϕ
|
|
||||||
|
|
||||||
R∘λ = λ∘R = R
|
|
||||||
|
|
||||||
Concatination of sets: for two sets A and B the set A∘B is defined as:
|
|
||||||
|
|
||||||
{a∘b for a in A for b in B}
|
|
||||||
|
|
||||||
E.g.:
|
|
||||||
|
|
||||||
{'a', 'b'}∘{'c', 'd'} → {'ac', 'ad', 'bc', 'bd'}
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from functools import partial as curry
|
|
||||||
from itertools import product
|
|
||||||
```
|
|
||||||
|
|
||||||
### `ϕ` and `λ`
|
|
||||||
The empty set and the set of just the empty string.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
phi = frozenset() # ϕ
|
|
||||||
y = frozenset({''}) # λ
|
|
||||||
```
|
|
||||||
|
|
||||||
### Two-letter Alphabet
|
|
||||||
I'm only going to use two symbols (at first) becaase this is enough to illustrate the algorithm and because you can represent any other alphabet with two symbols (if you had to.)
|
|
||||||
|
|
||||||
I chose the names `O` and `l` (uppercase "o" and lowercase "L") to look like `0` and `1` (zero and one) respectively.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
syms = O, l = frozenset({'0'}), frozenset({'1'})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Representing Regular Expressions
|
|
||||||
To represent REs in Python I'm going to use tagged tuples. A _regular expression_ is one of:
|
|
||||||
|
|
||||||
O
|
|
||||||
l
|
|
||||||
(KSTAR, R)
|
|
||||||
(NOT, R)
|
|
||||||
(AND, R, S)
|
|
||||||
(CONS, R, S)
|
|
||||||
(OR, R, S)
|
|
||||||
|
|
||||||
Where `R` and `S` stand for _regular expressions_.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
AND, CONS, KSTAR, NOT, OR = 'and cons * not or'.split() # Tags are just strings.
|
|
||||||
```
|
|
||||||
|
|
||||||
Because they are formed of `frozenset`, `tuple` and `str` objects only, these datastructures are immutable.
|
|
||||||
|
|
||||||
### String Representation of RE Datastructures
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def stringy(re):
|
|
||||||
'''
|
|
||||||
Return a nice string repr for a regular expression datastructure.
|
|
||||||
'''
|
|
||||||
if re == I: return '.'
|
|
||||||
if re in syms: return next(iter(re))
|
|
||||||
if re == y: return '^'
|
|
||||||
if re == phi: return 'X'
|
|
||||||
|
|
||||||
assert isinstance(re, tuple), repr(re)
|
|
||||||
tag = re[0]
|
|
||||||
|
|
||||||
if tag == KSTAR:
|
|
||||||
body = stringy(re[1])
|
|
||||||
if not body: return body
|
|
||||||
if len(body) > 1: return '(' + body + ")*"
|
|
||||||
return body + '*'
|
|
||||||
|
|
||||||
if tag == NOT:
|
|
||||||
body = stringy(re[1])
|
|
||||||
if not body: return body
|
|
||||||
if len(body) > 1: return '(' + body + ")'"
|
|
||||||
return body + "'"
|
|
||||||
|
|
||||||
r, s = stringy(re[1]), stringy(re[2])
|
|
||||||
if tag == CONS: return r + s
|
|
||||||
if tag == OR: return '%s | %s' % (r, s)
|
|
||||||
if tag == AND: return '(%s) & (%s)' % (r, s)
|
|
||||||
|
|
||||||
raise ValueError
|
|
||||||
```
|
|
||||||
|
|
||||||
### `I`
|
|
||||||
Match anything. Often spelled "."
|
|
||||||
|
|
||||||
I = (0|1)*
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
I = (KSTAR, (OR, O, l))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print stringy(I)
|
|
||||||
```
|
|
||||||
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
### `(.111.) & (.01 + 11*)'`
|
|
||||||
The example expression from Brzozowski:
|
|
||||||
|
|
||||||
(.111.) & (.01 + 11*)'
|
|
||||||
a & (b + c)'
|
|
||||||
|
|
||||||
Note that it contains one of everything.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
a = (CONS, I, (CONS, l, (CONS, l, (CONS, l, I))))
|
|
||||||
b = (CONS, I, (CONS, O, l))
|
|
||||||
c = (CONS, l, (KSTAR, l))
|
|
||||||
it = (AND, a, (NOT, (OR, b, c)))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print stringy(it)
|
|
||||||
```
|
|
||||||
|
|
||||||
(.111.) & ((.01 | 11*)')
|
|
||||||
|
|
||||||
|
|
||||||
### `nully()`
|
|
||||||
Let's get that auxiliary predicate function `δ` out of the way.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def nully(R):
|
|
||||||
'''
|
|
||||||
δ - Return λ if λ ⊆ R otherwise ϕ.
|
|
||||||
'''
|
|
||||||
|
|
||||||
# δ(a) → ϕ
|
|
||||||
# δ(ϕ) → ϕ
|
|
||||||
if R in syms or R == phi:
|
|
||||||
return phi
|
|
||||||
|
|
||||||
# δ(λ) → λ
|
|
||||||
if R == y:
|
|
||||||
return y
|
|
||||||
|
|
||||||
tag = R[0]
|
|
||||||
|
|
||||||
# δ(R*) → λ
|
|
||||||
if tag == KSTAR:
|
|
||||||
return y
|
|
||||||
|
|
||||||
# δ(¬R) δ(R)≟ϕ → λ
|
|
||||||
# δ(¬R) δ(R)≟λ → ϕ
|
|
||||||
if tag == NOT:
|
|
||||||
return phi if nully(R[1]) else y
|
|
||||||
|
|
||||||
# δ(R∘S) → δ(R) ∧ δ(S)
|
|
||||||
# δ(R ∧ S) → δ(R) ∧ δ(S)
|
|
||||||
# δ(R ∨ S) → δ(R) ∨ δ(S)
|
|
||||||
r, s = nully(R[1]), nully(R[2])
|
|
||||||
return r & s if tag in {AND, CONS} else r | s
|
|
||||||
```
|
|
||||||
|
|
||||||
### No "Compaction"
|
|
||||||
This is the straightforward version with no "compaction".
|
|
||||||
It works fine, but does waaaay too much work because the
|
|
||||||
expressions grow each derivation.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def D(symbol):
|
|
||||||
|
|
||||||
def derv(R):
|
|
||||||
|
|
||||||
# ∂a(a) → λ
|
|
||||||
if R == {symbol}:
|
|
||||||
return y
|
|
||||||
|
|
||||||
# ∂a(λ) → ϕ
|
|
||||||
# ∂a(ϕ) → ϕ
|
|
||||||
# ∂a(¬a) → ϕ
|
|
||||||
if R == y or R == phi or R in syms:
|
|
||||||
return phi
|
|
||||||
|
|
||||||
tag = R[0]
|
|
||||||
|
|
||||||
# ∂a(R*) → ∂a(R)∘R*
|
|
||||||
if tag == KSTAR:
|
|
||||||
return (CONS, derv(R[1]), R)
|
|
||||||
|
|
||||||
# ∂a(¬R) → ¬∂a(R)
|
|
||||||
if tag == NOT:
|
|
||||||
return (NOT, derv(R[1]))
|
|
||||||
|
|
||||||
r, s = R[1:]
|
|
||||||
|
|
||||||
# ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
|
|
||||||
if tag == CONS:
|
|
||||||
A = (CONS, derv(r), s) # A = ∂a(R)∘S
|
|
||||||
# A ∨ δ(R) ∘ ∂a(S)
|
|
||||||
# A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S)
|
|
||||||
# A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A
|
|
||||||
return (OR, A, derv(s)) if nully(r) else A
|
|
||||||
|
|
||||||
# ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
|
|
||||||
# ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
return (tag, derv(r), derv(s))
|
|
||||||
|
|
||||||
return derv
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compaction Rules
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def _compaction_rule(relation, one, zero, a, b):
|
|
||||||
return (
|
|
||||||
b if a == one else # R*1 = 1*R = R
|
|
||||||
a if b == one else
|
|
||||||
zero if a == zero or b == zero else # R*0 = 0*R = 0
|
|
||||||
(relation, a, b)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
An elegant symmetry.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
# R ∧ I = I ∧ R = R
|
|
||||||
# R ∧ ϕ = ϕ ∧ R = ϕ
|
|
||||||
_and = curry(_compaction_rule, AND, I, phi)
|
|
||||||
|
|
||||||
# R ∨ ϕ = ϕ ∨ R = R
|
|
||||||
# R ∨ I = I ∨ R = I
|
|
||||||
_or = curry(_compaction_rule, OR, phi, I)
|
|
||||||
|
|
||||||
# R∘λ = λ∘R = R
|
|
||||||
# R∘ϕ = ϕ∘R = ϕ
|
|
||||||
_cons = curry(_compaction_rule, CONS, y, phi)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memoizing
|
|
||||||
We can save re-processing by remembering results we have already computed. RE datastructures are immutable and the `derv()` functions are _pure_ so this is fine.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Memo(object):
|
|
||||||
|
|
||||||
def __init__(self, f):
|
|
||||||
self.f = f
|
|
||||||
self.calls = self.hits = 0
|
|
||||||
self.mem = {}
|
|
||||||
|
|
||||||
def __call__(self, key):
|
|
||||||
self.calls += 1
|
|
||||||
try:
|
|
||||||
result = self.mem[key]
|
|
||||||
self.hits += 1
|
|
||||||
except KeyError:
|
|
||||||
result = self.mem[key] = self.f(key)
|
|
||||||
return result
|
|
||||||
```
|
|
||||||
|
|
||||||
### With "Compaction"
|
|
||||||
This version uses the rules above to perform compaction. It keeps the expressions from growing too large.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def D_compaction(symbol):
|
|
||||||
|
|
||||||
@Memo
|
|
||||||
def derv(R):
|
|
||||||
|
|
||||||
# ∂a(a) → λ
|
|
||||||
if R == {symbol}:
|
|
||||||
return y
|
|
||||||
|
|
||||||
# ∂a(λ) → ϕ
|
|
||||||
# ∂a(ϕ) → ϕ
|
|
||||||
# ∂a(¬a) → ϕ
|
|
||||||
if R == y or R == phi or R in syms:
|
|
||||||
return phi
|
|
||||||
|
|
||||||
tag = R[0]
|
|
||||||
|
|
||||||
# ∂a(R*) → ∂a(R)∘R*
|
|
||||||
if tag == KSTAR:
|
|
||||||
return _cons(derv(R[1]), R)
|
|
||||||
|
|
||||||
# ∂a(¬R) → ¬∂a(R)
|
|
||||||
if tag == NOT:
|
|
||||||
return (NOT, derv(R[1]))
|
|
||||||
|
|
||||||
r, s = R[1:]
|
|
||||||
|
|
||||||
# ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
|
|
||||||
if tag == CONS:
|
|
||||||
A = _cons(derv(r), s) # A = ∂a(r)∘s
|
|
||||||
# A ∨ δ(R) ∘ ∂a(S)
|
|
||||||
# A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S)
|
|
||||||
# A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A
|
|
||||||
return _or(A, derv(s)) if nully(r) else A
|
|
||||||
|
|
||||||
# ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
|
|
||||||
# ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
dr, ds = derv(r), derv(s)
|
|
||||||
return _and(dr, ds) if tag == AND else _or(dr, ds)
|
|
||||||
|
|
||||||
return derv
|
|
||||||
```
|
|
||||||
|
|
||||||
## Let's try it out...
|
|
||||||
(FIXME: redo.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
o, z = D_compaction('0'), D_compaction('1')
|
|
||||||
REs = set()
|
|
||||||
N = 5
|
|
||||||
names = list(product(*(N * [(0, 1)])))
|
|
||||||
dervs = list(product(*(N * [(o, z)])))
|
|
||||||
for name, ds in zip(names, dervs):
|
|
||||||
R = it
|
|
||||||
ds = list(ds)
|
|
||||||
while ds:
|
|
||||||
R = ds.pop()(R)
|
|
||||||
if R == phi or R == I:
|
|
||||||
break
|
|
||||||
REs.add(R)
|
|
||||||
|
|
||||||
print stringy(it) ; print
|
|
||||||
print o.hits, '/', o.calls
|
|
||||||
print z.hits, '/', z.calls
|
|
||||||
print
|
|
||||||
for s in sorted(map(stringy, REs), key=lambda n: (len(n), n)):
|
|
||||||
print s
|
|
||||||
```
|
|
||||||
|
|
||||||
(.111.) & ((.01 | 11*)')
|
|
||||||
|
|
||||||
92 / 122
|
|
||||||
92 / 122
|
|
||||||
|
|
||||||
(.01)'
|
|
||||||
(.01 | 1)'
|
|
||||||
(.01 | ^)'
|
|
||||||
(.01 | 1*)'
|
|
||||||
(.111.) & ((.01 | 1)')
|
|
||||||
(.111. | 11.) & ((.01 | ^)')
|
|
||||||
(.111. | 11. | 1.) & ((.01)')
|
|
||||||
(.111. | 11.) & ((.01 | 1*)')
|
|
||||||
(.111. | 11. | 1.) & ((.01 | 1*)')
|
|
||||||
|
|
||||||
|
|
||||||
Should match:
|
|
||||||
|
|
||||||
(.111.) & ((.01 | 11*)')
|
|
||||||
|
|
||||||
92 / 122
|
|
||||||
92 / 122
|
|
||||||
|
|
||||||
(.01 )'
|
|
||||||
(.01 | 1 )'
|
|
||||||
(.01 | ^ )'
|
|
||||||
(.01 | 1*)'
|
|
||||||
(.111.) & ((.01 | 1 )')
|
|
||||||
(.111. | 11.) & ((.01 | ^ )')
|
|
||||||
(.111. | 11.) & ((.01 | 1*)')
|
|
||||||
(.111. | 11. | 1.) & ((.01 )')
|
|
||||||
(.111. | 11. | 1.) & ((.01 | 1*)')
|
|
||||||
|
|
||||||
## Larger Alphabets
|
|
||||||
|
|
||||||
We could parse larger alphabets by defining patterns for e.g. each byte of the ASCII code. Or we can generalize this code. If you study the code above you'll see that we never use the "set-ness" of the symbols `O` and `l`. The only time Python set operators (`&` and `|`) appear is in the `nully()` function, and there they operate on (recursively computed) outputs of that function, never `O` and `l`.
|
|
||||||
|
|
||||||
|
|
||||||
What if we try:
|
|
||||||
|
|
||||||
(OR, O, l)
|
|
||||||
|
|
||||||
∂1((OR, O, l))
|
|
||||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
∂1(O) ∨ ∂1(l)
|
|
||||||
∂a(¬a) → ϕ
|
|
||||||
ϕ ∨ ∂1(l)
|
|
||||||
∂a(a) → λ
|
|
||||||
ϕ ∨ λ
|
|
||||||
ϕ ∨ R = R
|
|
||||||
λ
|
|
||||||
|
|
||||||
And compare it to:
|
|
||||||
|
|
||||||
{'0', '1')
|
|
||||||
|
|
||||||
∂1({'0', '1'))
|
|
||||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
∂1({'0')) ∨ ∂1({'1'))
|
|
||||||
∂a(¬a) → ϕ
|
|
||||||
ϕ ∨ ∂1({'1'))
|
|
||||||
∂a(a) → λ
|
|
||||||
ϕ ∨ λ
|
|
||||||
ϕ ∨ R = R
|
|
||||||
λ
|
|
||||||
|
|
||||||
This suggests that we should be able to alter the functions above to detect sets and deal with them appropriately. Exercise for the Reader for now.
|
|
||||||
|
|
||||||
## State Machine
|
|
||||||
We can drive the regular expressions to flesh out the underlying state machine transition table.
|
|
||||||
|
|
||||||
.111. & (.01 + 11*)'
|
|
||||||
|
|
||||||
Says, "Three or more 1's and not ending in 01 nor composed of all 1's."
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
Start at `a` and follow the transition arrows according to their labels. Accepting states have a double outline. (Graphic generated with [Dot from Graphviz](http://www.graphviz.org/).) You'll see that only paths that lead to one of the accepting states will match the regular expression. All other paths will terminate at one of the non-accepting states.
|
|
||||||
|
|
||||||
|
|
||||||
There's a happy path to `g` along 111:
|
|
||||||
|
|
||||||
a→c→e→g
|
|
||||||
|
|
||||||
After you reach `g` you're stuck there eating 1's until you see a 0, which takes you to the `i→j→i|i→j→h→i` "trap". You can't reach any other states from those two loops.
|
|
||||||
|
|
||||||
If you see a 0 before you see 111 you will reach `b`, which forms another "trap" with `d` and `f`. The only way out is another happy path along 111 to `h`:
|
|
||||||
|
|
||||||
b→d→f→h
|
|
||||||
|
|
||||||
Once you have reached `h` you can see as many 1's or as many 0' in a row and still be either still at `h` (for 1's) or move to `i` (for 0's). If you find yourself at `i` you can see as many 0's, or repetitions of 10, as there are, but if you see just a 1 you move to `j`.
|
|
||||||
|
|
||||||
### RE to FSM
|
|
||||||
So how do we get the state machine from the regular expression?
|
|
||||||
|
|
||||||
It turns out that each RE is effectively a state, and each arrow points to the derivative RE in respect to the arrow's symbol.
|
|
||||||
|
|
||||||
If we label the initial RE `a`, we can say:
|
|
||||||
|
|
||||||
a --0--> ∂0(a)
|
|
||||||
a --1--> ∂1(a)
|
|
||||||
|
|
||||||
And so on, each new unique RE is a new state in the FSM table.
|
|
||||||
|
|
||||||
Here are the derived REs at each state:
|
|
||||||
|
|
||||||
a = (.111.) & ((.01 | 11*)')
|
|
||||||
b = (.111.) & ((.01 | 1)')
|
|
||||||
c = (.111. | 11.) & ((.01 | 1*)')
|
|
||||||
d = (.111. | 11.) & ((.01 | ^)')
|
|
||||||
e = (.111. | 11. | 1.) & ((.01 | 1*)')
|
|
||||||
f = (.111. | 11. | 1.) & ((.01)')
|
|
||||||
g = (.01 | 1*)'
|
|
||||||
h = (.01)'
|
|
||||||
i = (.01 | 1)'
|
|
||||||
j = (.01 | ^)'
|
|
||||||
|
|
||||||
You can see the one-way nature of the `g` state and the `hij` "trap" in the way that the `.111.` on the left-hand side of the `&` disappears once it has been matched.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from collections import defaultdict
|
|
||||||
from pprint import pprint
|
|
||||||
from string import ascii_lowercase
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
d0, d1 = D_compaction('0'), D_compaction('1')
|
|
||||||
```
|
|
||||||
|
|
||||||
### `explore()`
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def explore(re):
|
|
||||||
|
|
||||||
# Don't have more than 26 states...
|
|
||||||
names = defaultdict(iter(ascii_lowercase).next)
|
|
||||||
|
|
||||||
table, accepting = dict(), set()
|
|
||||||
|
|
||||||
to_check = {re}
|
|
||||||
while to_check:
|
|
||||||
|
|
||||||
re = to_check.pop()
|
|
||||||
state_name = names[re]
|
|
||||||
|
|
||||||
if (state_name, 0) in table:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if nully(re):
|
|
||||||
accepting.add(state_name)
|
|
||||||
|
|
||||||
o, i = d0(re), d1(re)
|
|
||||||
table[state_name, 0] = names[o] ; to_check.add(o)
|
|
||||||
table[state_name, 1] = names[i] ; to_check.add(i)
|
|
||||||
|
|
||||||
return table, accepting
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
table, accepting = explore(it)
|
|
||||||
table
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{('a', 0): 'b',
|
|
||||||
('a', 1): 'c',
|
|
||||||
('b', 0): 'b',
|
|
||||||
('b', 1): 'd',
|
|
||||||
('c', 0): 'b',
|
|
||||||
('c', 1): 'e',
|
|
||||||
('d', 0): 'b',
|
|
||||||
('d', 1): 'f',
|
|
||||||
('e', 0): 'b',
|
|
||||||
('e', 1): 'g',
|
|
||||||
('f', 0): 'b',
|
|
||||||
('f', 1): 'h',
|
|
||||||
('g', 0): 'i',
|
|
||||||
('g', 1): 'g',
|
|
||||||
('h', 0): 'i',
|
|
||||||
('h', 1): 'h',
|
|
||||||
('i', 0): 'i',
|
|
||||||
('i', 1): 'j',
|
|
||||||
('j', 0): 'i',
|
|
||||||
('j', 1): 'h'}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
accepting
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{'h', 'i'}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Generate Diagram
|
|
||||||
Once we have the FSM table and the set of accepting states we can generate the diagram above.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
_template = '''\
|
|
||||||
digraph finite_state_machine {
|
|
||||||
rankdir=LR;
|
|
||||||
size="8,5"
|
|
||||||
node [shape = doublecircle]; %s;
|
|
||||||
node [shape = circle];
|
|
||||||
%s
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
|
|
||||||
def link(fr, nm, label):
|
|
||||||
return ' %s -> %s [ label = "%s" ];' % (fr, nm, label)
|
|
||||||
|
|
||||||
|
|
||||||
def make_graph(table, accepting):
|
|
||||||
return _template % (
|
|
||||||
' '.join(accepting),
|
|
||||||
'\n'.join(
|
|
||||||
link(from_, to, char)
|
|
||||||
for (from_, char), (to) in sorted(table.iteritems())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print make_graph(table, accepting)
|
|
||||||
```
|
|
||||||
|
|
||||||
digraph finite_state_machine {
|
|
||||||
rankdir=LR;
|
|
||||||
size="8,5"
|
|
||||||
node [shape = doublecircle]; i h;
|
|
||||||
node [shape = circle];
|
|
||||||
a -> b [ label = "0" ];
|
|
||||||
a -> c [ label = "1" ];
|
|
||||||
b -> b [ label = "0" ];
|
|
||||||
b -> d [ label = "1" ];
|
|
||||||
c -> b [ label = "0" ];
|
|
||||||
c -> e [ label = "1" ];
|
|
||||||
d -> b [ label = "0" ];
|
|
||||||
d -> f [ label = "1" ];
|
|
||||||
e -> b [ label = "0" ];
|
|
||||||
e -> g [ label = "1" ];
|
|
||||||
f -> b [ label = "0" ];
|
|
||||||
f -> h [ label = "1" ];
|
|
||||||
g -> i [ label = "0" ];
|
|
||||||
g -> g [ label = "1" ];
|
|
||||||
h -> i [ label = "0" ];
|
|
||||||
h -> h [ label = "1" ];
|
|
||||||
i -> i [ label = "0" ];
|
|
||||||
i -> j [ label = "1" ];
|
|
||||||
j -> i [ label = "0" ];
|
|
||||||
j -> h [ label = "1" ];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Drive a FSM
|
|
||||||
There are _lots_ of FSM libraries already. Once you have the state transition table they should all be straightforward to use. State Machine code is very simple. Just for fun, here is an implementation in Python that imitates what "compiled" FSM code might look like in an "unrolled" form. Most FSM code uses a little driver loop and a table datastructure, the code below instead acts like JMP instructions ("jump", or GOTO in higher-level-but-still-low-level languages) to hard-code the information in the table into a little patch of branches.
|
|
||||||
|
|
||||||
#### Trampoline Function
|
|
||||||
Python has no GOTO statement but we can fake it with a "trampoline" function.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def trampoline(input_, jump_from, accepting):
|
|
||||||
I = iter(input_)
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
bounce_to = jump_from(I)
|
|
||||||
except StopIteration:
|
|
||||||
return jump_from in accepting
|
|
||||||
jump_from = bounce_to
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Stream Functions
|
|
||||||
Little helpers to process the iterator of our data (a "stream" of "1" and "0" characters, not bits.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
getch = lambda I: int(next(I))
|
|
||||||
|
|
||||||
|
|
||||||
def _1(I):
|
|
||||||
'''Loop on ones.'''
|
|
||||||
while getch(I): pass
|
|
||||||
|
|
||||||
|
|
||||||
def _0(I):
|
|
||||||
'''Loop on zeros.'''
|
|
||||||
while not getch(I): pass
|
|
||||||
```
|
|
||||||
|
|
||||||
#### A Finite State Machine
|
|
||||||
With those preliminaries out of the way, from the state table of `.111. & (.01 + 11*)'` we can immediately write down state machine code. (You have to imagine that these are GOTO statements in C or branches in assembly and that the state names are branch destination labels.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
a = lambda I: c if getch(I) else b
|
|
||||||
b = lambda I: _0(I) or d
|
|
||||||
c = lambda I: e if getch(I) else b
|
|
||||||
d = lambda I: f if getch(I) else b
|
|
||||||
e = lambda I: g if getch(I) else b
|
|
||||||
f = lambda I: h if getch(I) else b
|
|
||||||
g = lambda I: _1(I) or i
|
|
||||||
h = lambda I: _1(I) or i
|
|
||||||
i = lambda I: _0(I) or j
|
|
||||||
j = lambda I: h if getch(I) else i
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the implementations of `h` and `g` are identical ergo `h = g` and we could eliminate one in the code but `h` is an accepting state and `g` isn't.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def acceptable(input_):
|
|
||||||
return trampoline(input_, a, {h, i})
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
for n in range(2**5):
|
|
||||||
s = bin(n)[2:]
|
|
||||||
print '%05s' % s, acceptable(s)
|
|
||||||
```
|
|
||||||
|
|
||||||
0 False
|
|
||||||
1 False
|
|
||||||
10 False
|
|
||||||
11 False
|
|
||||||
100 False
|
|
||||||
101 False
|
|
||||||
110 False
|
|
||||||
111 False
|
|
||||||
1000 False
|
|
||||||
1001 False
|
|
||||||
1010 False
|
|
||||||
1011 False
|
|
||||||
1100 False
|
|
||||||
1101 False
|
|
||||||
1110 True
|
|
||||||
1111 False
|
|
||||||
10000 False
|
|
||||||
10001 False
|
|
||||||
10010 False
|
|
||||||
10011 False
|
|
||||||
10100 False
|
|
||||||
10101 False
|
|
||||||
10110 False
|
|
||||||
10111 True
|
|
||||||
11000 False
|
|
||||||
11001 False
|
|
||||||
11010 False
|
|
||||||
11011 False
|
|
||||||
11100 True
|
|
||||||
11101 False
|
|
||||||
11110 True
|
|
||||||
11111 False
|
|
||||||
|
|
||||||
|
|
||||||
## Reversing the Derivatives to Generate Matching Strings
|
|
||||||
(UNFINISHED)
|
|
||||||
Brzozowski also shewed how to go from the state machine to strings and expressions...
|
|
||||||
|
|
||||||
Each of these states is just a name for a Brzozowskian RE, and so, other than the initial state `a`, they can can be described in terms of the derivative-with-respect-to-N of some other state/RE:
|
|
||||||
|
|
||||||
c = d1(a)
|
|
||||||
b = d0(a)
|
|
||||||
b = d0(c)
|
|
||||||
...
|
|
||||||
i = d0(j)
|
|
||||||
j = d1(i)
|
|
||||||
|
|
||||||
Consider:
|
|
||||||
|
|
||||||
c = d1(a)
|
|
||||||
b = d0(c)
|
|
||||||
|
|
||||||
Substituting:
|
|
||||||
|
|
||||||
b = d0(d1(a))
|
|
||||||
|
|
||||||
Unwrapping:
|
|
||||||
|
|
||||||
|
|
||||||
b = d10(a)
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
j = d1(d0(j))
|
|
||||||
|
|
||||||
Unwrapping:
|
|
||||||
|
|
||||||
j = d1(d0(j)) = d01(j)
|
|
||||||
|
|
||||||
We have a loop or "fixed point".
|
|
||||||
|
|
||||||
j = d01(j) = d0101(j) = d010101(j) = ...
|
|
||||||
|
|
||||||
hmm...
|
|
||||||
|
|
||||||
j = (01)*
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,946 +0,0 @@
|
||||||
∂RE
|
|
||||||
===
|
|
||||||
|
|
||||||
Brzozowski’s Derivatives of Regular Expressions
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
Legend:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
∧ intersection
|
|
||||||
∨ union
|
|
||||||
∘ concatenation (see below)
|
|
||||||
¬ complement
|
|
||||||
ϕ empty set (aka ∅)
|
|
||||||
λ singleton set containing just the empty string
|
|
||||||
I set of all letters in alphabet
|
|
||||||
|
|
||||||
Derivative of a set ``R`` of strings and a string ``a``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
∂a(R)
|
|
||||||
|
|
||||||
∂a(a) → λ
|
|
||||||
∂a(λ) → ϕ
|
|
||||||
∂a(ϕ) → ϕ
|
|
||||||
∂a(¬a) → ϕ
|
|
||||||
∂a(R*) → ∂a(R)∘R*
|
|
||||||
∂a(¬R) → ¬∂a(R)
|
|
||||||
∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
|
|
||||||
∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
|
|
||||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
|
|
||||||
∂ab(R) = ∂b(∂a(R))
|
|
||||||
|
|
||||||
Auxiliary predicate function ``δ`` (I call it ``nully``) returns either
|
|
||||||
``λ`` if ``λ ⊆ R`` or ``ϕ`` otherwise:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
δ(a) → ϕ
|
|
||||||
δ(λ) → λ
|
|
||||||
δ(ϕ) → ϕ
|
|
||||||
δ(R*) → λ
|
|
||||||
δ(¬R) δ(R)≟ϕ → λ
|
|
||||||
δ(¬R) δ(R)≟λ → ϕ
|
|
||||||
δ(R∘S) → δ(R) ∧ δ(S)
|
|
||||||
δ(R ∧ S) → δ(R) ∧ δ(S)
|
|
||||||
δ(R ∨ S) → δ(R) ∨ δ(S)
|
|
||||||
|
|
||||||
Some rules we will use later for “compaction”:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
R ∧ ϕ = ϕ ∧ R = ϕ
|
|
||||||
|
|
||||||
R ∧ I = I ∧ R = R
|
|
||||||
|
|
||||||
R ∨ ϕ = ϕ ∨ R = R
|
|
||||||
|
|
||||||
R ∨ I = I ∨ R = I
|
|
||||||
|
|
||||||
R∘ϕ = ϕ∘R = ϕ
|
|
||||||
|
|
||||||
R∘λ = λ∘R = R
|
|
||||||
|
|
||||||
Concatination of sets: for two sets A and B the set A∘B is defined as:
|
|
||||||
|
|
||||||
{a∘b for a in A for b in B}
|
|
||||||
|
|
||||||
E.g.:
|
|
||||||
|
|
||||||
{‘a’, ‘b’}∘{‘c’, ‘d’} → {‘ac’, ‘ad’, ‘bc’, ‘bd’}
|
|
||||||
|
|
||||||
Implementation
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from functools import partial as curry
|
|
||||||
from itertools import product
|
|
||||||
|
|
||||||
``ϕ`` and ``λ``
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The empty set and the set of just the empty string.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
phi = frozenset() # ϕ
|
|
||||||
y = frozenset({''}) # λ
|
|
||||||
|
|
||||||
Two-letter Alphabet
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
I’m only going to use two symbols (at first) becaase this is enough to
|
|
||||||
illustrate the algorithm and because you can represent any other
|
|
||||||
alphabet with two symbols (if you had to.)
|
|
||||||
|
|
||||||
I chose the names ``O`` and ``l`` (uppercase “o” and lowercase “L”) to
|
|
||||||
look like ``0`` and ``1`` (zero and one) respectively.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
syms = O, l = frozenset({'0'}), frozenset({'1'})
|
|
||||||
|
|
||||||
Representing Regular Expressions
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
To represent REs in Python I’m going to use tagged tuples. A *regular
|
|
||||||
expression* is one of:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
O
|
|
||||||
l
|
|
||||||
(KSTAR, R)
|
|
||||||
(NOT, R)
|
|
||||||
(AND, R, S)
|
|
||||||
(CONS, R, S)
|
|
||||||
(OR, R, S)
|
|
||||||
|
|
||||||
Where ``R`` and ``S`` stand for *regular expressions*.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
AND, CONS, KSTAR, NOT, OR = 'and cons * not or'.split() # Tags are just strings.
|
|
||||||
|
|
||||||
Because they are formed of ``frozenset``, ``tuple`` and ``str`` objects
|
|
||||||
only, these datastructures are immutable.
|
|
||||||
|
|
||||||
String Representation of RE Datastructures
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def stringy(re):
|
|
||||||
'''
|
|
||||||
Return a nice string repr for a regular expression datastructure.
|
|
||||||
'''
|
|
||||||
if re == I: return '.'
|
|
||||||
if re in syms: return next(iter(re))
|
|
||||||
if re == y: return '^'
|
|
||||||
if re == phi: return 'X'
|
|
||||||
|
|
||||||
assert isinstance(re, tuple), repr(re)
|
|
||||||
tag = re[0]
|
|
||||||
|
|
||||||
if tag == KSTAR:
|
|
||||||
body = stringy(re[1])
|
|
||||||
if not body: return body
|
|
||||||
if len(body) > 1: return '(' + body + ")*"
|
|
||||||
return body + '*'
|
|
||||||
|
|
||||||
if tag == NOT:
|
|
||||||
body = stringy(re[1])
|
|
||||||
if not body: return body
|
|
||||||
if len(body) > 1: return '(' + body + ")'"
|
|
||||||
return body + "'"
|
|
||||||
|
|
||||||
r, s = stringy(re[1]), stringy(re[2])
|
|
||||||
if tag == CONS: return r + s
|
|
||||||
if tag == OR: return '%s | %s' % (r, s)
|
|
||||||
if tag == AND: return '(%s) & (%s)' % (r, s)
|
|
||||||
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
``I``
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
Match anything. Often spelled “.”
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
I = (0|1)*
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
I = (KSTAR, (OR, O, l))
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
print stringy(I)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
``(.111.) & (.01 + 11*)'``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The example expression from Brzozowski:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
(.111.) & (.01 + 11*)'
|
|
||||||
a & (b + c)'
|
|
||||||
|
|
||||||
Note that it contains one of everything.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
a = (CONS, I, (CONS, l, (CONS, l, (CONS, l, I))))
|
|
||||||
b = (CONS, I, (CONS, O, l))
|
|
||||||
c = (CONS, l, (KSTAR, l))
|
|
||||||
it = (AND, a, (NOT, (OR, b, c)))
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
print stringy(it)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
(.111.) & ((.01 | 11*)')
|
|
||||||
|
|
||||||
|
|
||||||
``nully()``
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
Let’s get that auxiliary predicate function ``δ`` out of the way.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def nully(R):
|
|
||||||
'''
|
|
||||||
δ - Return λ if λ ⊆ R otherwise ϕ.
|
|
||||||
'''
|
|
||||||
|
|
||||||
# δ(a) → ϕ
|
|
||||||
# δ(ϕ) → ϕ
|
|
||||||
if R in syms or R == phi:
|
|
||||||
return phi
|
|
||||||
|
|
||||||
# δ(λ) → λ
|
|
||||||
if R == y:
|
|
||||||
return y
|
|
||||||
|
|
||||||
tag = R[0]
|
|
||||||
|
|
||||||
# δ(R*) → λ
|
|
||||||
if tag == KSTAR:
|
|
||||||
return y
|
|
||||||
|
|
||||||
# δ(¬R) δ(R)≟ϕ → λ
|
|
||||||
# δ(¬R) δ(R)≟λ → ϕ
|
|
||||||
if tag == NOT:
|
|
||||||
return phi if nully(R[1]) else y
|
|
||||||
|
|
||||||
# δ(R∘S) → δ(R) ∧ δ(S)
|
|
||||||
# δ(R ∧ S) → δ(R) ∧ δ(S)
|
|
||||||
# δ(R ∨ S) → δ(R) ∨ δ(S)
|
|
||||||
r, s = nully(R[1]), nully(R[2])
|
|
||||||
return r & s if tag in {AND, CONS} else r | s
|
|
||||||
|
|
||||||
No “Compaction”
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This is the straightforward version with no “compaction”. It works fine,
|
|
||||||
but does waaaay too much work because the expressions grow each
|
|
||||||
derivation.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def D(symbol):
|
|
||||||
|
|
||||||
def derv(R):
|
|
||||||
|
|
||||||
# ∂a(a) → λ
|
|
||||||
if R == {symbol}:
|
|
||||||
return y
|
|
||||||
|
|
||||||
# ∂a(λ) → ϕ
|
|
||||||
# ∂a(ϕ) → ϕ
|
|
||||||
# ∂a(¬a) → ϕ
|
|
||||||
if R == y or R == phi or R in syms:
|
|
||||||
return phi
|
|
||||||
|
|
||||||
tag = R[0]
|
|
||||||
|
|
||||||
# ∂a(R*) → ∂a(R)∘R*
|
|
||||||
if tag == KSTAR:
|
|
||||||
return (CONS, derv(R[1]), R)
|
|
||||||
|
|
||||||
# ∂a(¬R) → ¬∂a(R)
|
|
||||||
if tag == NOT:
|
|
||||||
return (NOT, derv(R[1]))
|
|
||||||
|
|
||||||
r, s = R[1:]
|
|
||||||
|
|
||||||
# ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
|
|
||||||
if tag == CONS:
|
|
||||||
A = (CONS, derv(r), s) # A = ∂a(R)∘S
|
|
||||||
# A ∨ δ(R) ∘ ∂a(S)
|
|
||||||
# A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S)
|
|
||||||
# A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A
|
|
||||||
return (OR, A, derv(s)) if nully(r) else A
|
|
||||||
|
|
||||||
# ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
|
|
||||||
# ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
return (tag, derv(r), derv(s))
|
|
||||||
|
|
||||||
return derv
|
|
||||||
|
|
||||||
Compaction Rules
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def _compaction_rule(relation, one, zero, a, b):
|
|
||||||
return (
|
|
||||||
b if a == one else # R*1 = 1*R = R
|
|
||||||
a if b == one else
|
|
||||||
zero if a == zero or b == zero else # R*0 = 0*R = 0
|
|
||||||
(relation, a, b)
|
|
||||||
)
|
|
||||||
|
|
||||||
An elegant symmetry.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
# R ∧ I = I ∧ R = R
|
|
||||||
# R ∧ ϕ = ϕ ∧ R = ϕ
|
|
||||||
_and = curry(_compaction_rule, AND, I, phi)
|
|
||||||
|
|
||||||
# R ∨ ϕ = ϕ ∨ R = R
|
|
||||||
# R ∨ I = I ∨ R = I
|
|
||||||
_or = curry(_compaction_rule, OR, phi, I)
|
|
||||||
|
|
||||||
# R∘λ = λ∘R = R
|
|
||||||
# R∘ϕ = ϕ∘R = ϕ
|
|
||||||
_cons = curry(_compaction_rule, CONS, y, phi)
|
|
||||||
|
|
||||||
Memoizing
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
We can save re-processing by remembering results we have already
|
|
||||||
computed. RE datastructures are immutable and the ``derv()`` functions
|
|
||||||
are *pure* so this is fine.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
class Memo(object):
|
|
||||||
|
|
||||||
def __init__(self, f):
|
|
||||||
self.f = f
|
|
||||||
self.calls = self.hits = 0
|
|
||||||
self.mem = {}
|
|
||||||
|
|
||||||
def __call__(self, key):
|
|
||||||
self.calls += 1
|
|
||||||
try:
|
|
||||||
result = self.mem[key]
|
|
||||||
self.hits += 1
|
|
||||||
except KeyError:
|
|
||||||
result = self.mem[key] = self.f(key)
|
|
||||||
return result
|
|
||||||
|
|
||||||
With “Compaction”
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This version uses the rules above to perform compaction. It keeps the
|
|
||||||
expressions from growing too large.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def D_compaction(symbol):
|
|
||||||
|
|
||||||
@Memo
|
|
||||||
def derv(R):
|
|
||||||
|
|
||||||
# ∂a(a) → λ
|
|
||||||
if R == {symbol}:
|
|
||||||
return y
|
|
||||||
|
|
||||||
# ∂a(λ) → ϕ
|
|
||||||
# ∂a(ϕ) → ϕ
|
|
||||||
# ∂a(¬a) → ϕ
|
|
||||||
if R == y or R == phi or R in syms:
|
|
||||||
return phi
|
|
||||||
|
|
||||||
tag = R[0]
|
|
||||||
|
|
||||||
# ∂a(R*) → ∂a(R)∘R*
|
|
||||||
if tag == KSTAR:
|
|
||||||
return _cons(derv(R[1]), R)
|
|
||||||
|
|
||||||
# ∂a(¬R) → ¬∂a(R)
|
|
||||||
if tag == NOT:
|
|
||||||
return (NOT, derv(R[1]))
|
|
||||||
|
|
||||||
r, s = R[1:]
|
|
||||||
|
|
||||||
# ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
|
|
||||||
if tag == CONS:
|
|
||||||
A = _cons(derv(r), s) # A = ∂a(r)∘s
|
|
||||||
# A ∨ δ(R) ∘ ∂a(S)
|
|
||||||
# A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S)
|
|
||||||
# A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A
|
|
||||||
return _or(A, derv(s)) if nully(r) else A
|
|
||||||
|
|
||||||
# ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
|
|
||||||
# ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
dr, ds = derv(r), derv(s)
|
|
||||||
return _and(dr, ds) if tag == AND else _or(dr, ds)
|
|
||||||
|
|
||||||
return derv
|
|
||||||
|
|
||||||
Let’s try it out…
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
(FIXME: redo.)
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
o, z = D_compaction('0'), D_compaction('1')
|
|
||||||
REs = set()
|
|
||||||
N = 5
|
|
||||||
names = list(product(*(N * [(0, 1)])))
|
|
||||||
dervs = list(product(*(N * [(o, z)])))
|
|
||||||
for name, ds in zip(names, dervs):
|
|
||||||
R = it
|
|
||||||
ds = list(ds)
|
|
||||||
while ds:
|
|
||||||
R = ds.pop()(R)
|
|
||||||
if R == phi or R == I:
|
|
||||||
break
|
|
||||||
REs.add(R)
|
|
||||||
|
|
||||||
print stringy(it) ; print
|
|
||||||
print o.hits, '/', o.calls
|
|
||||||
print z.hits, '/', z.calls
|
|
||||||
print
|
|
||||||
for s in sorted(map(stringy, REs), key=lambda n: (len(n), n)):
|
|
||||||
print s
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
(.111.) & ((.01 | 11*)')
|
|
||||||
|
|
||||||
92 / 122
|
|
||||||
92 / 122
|
|
||||||
|
|
||||||
(.01)'
|
|
||||||
(.01 | 1)'
|
|
||||||
(.01 | ^)'
|
|
||||||
(.01 | 1*)'
|
|
||||||
(.111.) & ((.01 | 1)')
|
|
||||||
(.111. | 11.) & ((.01 | ^)')
|
|
||||||
(.111. | 11. | 1.) & ((.01)')
|
|
||||||
(.111. | 11.) & ((.01 | 1*)')
|
|
||||||
(.111. | 11. | 1.) & ((.01 | 1*)')
|
|
||||||
|
|
||||||
|
|
||||||
Should match:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
(.111.) & ((.01 | 11*)')
|
|
||||||
|
|
||||||
92 / 122
|
|
||||||
92 / 122
|
|
||||||
|
|
||||||
(.01 )'
|
|
||||||
(.01 | 1 )'
|
|
||||||
(.01 | ^ )'
|
|
||||||
(.01 | 1*)'
|
|
||||||
(.111.) & ((.01 | 1 )')
|
|
||||||
(.111. | 11.) & ((.01 | ^ )')
|
|
||||||
(.111. | 11.) & ((.01 | 1*)')
|
|
||||||
(.111. | 11. | 1.) & ((.01 )')
|
|
||||||
(.111. | 11. | 1.) & ((.01 | 1*)')
|
|
||||||
|
|
||||||
Larger Alphabets
|
|
||||||
----------------
|
|
||||||
|
|
||||||
We could parse larger alphabets by defining patterns for e.g. each byte
|
|
||||||
of the ASCII code. Or we can generalize this code. If you study the code
|
|
||||||
above you’ll see that we never use the “set-ness” of the symbols ``O``
|
|
||||||
and ``l``. The only time Python set operators (``&`` and ``|``) appear
|
|
||||||
is in the ``nully()`` function, and there they operate on (recursively
|
|
||||||
computed) outputs of that function, never ``O`` and ``l``.
|
|
||||||
|
|
||||||
What if we try:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
(OR, O, l)
|
|
||||||
|
|
||||||
∂1((OR, O, l))
|
|
||||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
∂1(O) ∨ ∂1(l)
|
|
||||||
∂a(¬a) → ϕ
|
|
||||||
ϕ ∨ ∂1(l)
|
|
||||||
∂a(a) → λ
|
|
||||||
ϕ ∨ λ
|
|
||||||
ϕ ∨ R = R
|
|
||||||
λ
|
|
||||||
|
|
||||||
And compare it to:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
{'0', '1')
|
|
||||||
|
|
||||||
∂1({'0', '1'))
|
|
||||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
|
||||||
∂1({'0')) ∨ ∂1({'1'))
|
|
||||||
∂a(¬a) → ϕ
|
|
||||||
ϕ ∨ ∂1({'1'))
|
|
||||||
∂a(a) → λ
|
|
||||||
ϕ ∨ λ
|
|
||||||
ϕ ∨ R = R
|
|
||||||
λ
|
|
||||||
|
|
||||||
This suggests that we should be able to alter the functions above to
|
|
||||||
detect sets and deal with them appropriately. Exercise for the Reader
|
|
||||||
for now.
|
|
||||||
|
|
||||||
State Machine
|
|
||||||
-------------
|
|
||||||
|
|
||||||
We can drive the regular expressions to flesh out the underlying state
|
|
||||||
machine transition table.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
.111. & (.01 + 11*)'
|
|
||||||
|
|
||||||
Says, “Three or more 1’s and not ending in 01 nor composed of all 1’s.”
|
|
||||||
|
|
||||||
.. figure:: attachment:omg.svg
|
|
||||||
:alt: omg.svg
|
|
||||||
|
|
||||||
omg.svg
|
|
||||||
|
|
||||||
Start at ``a`` and follow the transition arrows according to their
|
|
||||||
labels. Accepting states have a double outline. (Graphic generated with
|
|
||||||
`Dot from Graphviz <http://www.graphviz.org/>`__.) You’ll see that only
|
|
||||||
paths that lead to one of the accepting states will match the regular
|
|
||||||
expression. All other paths will terminate at one of the non-accepting
|
|
||||||
states.
|
|
||||||
|
|
||||||
There’s a happy path to ``g`` along 111:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a→c→e→g
|
|
||||||
|
|
||||||
After you reach ``g`` you’re stuck there eating 1’s until you see a 0,
|
|
||||||
which takes you to the ``i→j→i|i→j→h→i`` “trap”. You can’t reach any
|
|
||||||
other states from those two loops.
|
|
||||||
|
|
||||||
If you see a 0 before you see 111 you will reach ``b``, which forms
|
|
||||||
another “trap” with ``d`` and ``f``. The only way out is another happy
|
|
||||||
path along 111 to ``h``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b→d→f→h
|
|
||||||
|
|
||||||
Once you have reached ``h`` you can see as many 1’s or as many 0’ in a
|
|
||||||
row and still be either still at ``h`` (for 1’s) or move to ``i`` (for
|
|
||||||
0’s). If you find yourself at ``i`` you can see as many 0’s, or
|
|
||||||
repetitions of 10, as there are, but if you see just a 1 you move to
|
|
||||||
``j``.
|
|
||||||
|
|
||||||
RE to FSM
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
So how do we get the state machine from the regular expression?
|
|
||||||
|
|
||||||
It turns out that each RE is effectively a state, and each arrow points
|
|
||||||
to the derivative RE in respect to the arrow’s symbol.
|
|
||||||
|
|
||||||
If we label the initial RE ``a``, we can say:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a --0--> ∂0(a)
|
|
||||||
a --1--> ∂1(a)
|
|
||||||
|
|
||||||
And so on, each new unique RE is a new state in the FSM table.
|
|
||||||
|
|
||||||
Here are the derived REs at each state:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a = (.111.) & ((.01 | 11*)')
|
|
||||||
b = (.111.) & ((.01 | 1)')
|
|
||||||
c = (.111. | 11.) & ((.01 | 1*)')
|
|
||||||
d = (.111. | 11.) & ((.01 | ^)')
|
|
||||||
e = (.111. | 11. | 1.) & ((.01 | 1*)')
|
|
||||||
f = (.111. | 11. | 1.) & ((.01)')
|
|
||||||
g = (.01 | 1*)'
|
|
||||||
h = (.01)'
|
|
||||||
i = (.01 | 1)'
|
|
||||||
j = (.01 | ^)'
|
|
||||||
|
|
||||||
You can see the one-way nature of the ``g`` state and the ``hij`` “trap”
|
|
||||||
in the way that the ``.111.`` on the left-hand side of the ``&``
|
|
||||||
disappears once it has been matched.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
from pprint import pprint
|
|
||||||
from string import ascii_lowercase
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
d0, d1 = D_compaction('0'), D_compaction('1')
|
|
||||||
|
|
||||||
``explore()``
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def explore(re):
|
|
||||||
|
|
||||||
# Don't have more than 26 states...
|
|
||||||
names = defaultdict(iter(ascii_lowercase).next)
|
|
||||||
|
|
||||||
table, accepting = dict(), set()
|
|
||||||
|
|
||||||
to_check = {re}
|
|
||||||
while to_check:
|
|
||||||
|
|
||||||
re = to_check.pop()
|
|
||||||
state_name = names[re]
|
|
||||||
|
|
||||||
if (state_name, 0) in table:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if nully(re):
|
|
||||||
accepting.add(state_name)
|
|
||||||
|
|
||||||
o, i = d0(re), d1(re)
|
|
||||||
table[state_name, 0] = names[o] ; to_check.add(o)
|
|
||||||
table[state_name, 1] = names[i] ; to_check.add(i)
|
|
||||||
|
|
||||||
return table, accepting
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
table, accepting = explore(it)
|
|
||||||
table
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
{('a', 0): 'b',
|
|
||||||
('a', 1): 'c',
|
|
||||||
('b', 0): 'b',
|
|
||||||
('b', 1): 'd',
|
|
||||||
('c', 0): 'b',
|
|
||||||
('c', 1): 'e',
|
|
||||||
('d', 0): 'b',
|
|
||||||
('d', 1): 'f',
|
|
||||||
('e', 0): 'b',
|
|
||||||
('e', 1): 'g',
|
|
||||||
('f', 0): 'b',
|
|
||||||
('f', 1): 'h',
|
|
||||||
('g', 0): 'i',
|
|
||||||
('g', 1): 'g',
|
|
||||||
('h', 0): 'i',
|
|
||||||
('h', 1): 'h',
|
|
||||||
('i', 0): 'i',
|
|
||||||
('i', 1): 'j',
|
|
||||||
('j', 0): 'i',
|
|
||||||
('j', 1): 'h'}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
accepting
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
{'h', 'i'}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Generate Diagram
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Once we have the FSM table and the set of accepting states we can
|
|
||||||
generate the diagram above.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
_template = '''\
|
|
||||||
digraph finite_state_machine {
|
|
||||||
rankdir=LR;
|
|
||||||
size="8,5"
|
|
||||||
node [shape = doublecircle]; %s;
|
|
||||||
node [shape = circle];
|
|
||||||
%s
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
|
|
||||||
def link(fr, nm, label):
|
|
||||||
return ' %s -> %s [ label = "%s" ];' % (fr, nm, label)
|
|
||||||
|
|
||||||
|
|
||||||
def make_graph(table, accepting):
|
|
||||||
return _template % (
|
|
||||||
' '.join(accepting),
|
|
||||||
'\n'.join(
|
|
||||||
link(from_, to, char)
|
|
||||||
for (from_, char), (to) in sorted(table.iteritems())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
print make_graph(table, accepting)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
digraph finite_state_machine {
|
|
||||||
rankdir=LR;
|
|
||||||
size="8,5"
|
|
||||||
node [shape = doublecircle]; i h;
|
|
||||||
node [shape = circle];
|
|
||||||
a -> b [ label = "0" ];
|
|
||||||
a -> c [ label = "1" ];
|
|
||||||
b -> b [ label = "0" ];
|
|
||||||
b -> d [ label = "1" ];
|
|
||||||
c -> b [ label = "0" ];
|
|
||||||
c -> e [ label = "1" ];
|
|
||||||
d -> b [ label = "0" ];
|
|
||||||
d -> f [ label = "1" ];
|
|
||||||
e -> b [ label = "0" ];
|
|
||||||
e -> g [ label = "1" ];
|
|
||||||
f -> b [ label = "0" ];
|
|
||||||
f -> h [ label = "1" ];
|
|
||||||
g -> i [ label = "0" ];
|
|
||||||
g -> g [ label = "1" ];
|
|
||||||
h -> i [ label = "0" ];
|
|
||||||
h -> h [ label = "1" ];
|
|
||||||
i -> i [ label = "0" ];
|
|
||||||
i -> j [ label = "1" ];
|
|
||||||
j -> i [ label = "0" ];
|
|
||||||
j -> h [ label = "1" ];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Drive a FSM
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
There are *lots* of FSM libraries already. Once you have the state
|
|
||||||
transition table they should all be straightforward to use. State
|
|
||||||
Machine code is very simple. Just for fun, here is an implementation in
|
|
||||||
Python that imitates what “compiled” FSM code might look like in an
|
|
||||||
“unrolled” form. Most FSM code uses a little driver loop and a table
|
|
||||||
datastructure, the code below instead acts like JMP instructions
|
|
||||||
(“jump”, or GOTO in higher-level-but-still-low-level languages) to
|
|
||||||
hard-code the information in the table into a little patch of branches.
|
|
||||||
|
|
||||||
Trampoline Function
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Python has no GOTO statement but we can fake it with a “trampoline”
|
|
||||||
function.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def trampoline(input_, jump_from, accepting):
|
|
||||||
I = iter(input_)
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
bounce_to = jump_from(I)
|
|
||||||
except StopIteration:
|
|
||||||
return jump_from in accepting
|
|
||||||
jump_from = bounce_to
|
|
||||||
|
|
||||||
Stream Functions
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Little helpers to process the iterator of our data (a “stream” of “1”
|
|
||||||
and “0” characters, not bits.)
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
getch = lambda I: int(next(I))
|
|
||||||
|
|
||||||
|
|
||||||
def _1(I):
|
|
||||||
'''Loop on ones.'''
|
|
||||||
while getch(I): pass
|
|
||||||
|
|
||||||
|
|
||||||
def _0(I):
|
|
||||||
'''Loop on zeros.'''
|
|
||||||
while not getch(I): pass
|
|
||||||
|
|
||||||
A Finite State Machine
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
With those preliminaries out of the way, from the state table of
|
|
||||||
``.111. & (.01 + 11*)'`` we can immediately write down state machine
|
|
||||||
code. (You have to imagine that these are GOTO statements in C or
|
|
||||||
branches in assembly and that the state names are branch destination
|
|
||||||
labels.)
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
a = lambda I: c if getch(I) else b
|
|
||||||
b = lambda I: _0(I) or d
|
|
||||||
c = lambda I: e if getch(I) else b
|
|
||||||
d = lambda I: f if getch(I) else b
|
|
||||||
e = lambda I: g if getch(I) else b
|
|
||||||
f = lambda I: h if getch(I) else b
|
|
||||||
g = lambda I: _1(I) or i
|
|
||||||
h = lambda I: _1(I) or i
|
|
||||||
i = lambda I: _0(I) or j
|
|
||||||
j = lambda I: h if getch(I) else i
|
|
||||||
|
|
||||||
Note that the implementations of ``h`` and ``g`` are identical ergo
|
|
||||||
``h = g`` and we could eliminate one in the code but ``h`` is an
|
|
||||||
accepting state and ``g`` isn’t.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def acceptable(input_):
|
|
||||||
return trampoline(input_, a, {h, i})
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
for n in range(2**5):
|
|
||||||
s = bin(n)[2:]
|
|
||||||
print '%05s' % s, acceptable(s)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0 False
|
|
||||||
1 False
|
|
||||||
10 False
|
|
||||||
11 False
|
|
||||||
100 False
|
|
||||||
101 False
|
|
||||||
110 False
|
|
||||||
111 False
|
|
||||||
1000 False
|
|
||||||
1001 False
|
|
||||||
1010 False
|
|
||||||
1011 False
|
|
||||||
1100 False
|
|
||||||
1101 False
|
|
||||||
1110 True
|
|
||||||
1111 False
|
|
||||||
10000 False
|
|
||||||
10001 False
|
|
||||||
10010 False
|
|
||||||
10011 False
|
|
||||||
10100 False
|
|
||||||
10101 False
|
|
||||||
10110 False
|
|
||||||
10111 True
|
|
||||||
11000 False
|
|
||||||
11001 False
|
|
||||||
11010 False
|
|
||||||
11011 False
|
|
||||||
11100 True
|
|
||||||
11101 False
|
|
||||||
11110 True
|
|
||||||
11111 False
|
|
||||||
|
|
||||||
|
|
||||||
Reversing the Derivatives to Generate Matching Strings
|
|
||||||
------------------------------------------------------
|
|
||||||
|
|
||||||
(UNFINISHED) Brzozowski also shewed how to go from the state machine to
|
|
||||||
strings and expressions…
|
|
||||||
|
|
||||||
Each of these states is just a name for a Brzozowskian RE, and so, other
|
|
||||||
than the initial state ``a``, they can can be described in terms of the
|
|
||||||
derivative-with-respect-to-N of some other state/RE:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
c = d1(a)
|
|
||||||
b = d0(a)
|
|
||||||
b = d0(c)
|
|
||||||
...
|
|
||||||
i = d0(j)
|
|
||||||
j = d1(i)
|
|
||||||
|
|
||||||
Consider:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
c = d1(a)
|
|
||||||
b = d0(c)
|
|
||||||
|
|
||||||
Substituting:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b = d0(d1(a))
|
|
||||||
|
|
||||||
Unwrapping:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b = d10(a)
|
|
||||||
|
|
||||||
’’’
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
j = d1(d0(j))
|
|
||||||
|
|
||||||
Unwrapping:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
j = d1(d0(j)) = d01(j)
|
|
||||||
|
|
||||||
We have a loop or “fixed point”.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
j = d01(j) = d0101(j) = d010101(j) = ...
|
|
||||||
|
|
||||||
hmm…
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
j = (01)*
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,506 +0,0 @@
|
||||||
# Using `x` to Generate Values
|
|
||||||
|
|
||||||
Cf. jp-reprod.html
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
Consider the `x` combinator:
|
|
||||||
|
|
||||||
x == dup i
|
|
||||||
|
|
||||||
We can apply it to a quoted program consisting of some value `a` and some function `B`:
|
|
||||||
|
|
||||||
[a B] x
|
|
||||||
[a B] a B
|
|
||||||
|
|
||||||
Let `B` function `swap` the `a` with the quote and run some function `C` on it to generate a new value `b`:
|
|
||||||
|
|
||||||
B == swap [C] dip
|
|
||||||
|
|
||||||
[a B] a B
|
|
||||||
[a B] a swap [C] dip
|
|
||||||
a [a B] [C] dip
|
|
||||||
a C [a B]
|
|
||||||
b [a B]
|
|
||||||
|
|
||||||
Now discard the quoted `a` with `rest` then `cons` `b`:
|
|
||||||
|
|
||||||
b [a B] rest cons
|
|
||||||
b [B] cons
|
|
||||||
[b B]
|
|
||||||
|
|
||||||
Altogether, this is the definition of `B`:
|
|
||||||
|
|
||||||
B == swap [C] dip rest cons
|
|
||||||
|
|
||||||
We can make a generator for the Natural numbers (0, 1, 2, ...) by using `0` for `a` and `[dup ++]` for `[C]`:
|
|
||||||
|
|
||||||
[0 swap [dup ++] dip rest cons]
|
|
||||||
|
|
||||||
Let's try it:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[0 swap [dup ++] dip rest cons] x')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [0 swap [dup ++] dip rest cons] x
|
|
||||||
[0 swap [dup ++] dip rest cons] . x
|
|
||||||
[0 swap [dup ++] dip rest cons] . 0 swap [dup ++] dip rest cons
|
|
||||||
[0 swap [dup ++] dip rest cons] 0 . swap [dup ++] dip rest cons
|
|
||||||
0 [0 swap [dup ++] dip rest cons] . [dup ++] dip rest cons
|
|
||||||
0 [0 swap [dup ++] dip rest cons] [dup ++] . dip rest cons
|
|
||||||
0 . dup ++ [0 swap [dup ++] dip rest cons] rest cons
|
|
||||||
0 0 . ++ [0 swap [dup ++] dip rest cons] rest cons
|
|
||||||
0 1 . [0 swap [dup ++] dip rest cons] rest cons
|
|
||||||
0 1 [0 swap [dup ++] dip rest cons] . rest cons
|
|
||||||
0 1 [swap [dup ++] dip rest cons] . cons
|
|
||||||
0 [1 swap [dup ++] dip rest cons] .
|
|
||||||
|
|
||||||
|
|
||||||
After one application of `x` the quoted program contains `1` and `0` is below it on the stack.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 swap [dup ++] dip rest cons] x x x x x pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
0 1 2 3 4
|
|
||||||
|
|
||||||
|
|
||||||
## `direco`
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('direco == dip rest cons')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[0 swap [dup ++] direco] x')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [0 swap [dup ++] direco] x
|
|
||||||
[0 swap [dup ++] direco] . x
|
|
||||||
[0 swap [dup ++] direco] . 0 swap [dup ++] direco
|
|
||||||
[0 swap [dup ++] direco] 0 . swap [dup ++] direco
|
|
||||||
0 [0 swap [dup ++] direco] . [dup ++] direco
|
|
||||||
0 [0 swap [dup ++] direco] [dup ++] . direco
|
|
||||||
0 [0 swap [dup ++] direco] [dup ++] . dip rest cons
|
|
||||||
0 . dup ++ [0 swap [dup ++] direco] rest cons
|
|
||||||
0 0 . ++ [0 swap [dup ++] direco] rest cons
|
|
||||||
0 1 . [0 swap [dup ++] direco] rest cons
|
|
||||||
0 1 [0 swap [dup ++] direco] . rest cons
|
|
||||||
0 1 [swap [dup ++] direco] . cons
|
|
||||||
0 [1 swap [dup ++] direco] .
|
|
||||||
|
|
||||||
|
|
||||||
## Making Generators
|
|
||||||
We want to define a function that accepts `a` and `[C]` and builds our quoted program:
|
|
||||||
|
|
||||||
a [C] G
|
|
||||||
-------------------------
|
|
||||||
[a swap [C] direco]
|
|
||||||
|
|
||||||
Working in reverse:
|
|
||||||
|
|
||||||
[a swap [C] direco] cons
|
|
||||||
a [swap [C] direco] concat
|
|
||||||
a [swap] [[C] direco] swap
|
|
||||||
a [[C] direco] [swap]
|
|
||||||
a [C] [direco] cons [swap]
|
|
||||||
|
|
||||||
Reading from the bottom up:
|
|
||||||
|
|
||||||
G == [direco] cons [swap] swap concat cons
|
|
||||||
G == [direco] cons [swap] swoncat cons
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('G == [direco] cons [swap] swoncat cons')
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's try it out:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('0 [dup ++] G')
|
|
||||||
```
|
|
||||||
|
|
||||||
[0 swap [dup ++] direco]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('0 [dup ++] G x x x pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
0 1 2
|
|
||||||
|
|
||||||
|
|
||||||
### Powers of 2
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('1 [dup 1 <<] G x x x x x x x x x pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
1 2 4 8 16 32 64 128 256
|
|
||||||
|
|
||||||
|
|
||||||
### `[x] times`
|
|
||||||
If we have one of these quoted programs we can drive it using `times` with the `x` combinator.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('23 [dup ++] G 5 [x] times')
|
|
||||||
```
|
|
||||||
|
|
||||||
23 24 25 26 27 [28 swap [dup ++] direco]
|
|
||||||
|
|
||||||
|
|
||||||
## Generating Multiples of Three and Five
|
|
||||||
Look at the treatment of the Project Euler Problem One in the "Developing a Program" notebook and you'll see that we might be interested in generating an endless cycle of:
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3
|
|
||||||
|
|
||||||
To do this we want to encode the numbers as pairs of bits in a single int:
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3
|
|
||||||
0b 11 10 01 11 01 10 11 == 14811
|
|
||||||
|
|
||||||
And pick them off by masking with 3 (binary 11) and then shifting the int right two bits.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1.1 == dup [3 &] dip 2 >>')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('14811 PE1.1')
|
|
||||||
```
|
|
||||||
|
|
||||||
. 14811 PE1.1
|
|
||||||
14811 . PE1.1
|
|
||||||
14811 . dup [3 &] dip 2 >>
|
|
||||||
14811 14811 . [3 &] dip 2 >>
|
|
||||||
14811 14811 [3 &] . dip 2 >>
|
|
||||||
14811 . 3 & 14811 2 >>
|
|
||||||
14811 3 . & 14811 2 >>
|
|
||||||
3 . 14811 2 >>
|
|
||||||
3 14811 . 2 >>
|
|
||||||
3 14811 2 . >>
|
|
||||||
3 3702 .
|
|
||||||
|
|
||||||
|
|
||||||
If we plug `14811` and `[PE1.1]` into our generator form...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('14811 [PE1.1] G')
|
|
||||||
```
|
|
||||||
|
|
||||||
[14811 swap [PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
...we get a generator that works for seven cycles before it reaches zero:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[14811 swap [PE1.1] direco] 7 [x] times')
|
|
||||||
```
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 [0 swap [PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
### Reset at Zero
|
|
||||||
We need a function that checks if the int has reached zero and resets it if so.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1.1.check == dup [pop 14811] [] branch')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('14811 [PE1.1.check PE1.1] G')
|
|
||||||
```
|
|
||||||
|
|
||||||
[14811 swap [PE1.1.check PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[14811 swap [PE1.1.check PE1.1] direco] 21 [x] times')
|
|
||||||
```
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 [0 swap [PE1.1.check PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
(It would be more efficient to reset the int every seven cycles but that's a little beyond the scope of this article. This solution does extra work, but not much, and we're not using it "in production" as they say.)
|
|
||||||
|
|
||||||
### Run 466 times
|
|
||||||
In the PE1 problem we are asked to sum all the multiples of three and five less than 1000. It's worked out that we need to use all seven numbers sixty-six times and then four more.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('7 66 * 4 +')
|
|
||||||
```
|
|
||||||
|
|
||||||
466
|
|
||||||
|
|
||||||
|
|
||||||
If we drive our generator 466 times and sum the stack we get 999.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[14811 swap [PE1.1.check PE1.1] direco] 466 [x] times')
|
|
||||||
```
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 [57 swap [PE1.1.check PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[14811 swap [PE1.1.check PE1.1] direco] 466 [x] times pop enstacken sum')
|
|
||||||
```
|
|
||||||
|
|
||||||
999
|
|
||||||
|
|
||||||
|
|
||||||
## Project Euler Problem One
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE1.2 == + dup [+] dip')
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can add `PE1.2` to the quoted program given to `G`.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('0 0 0 [PE1.1.check PE1.1] G 466 [x [PE1.2] dip] times popop')
|
|
||||||
```
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
## A generator for the Fibonacci Sequence.
|
|
||||||
Consider:
|
|
||||||
|
|
||||||
[b a F] x
|
|
||||||
[b a F] b a F
|
|
||||||
|
|
||||||
The obvious first thing to do is just add `b` and `a`:
|
|
||||||
|
|
||||||
[b a F] b a +
|
|
||||||
[b a F] b+a
|
|
||||||
|
|
||||||
From here we want to arrive at:
|
|
||||||
|
|
||||||
b [b+a b F]
|
|
||||||
|
|
||||||
Let's start with `swons`:
|
|
||||||
|
|
||||||
[b a F] b+a swons
|
|
||||||
[b+a b a F]
|
|
||||||
|
|
||||||
Considering this quote as a stack:
|
|
||||||
|
|
||||||
F a b b+a
|
|
||||||
|
|
||||||
We want to get it to:
|
|
||||||
|
|
||||||
F b b+a b
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
F a b b+a popdd over
|
|
||||||
F b b+a b
|
|
||||||
|
|
||||||
And therefore:
|
|
||||||
|
|
||||||
[b+a b a F] [popdd over] infra
|
|
||||||
[b b+a b F]
|
|
||||||
|
|
||||||
But we can just use `cons` to carry `b+a` into the quote:
|
|
||||||
|
|
||||||
[b a F] b+a [popdd over] cons infra
|
|
||||||
[b a F] [b+a popdd over] infra
|
|
||||||
[b b+a b F]
|
|
||||||
|
|
||||||
Lastly:
|
|
||||||
|
|
||||||
[b b+a b F] uncons
|
|
||||||
b [b+a b F]
|
|
||||||
|
|
||||||
Putting it all together:
|
|
||||||
|
|
||||||
F == + [popdd over] cons infra uncons
|
|
||||||
fib_gen == [1 1 F]
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('fib == + [popdd over] cons infra uncons')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('fib_gen == [1 1 fib]')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('fib_gen 10 [x] times')
|
|
||||||
```
|
|
||||||
|
|
||||||
1 2 3 5 8 13 21 34 55 89 [144 89 fib]
|
|
||||||
|
|
||||||
|
|
||||||
## Project Euler Problem Two
|
|
||||||
> By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.
|
|
||||||
|
|
||||||
Now that we have a generator for the Fibonacci sequence, we need a function that adds a term in the sequence to a sum if it is even, and `pop`s it otherwise.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE2.1 == dup 2 % [+] [pop] branch')
|
|
||||||
```
|
|
||||||
|
|
||||||
And a predicate function that detects when the terms in the series "exceed four million".
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('>4M == 4000000 >')
|
|
||||||
```
|
|
||||||
|
|
||||||
Now it's straightforward to define `PE2` as a recursive function that generates terms in the Fibonacci sequence until they exceed four million and sums the even ones.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('PE2')
|
|
||||||
```
|
|
||||||
|
|
||||||
4613732
|
|
||||||
|
|
||||||
|
|
||||||
Here's the collected program definitions:
|
|
||||||
|
|
||||||
fib == + swons [popdd over] infra uncons
|
|
||||||
fib_gen == [1 1 fib]
|
|
||||||
|
|
||||||
even == dup 2 %
|
|
||||||
>4M == 4000000 >
|
|
||||||
|
|
||||||
PE2.1 == even [+] [pop] branch
|
|
||||||
PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec
|
|
||||||
|
|
||||||
### Even-valued Fibonacci Terms
|
|
||||||
|
|
||||||
Using `o` for odd and `e` for even:
|
|
||||||
|
|
||||||
o + o = e
|
|
||||||
e + e = e
|
|
||||||
o + e = o
|
|
||||||
|
|
||||||
So the Fibonacci sequence considered in terms of just parity would be:
|
|
||||||
|
|
||||||
o o e o o e o o e o o e o o e o o e
|
|
||||||
1 1 2 3 5 8 . . .
|
|
||||||
|
|
||||||
Every third term is even.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 0 fib] x x x') # To start the sequence with 1 1 2 3 instead of 1 2 3.
|
|
||||||
```
|
|
||||||
|
|
||||||
1 1 2 [3 2 fib]
|
|
||||||
|
|
||||||
|
|
||||||
Drive the generator three times and `popop` the two odd terms.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 0 fib] x x x [popop] dipd')
|
|
||||||
```
|
|
||||||
|
|
||||||
2 [3 2 fib]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('PE2.2 == x x x [popop] dipd')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 0 fib] 10 [PE2.2] times')
|
|
||||||
```
|
|
||||||
|
|
||||||
2 8 34 144 610 2584 10946 46368 196418 832040 [1346269 832040 fib]
|
|
||||||
|
|
||||||
|
|
||||||
Replace `x` with our new driver function `PE2.2` and start our `fib` generator at `1 0`.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('0 [1 0 fib] PE2.2 [pop >4M] [popop] [[PE2.1] dip PE2.2] primrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
4613732
|
|
||||||
|
|
||||||
|
|
||||||
## How to compile these?
|
|
||||||
You would probably start with a special version of `G`, and perhaps modifications to the default `x`?
|
|
||||||
|
|
||||||
## An Interesting Variation
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('codireco == cons dip rest cons')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[0 [dup ++] codireco] x')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [0 [dup ++] codireco] x
|
|
||||||
[0 [dup ++] codireco] . x
|
|
||||||
[0 [dup ++] codireco] . 0 [dup ++] codireco
|
|
||||||
[0 [dup ++] codireco] 0 . [dup ++] codireco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] . codireco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] . cons dip rest cons
|
|
||||||
[0 [dup ++] codireco] [0 dup ++] . dip rest cons
|
|
||||||
. 0 dup ++ [0 [dup ++] codireco] rest cons
|
|
||||||
0 . dup ++ [0 [dup ++] codireco] rest cons
|
|
||||||
0 0 . ++ [0 [dup ++] codireco] rest cons
|
|
||||||
0 1 . [0 [dup ++] codireco] rest cons
|
|
||||||
0 1 [0 [dup ++] codireco] . rest cons
|
|
||||||
0 1 [[dup ++] codireco] . cons
|
|
||||||
0 [1 [dup ++] codireco] .
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('G == [codireco] cons cons')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('230 [dup ++] G 5 [x] times pop')
|
|
||||||
```
|
|
||||||
|
|
||||||
230 231 232 233 234
|
|
||||||
|
|
||||||
|
|
@ -1,635 +0,0 @@
|
||||||
Using ``x`` to Generate Values
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Cf. jp-reprod.html
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
|
|
||||||
Consider the ``x`` combinator:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
x == dup i
|
|
||||||
|
|
||||||
We can apply it to a quoted program consisting of some value ``a`` and
|
|
||||||
some function ``B``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[a B] x
|
|
||||||
[a B] a B
|
|
||||||
|
|
||||||
Let ``B`` function ``swap`` the ``a`` with the quote and run some
|
|
||||||
function ``C`` on it to generate a new value ``b``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
B == swap [C] dip
|
|
||||||
|
|
||||||
[a B] a B
|
|
||||||
[a B] a swap [C] dip
|
|
||||||
a [a B] [C] dip
|
|
||||||
a C [a B]
|
|
||||||
b [a B]
|
|
||||||
|
|
||||||
Now discard the quoted ``a`` with ``rest`` then ``cons`` ``b``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b [a B] rest cons
|
|
||||||
b [B] cons
|
|
||||||
[b B]
|
|
||||||
|
|
||||||
Altogether, this is the definition of ``B``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
B == swap [C] dip rest cons
|
|
||||||
|
|
||||||
We can make a generator for the Natural numbers (0, 1, 2, …) by using
|
|
||||||
``0`` for ``a`` and ``[dup ++]`` for ``[C]``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[0 swap [dup ++] dip rest cons]
|
|
||||||
|
|
||||||
Let’s try it:
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[0 swap [dup ++] dip rest cons] x')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [0 swap [dup ++] dip rest cons] x
|
|
||||||
[0 swap [dup ++] dip rest cons] . x
|
|
||||||
[0 swap [dup ++] dip rest cons] . 0 swap [dup ++] dip rest cons
|
|
||||||
[0 swap [dup ++] dip rest cons] 0 . swap [dup ++] dip rest cons
|
|
||||||
0 [0 swap [dup ++] dip rest cons] . [dup ++] dip rest cons
|
|
||||||
0 [0 swap [dup ++] dip rest cons] [dup ++] . dip rest cons
|
|
||||||
0 . dup ++ [0 swap [dup ++] dip rest cons] rest cons
|
|
||||||
0 0 . ++ [0 swap [dup ++] dip rest cons] rest cons
|
|
||||||
0 1 . [0 swap [dup ++] dip rest cons] rest cons
|
|
||||||
0 1 [0 swap [dup ++] dip rest cons] . rest cons
|
|
||||||
0 1 [swap [dup ++] dip rest cons] . cons
|
|
||||||
0 [1 swap [dup ++] dip rest cons] .
|
|
||||||
|
|
||||||
|
|
||||||
After one application of ``x`` the quoted program contains ``1`` and
|
|
||||||
``0`` is below it on the stack.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[0 swap [dup ++] dip rest cons] x x x x x pop')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0 1 2 3 4
|
|
||||||
|
|
||||||
|
|
||||||
``direco``
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('direco == dip rest cons')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[0 swap [dup ++] direco] x')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [0 swap [dup ++] direco] x
|
|
||||||
[0 swap [dup ++] direco] . x
|
|
||||||
[0 swap [dup ++] direco] . 0 swap [dup ++] direco
|
|
||||||
[0 swap [dup ++] direco] 0 . swap [dup ++] direco
|
|
||||||
0 [0 swap [dup ++] direco] . [dup ++] direco
|
|
||||||
0 [0 swap [dup ++] direco] [dup ++] . direco
|
|
||||||
0 [0 swap [dup ++] direco] [dup ++] . dip rest cons
|
|
||||||
0 . dup ++ [0 swap [dup ++] direco] rest cons
|
|
||||||
0 0 . ++ [0 swap [dup ++] direco] rest cons
|
|
||||||
0 1 . [0 swap [dup ++] direco] rest cons
|
|
||||||
0 1 [0 swap [dup ++] direco] . rest cons
|
|
||||||
0 1 [swap [dup ++] direco] . cons
|
|
||||||
0 [1 swap [dup ++] direco] .
|
|
||||||
|
|
||||||
|
|
||||||
Making Generators
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
We want to define a function that accepts ``a`` and ``[C]`` and builds
|
|
||||||
our quoted program:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a [C] G
|
|
||||||
-------------------------
|
|
||||||
[a swap [C] direco]
|
|
||||||
|
|
||||||
Working in reverse:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[a swap [C] direco] cons
|
|
||||||
a [swap [C] direco] concat
|
|
||||||
a [swap] [[C] direco] swap
|
|
||||||
a [[C] direco] [swap]
|
|
||||||
a [C] [direco] cons [swap]
|
|
||||||
|
|
||||||
Reading from the bottom up:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
G == [direco] cons [swap] swap concat cons
|
|
||||||
G == [direco] cons [swap] swoncat cons
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('G == [direco] cons [swap] swoncat cons')
|
|
||||||
|
|
||||||
Let’s try it out:
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('0 [dup ++] G')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[0 swap [dup ++] direco]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('0 [dup ++] G x x x pop')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0 1 2
|
|
||||||
|
|
||||||
|
|
||||||
Powers of 2
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('1 [dup 1 <<] G x x x x x x x x x pop')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
1 2 4 8 16 32 64 128 256
|
|
||||||
|
|
||||||
|
|
||||||
``[x] times``
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If we have one of these quoted programs we can drive it using ``times``
|
|
||||||
with the ``x`` combinator.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('23 [dup ++] G 5 [x] times')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
23 24 25 26 27 [28 swap [dup ++] direco]
|
|
||||||
|
|
||||||
|
|
||||||
Generating Multiples of Three and Five
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
Look at the treatment of the Project Euler Problem One in the
|
|
||||||
“Developing a Program” notebook and you’ll see that we might be
|
|
||||||
interested in generating an endless cycle of:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3
|
|
||||||
|
|
||||||
To do this we want to encode the numbers as pairs of bits in a single
|
|
||||||
int:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3
|
|
||||||
0b 11 10 01 11 01 10 11 == 14811
|
|
||||||
|
|
||||||
And pick them off by masking with 3 (binary 11) and then shifting the
|
|
||||||
int right two bits.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('PE1.1 == dup [3 &] dip 2 >>')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('14811 PE1.1')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. 14811 PE1.1
|
|
||||||
14811 . PE1.1
|
|
||||||
14811 . dup [3 &] dip 2 >>
|
|
||||||
14811 14811 . [3 &] dip 2 >>
|
|
||||||
14811 14811 [3 &] . dip 2 >>
|
|
||||||
14811 . 3 & 14811 2 >>
|
|
||||||
14811 3 . & 14811 2 >>
|
|
||||||
3 . 14811 2 >>
|
|
||||||
3 14811 . 2 >>
|
|
||||||
3 14811 2 . >>
|
|
||||||
3 3702 .
|
|
||||||
|
|
||||||
|
|
||||||
If we plug ``14811`` and ``[PE1.1]`` into our generator form…
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('14811 [PE1.1] G')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[14811 swap [PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
…we get a generator that works for seven cycles before it reaches zero:
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[14811 swap [PE1.1] direco] 7 [x] times')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 [0 swap [PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
Reset at Zero
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
We need a function that checks if the int has reached zero and resets it
|
|
||||||
if so.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('PE1.1.check == dup [pop 14811] [] branch')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('14811 [PE1.1.check PE1.1] G')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[14811 swap [PE1.1.check PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[14811 swap [PE1.1.check PE1.1] direco] 21 [x] times')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 [0 swap [PE1.1.check PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
(It would be more efficient to reset the int every seven cycles but
|
|
||||||
that’s a little beyond the scope of this article. This solution does
|
|
||||||
extra work, but not much, and we’re not using it “in production” as they
|
|
||||||
say.)
|
|
||||||
|
|
||||||
Run 466 times
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
In the PE1 problem we are asked to sum all the multiples of three and
|
|
||||||
five less than 1000. It’s worked out that we need to use all seven
|
|
||||||
numbers sixty-six times and then four more.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('7 66 * 4 +')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
466
|
|
||||||
|
|
||||||
|
|
||||||
If we drive our generator 466 times and sum the stack we get 999.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[14811 swap [PE1.1.check PE1.1] direco] 466 [x] times')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 [57 swap [PE1.1.check PE1.1] direco]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[14811 swap [PE1.1.check PE1.1] direco] 466 [x] times pop enstacken sum')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
999
|
|
||||||
|
|
||||||
|
|
||||||
Project Euler Problem One
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('PE1.2 == + dup [+] dip')
|
|
||||||
|
|
||||||
Now we can add ``PE1.2`` to the quoted program given to ``G``.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('0 0 0 [PE1.1.check PE1.1] G 466 [x [PE1.2] dip] times popop')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
233168
|
|
||||||
|
|
||||||
|
|
||||||
A generator for the Fibonacci Sequence.
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
Consider:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[b a F] x
|
|
||||||
[b a F] b a F
|
|
||||||
|
|
||||||
The obvious first thing to do is just add ``b`` and ``a``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[b a F] b a +
|
|
||||||
[b a F] b+a
|
|
||||||
|
|
||||||
From here we want to arrive at:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b [b+a b F]
|
|
||||||
|
|
||||||
Let’s start with ``swons``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[b a F] b+a swons
|
|
||||||
[b+a b a F]
|
|
||||||
|
|
||||||
Considering this quote as a stack:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
F a b b+a
|
|
||||||
|
|
||||||
We want to get it to:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
F b b+a b
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
F a b b+a popdd over
|
|
||||||
F b b+a b
|
|
||||||
|
|
||||||
And therefore:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[b+a b a F] [popdd over] infra
|
|
||||||
[b b+a b F]
|
|
||||||
|
|
||||||
But we can just use ``cons`` to carry ``b+a`` into the quote:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[b a F] b+a [popdd over] cons infra
|
|
||||||
[b a F] [b+a popdd over] infra
|
|
||||||
[b b+a b F]
|
|
||||||
|
|
||||||
Lastly:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[b b+a b F] uncons
|
|
||||||
b [b+a b F]
|
|
||||||
|
|
||||||
Putting it all together:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
F == + [popdd over] cons infra uncons
|
|
||||||
fib_gen == [1 1 F]
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('fib == + [popdd over] cons infra uncons')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('fib_gen == [1 1 fib]')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('fib_gen 10 [x] times')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
1 2 3 5 8 13 21 34 55 89 [144 89 fib]
|
|
||||||
|
|
||||||
|
|
||||||
Project Euler Problem Two
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
By considering the terms in the Fibonacci sequence whose values do
|
|
||||||
not exceed four million, find the sum of the even-valued terms.
|
|
||||||
|
|
||||||
Now that we have a generator for the Fibonacci sequence, we need a
|
|
||||||
function that adds a term in the sequence to a sum if it is even, and
|
|
||||||
``pop``\ s it otherwise.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('PE2.1 == dup 2 % [+] [pop] branch')
|
|
||||||
|
|
||||||
And a predicate function that detects when the terms in the series
|
|
||||||
“exceed four million”.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('>4M == 4000000 >')
|
|
||||||
|
|
||||||
Now it’s straightforward to define ``PE2`` as a recursive function that
|
|
||||||
generates terms in the Fibonacci sequence until they exceed four million
|
|
||||||
and sums the even ones.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('PE2')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
4613732
|
|
||||||
|
|
||||||
|
|
||||||
Here’s the collected program definitions:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
fib == + swons [popdd over] infra uncons
|
|
||||||
fib_gen == [1 1 fib]
|
|
||||||
|
|
||||||
even == dup 2 %
|
|
||||||
>4M == 4000000 >
|
|
||||||
|
|
||||||
PE2.1 == even [+] [pop] branch
|
|
||||||
PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec
|
|
||||||
|
|
||||||
Even-valued Fibonacci Terms
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Using ``o`` for odd and ``e`` for even:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
o + o = e
|
|
||||||
e + e = e
|
|
||||||
o + e = o
|
|
||||||
|
|
||||||
So the Fibonacci sequence considered in terms of just parity would be:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
o o e o o e o o e o o e o o e o o e
|
|
||||||
1 1 2 3 5 8 . . .
|
|
||||||
|
|
||||||
Every third term is even.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1 0 fib] x x x') # To start the sequence with 1 1 2 3 instead of 1 2 3.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
1 1 2 [3 2 fib]
|
|
||||||
|
|
||||||
|
|
||||||
Drive the generator three times and ``popop`` the two odd terms.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1 0 fib] x x x [popop] dipd')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2 [3 2 fib]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('PE2.2 == x x x [popop] dipd')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1 0 fib] 10 [PE2.2] times')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2 8 34 144 610 2584 10946 46368 196418 832040 [1346269 832040 fib]
|
|
||||||
|
|
||||||
|
|
||||||
Replace ``x`` with our new driver function ``PE2.2`` and start our
|
|
||||||
``fib`` generator at ``1 0``.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('0 [1 0 fib] PE2.2 [pop >4M] [popop] [[PE2.1] dip PE2.2] primrec')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
4613732
|
|
||||||
|
|
||||||
|
|
||||||
How to compile these?
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
You would probably start with a special version of ``G``, and perhaps
|
|
||||||
modifications to the default ``x``?
|
|
||||||
|
|
||||||
An Interesting Variation
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('codireco == cons dip rest cons')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[0 [dup ++] codireco] x')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [0 [dup ++] codireco] x
|
|
||||||
[0 [dup ++] codireco] . x
|
|
||||||
[0 [dup ++] codireco] . 0 [dup ++] codireco
|
|
||||||
[0 [dup ++] codireco] 0 . [dup ++] codireco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] . codireco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] . cons dip rest cons
|
|
||||||
[0 [dup ++] codireco] [0 dup ++] . dip rest cons
|
|
||||||
. 0 dup ++ [0 [dup ++] codireco] rest cons
|
|
||||||
0 . dup ++ [0 [dup ++] codireco] rest cons
|
|
||||||
0 0 . ++ [0 [dup ++] codireco] rest cons
|
|
||||||
0 1 . [0 [dup ++] codireco] rest cons
|
|
||||||
0 1 [0 [dup ++] codireco] . rest cons
|
|
||||||
0 1 [[dup ++] codireco] . cons
|
|
||||||
0 [1 [dup ++] codireco] .
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('G == [codireco] cons cons')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('230 [dup ++] G 5 [x] times pop')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
230 231 232 233 234
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,29 +1,20 @@
|
||||||
docs = $(wildcard *.ipynb)
|
docs = $(wildcard *.ipynb)
|
||||||
docs_html = $(patsubst %.ipynb,%.html,$(docs))
|
docs_html = $(patsubst %.ipynb,%.html,$(docs))
|
||||||
docs_md = $(patsubst %.ipynb,%.md,$(docs))
|
|
||||||
docs_rst = $(patsubst %.ipynb,%.rst,$(docs))
|
|
||||||
|
|
||||||
.PHONY: clean sdist test docs
|
.PHONY: clean sdist test docs
|
||||||
|
|
||||||
|
|
||||||
all: $(docs_html) $(docs_md) $(docs_rst)
|
all: $(docs_html)
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(RM) -v $(docs_html) $(docs_md) $(docs_rst)
|
$(RM) -v $(docs_html)
|
||||||
|
|
||||||
|
|
||||||
$(docs_html): %.html : %.ipynb
|
$(docs_html): %.html : %.ipynb
|
||||||
python -m nbconvert --to html $<
|
python -m nbconvert --to html $<
|
||||||
|
|
||||||
$(docs_md): %.md : %.ipynb
|
|
||||||
python -m nbconvert --to markdown $<
|
|
||||||
|
|
||||||
$(docs_rst): %.rst : %.ipynb
|
|
||||||
python -m nbconvert --to rst $<
|
|
||||||
|
|
||||||
|
|
||||||
move_us = Derivatives_of_Regular_Expressions.rst Generator_Programs.rst Newton-Raphson.rst Ordered_Binary_Trees.rst Quadratic.rst Recursion_Combinators.rst Replacing.rst The_Four_Operations.rst Treestep.rst TypeChecking.rst Types.rst Zipper.rst
|
|
||||||
|
|
||||||
mov: $(move_us)
|
mov: $(move_us)
|
||||||
cp -v $? ./sphinx_docs/notebooks/
|
cp -v $? ./sphinx_docs/notebooks/
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,208 +0,0 @@
|
||||||
# [Newton's method](https://en.wikipedia.org/wiki/Newton%27s_method)
|
|
||||||
Let's use the Newton-Raphson method for finding the root of an equation to write a function that can compute the square root of a number.
|
|
||||||
|
|
||||||
Cf. ["Why Functional Programming Matters" by John Hughes](https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
## A Generator for Approximations
|
|
||||||
|
|
||||||
To make a generator that generates successive approximations let’s start by assuming an initial approximation and then derive the function that computes the next approximation:
|
|
||||||
|
|
||||||
a F
|
|
||||||
---------
|
|
||||||
a'
|
|
||||||
|
|
||||||
### A Function to Compute the Next Approximation
|
|
||||||
|
|
||||||
This is the equation for computing the next approximate value of the square root:
|
|
||||||
|
|
||||||
$a_{i+1} = \frac{(a_i+\frac{n}{a_i})}{2}$
|
|
||||||
|
|
||||||
a n over / + 2 /
|
|
||||||
a n a / + 2 /
|
|
||||||
a n/a + 2 /
|
|
||||||
a+n/a 2 /
|
|
||||||
(a+n/a)/2
|
|
||||||
|
|
||||||
The function we want has the argument `n` in it:
|
|
||||||
|
|
||||||
F == n over / + 2 /
|
|
||||||
|
|
||||||
### Make it into a Generator
|
|
||||||
|
|
||||||
Our generator would be created by:
|
|
||||||
|
|
||||||
a [dup F] make_generator
|
|
||||||
|
|
||||||
With n as part of the function F, but n is the input to the sqrt function we’re writing. If we let 1 be the initial approximation:
|
|
||||||
|
|
||||||
1 n 1 / + 2 /
|
|
||||||
1 n/1 + 2 /
|
|
||||||
1 n + 2 /
|
|
||||||
n+1 2 /
|
|
||||||
(n+1)/2
|
|
||||||
|
|
||||||
The generator can be written as:
|
|
||||||
|
|
||||||
23 1 swap [over / + 2 /] cons [dup] swoncat make_generator
|
|
||||||
1 23 [over / + 2 /] cons [dup] swoncat make_generator
|
|
||||||
1 [23 over / + 2 /] [dup] swoncat make_generator
|
|
||||||
1 [dup 23 over / + 2 /] make_generator
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('gsra 1 swap [over / + 2 /] cons [dup] swoncat make_generator')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('23 gsra')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 [dup 23 over / + 2 /] codireco]
|
|
||||||
|
|
||||||
|
|
||||||
Let's drive the generator a few time (with the `x` combinator) and square the approximation to see how well it works...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('23 gsra 6 [x popd] times first sqr')
|
|
||||||
```
|
|
||||||
|
|
||||||
23.0000000001585
|
|
||||||
|
|
||||||
|
|
||||||
## Finding Consecutive Approximations within a Tolerance
|
|
||||||
|
|
||||||
From ["Why Functional Programming Matters" by John Hughes](https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf):
|
|
||||||
|
|
||||||
|
|
||||||
> The remainder of a square root finder is a function _within_, which takes a tolerance and a list of approximations and looks down the list for two successive approximations that differ by no more than the given tolerance.
|
|
||||||
|
|
||||||
(And note that by “list” he means a lazily-evaluated list.)
|
|
||||||
|
|
||||||
Using the _output_ `[a G]` of the above generator for square root approximations, and further assuming that the first term a has been generated already and epsilon ε is handy on the stack...
|
|
||||||
|
|
||||||
a [b G] ε within
|
|
||||||
---------------------- a b - abs ε <=
|
|
||||||
b
|
|
||||||
|
|
||||||
|
|
||||||
a [b G] ε within
|
|
||||||
---------------------- a b - abs ε >
|
|
||||||
b [c G] ε within
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Predicate
|
|
||||||
|
|
||||||
a [b G] ε [first - abs] dip <=
|
|
||||||
a [b G] first - abs ε <=
|
|
||||||
a b - abs ε <=
|
|
||||||
a-b abs ε <=
|
|
||||||
abs(a-b) ε <=
|
|
||||||
(abs(a-b)<=ε)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('_within_P [first - abs] dip <=')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Base-Case
|
|
||||||
|
|
||||||
a [b G] ε roll< popop first
|
|
||||||
[b G] ε a popop first
|
|
||||||
[b G] first
|
|
||||||
b
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('_within_B roll< popop first')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Recur
|
|
||||||
|
|
||||||
a [b G] ε R0 [within] R1
|
|
||||||
|
|
||||||
1. Discard a.
|
|
||||||
2. Use `x` combinator to generate next term from `G`.
|
|
||||||
3. Run `within` with `i` (it is a "tail-recursive" function.)
|
|
||||||
|
|
||||||
Pretty straightforward:
|
|
||||||
|
|
||||||
a [b G] ε R0 [within] R1
|
|
||||||
a [b G] ε [popd x] dip [within] i
|
|
||||||
a [b G] popd x ε [within] i
|
|
||||||
[b G] x ε [within] i
|
|
||||||
b [c G] ε [within] i
|
|
||||||
b [c G] ε within
|
|
||||||
|
|
||||||
b [c G] ε within
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('_within_R [popd x] dip')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting up
|
|
||||||
|
|
||||||
The recursive function we have defined so far needs a slight preamble: `x` to prime the generator and the epsilon value to use:
|
|
||||||
|
|
||||||
[a G] x ε ...
|
|
||||||
a [b G] ε ...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('within x 0.000000001 [_within_P] [_within_B] [_within_R] tailrec')
|
|
||||||
define('sqrt gsra within')
|
|
||||||
```
|
|
||||||
|
|
||||||
Try it out...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('36 sqrt')
|
|
||||||
```
|
|
||||||
|
|
||||||
6.0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('23 sqrt')
|
|
||||||
```
|
|
||||||
|
|
||||||
4.795831523312719
|
|
||||||
|
|
||||||
|
|
||||||
Check it.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
4.795831523312719**2
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
22.999999999999996
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from math import sqrt
|
|
||||||
|
|
||||||
sqrt(23)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
4.795831523312719
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,257 +0,0 @@
|
||||||
`Newton's method <https://en.wikipedia.org/wiki/Newton%27s_method>`__
|
|
||||||
=====================================================================
|
|
||||||
|
|
||||||
Let's use the Newton-Raphson method for finding the root of an equation
|
|
||||||
to write a function that can compute the square root of a number.
|
|
||||||
|
|
||||||
Cf. `"Why Functional Programming Matters" by John
|
|
||||||
Hughes <https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf>`__
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
|
|
||||||
A Generator for Approximations
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
To make a generator that generates successive approximations let’s start
|
|
||||||
by assuming an initial approximation and then derive the function that
|
|
||||||
computes the next approximation:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a F
|
|
||||||
---------
|
|
||||||
a'
|
|
||||||
|
|
||||||
A Function to Compute the Next Approximation
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This is the equation for computing the next approximate value of the
|
|
||||||
square root:
|
|
||||||
|
|
||||||
:math:`a_{i+1} = \frac{(a_i+\frac{n}{a_i})}{2}`
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a n over / + 2 /
|
|
||||||
a n a / + 2 /
|
|
||||||
a n/a + 2 /
|
|
||||||
a+n/a 2 /
|
|
||||||
(a+n/a)/2
|
|
||||||
|
|
||||||
The function we want has the argument ``n`` in it:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
F == n over / + 2 /
|
|
||||||
|
|
||||||
Make it into a Generator
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Our generator would be created by:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a [dup F] make_generator
|
|
||||||
|
|
||||||
With n as part of the function F, but n is the input to the sqrt
|
|
||||||
function we’re writing. If we let 1 be the initial approximation:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
1 n 1 / + 2 /
|
|
||||||
1 n/1 + 2 /
|
|
||||||
1 n + 2 /
|
|
||||||
n+1 2 /
|
|
||||||
(n+1)/2
|
|
||||||
|
|
||||||
The generator can be written as:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
23 1 swap [over / + 2 /] cons [dup] swoncat make_generator
|
|
||||||
1 23 [over / + 2 /] cons [dup] swoncat make_generator
|
|
||||||
1 [23 over / + 2 /] [dup] swoncat make_generator
|
|
||||||
1 [dup 23 over / + 2 /] make_generator
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('gsra 1 swap [over / + 2 /] cons [dup] swoncat make_generator')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('23 gsra')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 [dup 23 over / + 2 /] codireco]
|
|
||||||
|
|
||||||
|
|
||||||
Let's drive the generator a few time (with the ``x`` combinator) and
|
|
||||||
square the approximation to see how well it works...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('23 gsra 6 [x popd] times first sqr')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
23.0000000001585
|
|
||||||
|
|
||||||
|
|
||||||
Finding Consecutive Approximations within a Tolerance
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
From `"Why Functional Programming Matters" by John
|
|
||||||
Hughes <https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf>`__:
|
|
||||||
|
|
||||||
The remainder of a square root finder is a function *within*, which
|
|
||||||
takes a tolerance and a list of approximations and looks down the
|
|
||||||
list for two successive approximations that differ by no more than
|
|
||||||
the given tolerance.
|
|
||||||
|
|
||||||
(And note that by “list” he means a lazily-evaluated list.)
|
|
||||||
|
|
||||||
Using the *output* ``[a G]`` of the above generator for square root
|
|
||||||
approximations, and further assuming that the first term a has been
|
|
||||||
generated already and epsilon ε is handy on the stack...
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a [b G] ε within
|
|
||||||
---------------------- a b - abs ε <=
|
|
||||||
b
|
|
||||||
|
|
||||||
|
|
||||||
a [b G] ε within
|
|
||||||
---------------------- a b - abs ε >
|
|
||||||
b [c G] ε within
|
|
||||||
|
|
||||||
Predicate
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a [b G] ε [first - abs] dip <=
|
|
||||||
a [b G] first - abs ε <=
|
|
||||||
a b - abs ε <=
|
|
||||||
a-b abs ε <=
|
|
||||||
abs(a-b) ε <=
|
|
||||||
(abs(a-b)<=ε)
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('_within_P [first - abs] dip <=')
|
|
||||||
|
|
||||||
Base-Case
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a [b G] ε roll< popop first
|
|
||||||
[b G] ε a popop first
|
|
||||||
[b G] first
|
|
||||||
b
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('_within_B roll< popop first')
|
|
||||||
|
|
||||||
Recur
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a [b G] ε R0 [within] R1
|
|
||||||
|
|
||||||
1. Discard a.
|
|
||||||
2. Use ``x`` combinator to generate next term from ``G``.
|
|
||||||
3. Run ``within`` with ``i`` (it is a "tail-recursive" function.)
|
|
||||||
|
|
||||||
Pretty straightforward:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a [b G] ε R0 [within] R1
|
|
||||||
a [b G] ε [popd x] dip [within] i
|
|
||||||
a [b G] popd x ε [within] i
|
|
||||||
[b G] x ε [within] i
|
|
||||||
b [c G] ε [within] i
|
|
||||||
b [c G] ε within
|
|
||||||
|
|
||||||
b [c G] ε within
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('_within_R [popd x] dip')
|
|
||||||
|
|
||||||
Setting up
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
The recursive function we have defined so far needs a slight preamble:
|
|
||||||
``x`` to prime the generator and the epsilon value to use:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[a G] x ε ...
|
|
||||||
a [b G] ε ...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('within x 0.000000001 [_within_P] [_within_B] [_within_R] tailrec')
|
|
||||||
define('sqrt gsra within')
|
|
||||||
|
|
||||||
Try it out...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('36 sqrt')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
6.0
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('23 sqrt')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
4.795831523312719
|
|
||||||
|
|
||||||
|
|
||||||
Check it.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
4.795831523312719**2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
22.999999999999996
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from math import sqrt
|
|
||||||
|
|
||||||
sqrt(23)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
4.795831523312719
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,121 +0,0 @@
|
||||||
```python
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
# [Quadratic formula](https://en.wikipedia.org/wiki/Quadratic_formula)
|
|
||||||
|
|
||||||
Cf. [jp-quadratic.html](http://www.kevinalbrecht.com/code/joy-mirror/jp-quadratic.html)
|
|
||||||
|
|
||||||
-b ± sqrt(b^2 - 4 * a * c)
|
|
||||||
--------------------------------
|
|
||||||
2 * a
|
|
||||||
|
|
||||||
$\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$
|
|
||||||
|
|
||||||
## Write a straightforward program with variable names.
|
|
||||||
This math translates to Joy code in a straightforward manner. We are going to use named variables to keep track of the arguments, then write a definition without them.
|
|
||||||
|
|
||||||
### `-b`
|
|
||||||
b neg
|
|
||||||
|
|
||||||
### `sqrt(b^2 - 4 * a * c)`
|
|
||||||
b sqr 4 a c * * - sqrt
|
|
||||||
|
|
||||||
### `/2a`
|
|
||||||
a 2 * /
|
|
||||||
|
|
||||||
### `±`
|
|
||||||
There is a function `pm` that accepts two values on the stack and replaces them with their sum and difference.
|
|
||||||
|
|
||||||
pm == [+] [-] cleave popdd
|
|
||||||
|
|
||||||
### Putting Them Together
|
|
||||||
|
|
||||||
b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
|
||||||
|
|
||||||
We use `app2` to compute both roots by using a quoted program `[2a /]` built with `cons`.
|
|
||||||
|
|
||||||
## Derive a definition.
|
|
||||||
Working backwards we use `dip` and `dipd` to extract the code from the variables:
|
|
||||||
|
|
||||||
b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
|
||||||
b [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
|
||||||
b a c [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2
|
|
||||||
b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
|
||||||
b a c over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
|
||||||
|
|
||||||
The three arguments are to the left, so we can "chop off" everything to the right and say it's the definition of the `quadratic` function:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('quadratic == over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2')
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's try it out:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('3 1 1 quadratic')
|
|
||||||
```
|
|
||||||
|
|
||||||
-0.3819660112501051 -2.618033988749895
|
|
||||||
|
|
||||||
|
|
||||||
If you look at the Joy evaluation trace you can see that the first few lines are the `dip` and `dipd` combinators building the main program by incorporating the values on the stack. Then that program runs and you get the results. This is pretty typical of Joy code.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('-5 1 4 quadratic')
|
|
||||||
```
|
|
||||||
|
|
||||||
. -5 1 4 quadratic
|
|
||||||
-5 . 1 4 quadratic
|
|
||||||
-5 1 . 4 quadratic
|
|
||||||
-5 1 4 . quadratic
|
|
||||||
-5 1 4 . over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
|
||||||
-5 1 4 1 . [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
|
||||||
-5 1 4 1 [[[neg] dupdip sqr 4] dipd * * - sqrt pm] . dip 2 * [/] cons app2
|
|
||||||
-5 1 4 . [[neg] dupdip sqr 4] dipd * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
-5 1 4 [[neg] dupdip sqr 4] . dipd * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
-5 . [neg] dupdip sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
-5 [neg] . dupdip sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
-5 . neg -5 sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 . -5 sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 -5 . sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 -5 . dup mul 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 -5 -5 . mul 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 . 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 4 . 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 4 1 . 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 4 1 4 . * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 4 4 . * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 16 . - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 9 . sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 3.0 . pm 1 2 * [/] cons app2
|
|
||||||
8.0 2.0 . 1 2 * [/] cons app2
|
|
||||||
8.0 2.0 1 . 2 * [/] cons app2
|
|
||||||
8.0 2.0 1 2 . * [/] cons app2
|
|
||||||
8.0 2.0 2 . [/] cons app2
|
|
||||||
8.0 2.0 2 [/] . cons app2
|
|
||||||
8.0 2.0 [2 /] . app2
|
|
||||||
[8.0] [2 /] . infra first [2.0] [2 /] infra first
|
|
||||||
8.0 . 2 / [] swaack first [2.0] [2 /] infra first
|
|
||||||
8.0 2 . / [] swaack first [2.0] [2 /] infra first
|
|
||||||
4.0 . [] swaack first [2.0] [2 /] infra first
|
|
||||||
4.0 [] . swaack first [2.0] [2 /] infra first
|
|
||||||
[4.0] . first [2.0] [2 /] infra first
|
|
||||||
4.0 . [2.0] [2 /] infra first
|
|
||||||
4.0 [2.0] . [2 /] infra first
|
|
||||||
4.0 [2.0] [2 /] . infra first
|
|
||||||
2.0 . 2 / [4.0] swaack first
|
|
||||||
2.0 2 . / [4.0] swaack first
|
|
||||||
1.0 . [4.0] swaack first
|
|
||||||
1.0 [4.0] . swaack first
|
|
||||||
4.0 [1.0] . first
|
|
||||||
4.0 1.0 .
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
|
|
||||||
`Quadratic formula <https://en.wikipedia.org/wiki/Quadratic_formula>`__
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Cf.
|
|
||||||
`jp-quadratic.html <http://www.kevinalbrecht.com/code/joy-mirror/jp-quadratic.html>`__
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
-b ± sqrt(b^2 - 4 * a * c)
|
|
||||||
--------------------------------
|
|
||||||
2 * a
|
|
||||||
|
|
||||||
:math:`\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}`
|
|
||||||
|
|
||||||
Write a straightforward program with variable names.
|
|
||||||
----------------------------------------------------
|
|
||||||
|
|
||||||
This math translates to Joy code in a straightforward manner. We are
|
|
||||||
going to use named variables to keep track of the arguments, then write
|
|
||||||
a definition without them.
|
|
||||||
|
|
||||||
``-b``
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b neg
|
|
||||||
|
|
||||||
``sqrt(b^2 - 4 * a * c)``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b sqr 4 a c * * - sqrt
|
|
||||||
|
|
||||||
``/2a``
|
|
||||||
~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a 2 * /
|
|
||||||
|
|
||||||
``±``
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
There is a function ``pm`` that accepts two values on the stack and
|
|
||||||
replaces them with their sum and difference.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
pm == [+] [-] cleave popdd
|
|
||||||
|
|
||||||
Putting Them Together
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
|
||||||
|
|
||||||
We use ``app2`` to compute both roots by using a quoted program
|
|
||||||
``[2a /]`` built with ``cons``.
|
|
||||||
|
|
||||||
Derive a definition.
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Working backwards we use ``dip`` and ``dipd`` to extract the code from
|
|
||||||
the variables:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
|
||||||
b [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
|
||||||
b a c [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2
|
|
||||||
b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
|
||||||
b a c over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
|
||||||
|
|
||||||
The three arguments are to the left, so we can “chop off” everything to
|
|
||||||
the right and say it’s the definition of the ``quadratic`` function:
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('quadratic == over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2')
|
|
||||||
|
|
||||||
Let’s try it out:
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('3 1 1 quadratic')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
-0.3819660112501051 -2.618033988749895
|
|
||||||
|
|
||||||
|
|
||||||
If you look at the Joy evaluation trace you can see that the first few
|
|
||||||
lines are the ``dip`` and ``dipd`` combinators building the main program
|
|
||||||
by incorporating the values on the stack. Then that program runs and you
|
|
||||||
get the results. This is pretty typical of Joy code.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('-5 1 4 quadratic')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. -5 1 4 quadratic
|
|
||||||
-5 . 1 4 quadratic
|
|
||||||
-5 1 . 4 quadratic
|
|
||||||
-5 1 4 . quadratic
|
|
||||||
-5 1 4 . over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
|
||||||
-5 1 4 1 . [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
|
||||||
-5 1 4 1 [[[neg] dupdip sqr 4] dipd * * - sqrt pm] . dip 2 * [/] cons app2
|
|
||||||
-5 1 4 . [[neg] dupdip sqr 4] dipd * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
-5 1 4 [[neg] dupdip sqr 4] . dipd * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
-5 . [neg] dupdip sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
-5 [neg] . dupdip sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
-5 . neg -5 sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 . -5 sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 -5 . sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 -5 . dup mul 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 -5 -5 . mul 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 . 4 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 4 . 1 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 4 1 . 4 * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 4 1 4 . * * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 4 4 . * - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 25 16 . - sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 9 . sqrt pm 1 2 * [/] cons app2
|
|
||||||
5 3.0 . pm 1 2 * [/] cons app2
|
|
||||||
8.0 2.0 . 1 2 * [/] cons app2
|
|
||||||
8.0 2.0 1 . 2 * [/] cons app2
|
|
||||||
8.0 2.0 1 2 . * [/] cons app2
|
|
||||||
8.0 2.0 2 . [/] cons app2
|
|
||||||
8.0 2.0 2 [/] . cons app2
|
|
||||||
8.0 2.0 [2 /] . app2
|
|
||||||
[8.0] [2 /] . infra first [2.0] [2 /] infra first
|
|
||||||
8.0 . 2 / [] swaack first [2.0] [2 /] infra first
|
|
||||||
8.0 2 . / [] swaack first [2.0] [2 /] infra first
|
|
||||||
4.0 . [] swaack first [2.0] [2 /] infra first
|
|
||||||
4.0 [] . swaack first [2.0] [2 /] infra first
|
|
||||||
[4.0] . first [2.0] [2 /] infra first
|
|
||||||
4.0 . [2.0] [2 /] infra first
|
|
||||||
4.0 [2.0] . [2 /] infra first
|
|
||||||
4.0 [2.0] [2 /] . infra first
|
|
||||||
2.0 . 2 / [4.0] swaack first
|
|
||||||
2.0 2 . / [4.0] swaack first
|
|
||||||
1.0 . [4.0] swaack first
|
|
||||||
1.0 [4.0] . swaack first
|
|
||||||
4.0 [1.0] . first
|
|
||||||
4.0 1.0 .
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
# Thun Documentation Documentation
|
|
||||||
|
|
||||||
Most of the docs take the form of some [Jupyter](https://jupyter.org/index.html) notebooks.
|
|
||||||
|
|
||||||
The notebooks are converted (using [nbconvert](https://nbconvert.readthedocs.io/en/latest/install.html)) to HTML, Markdown, and ReST files so you can view them without running Jupyter.
|
|
||||||
|
|
||||||
## Running the Notebooks with [Jupyter](https://jupyter.org/index.html)
|
|
||||||
|
|
||||||
In order to run the [Jupyter](https://jupyter.org/index.html) notebooks
|
|
||||||
you need [Jupyter](https://jupyter.org/index.html) (obviously):
|
|
||||||
|
|
||||||
[Installing the classic Jupyter Notebook interface](https://jupyter.readthedocs.io/en/latest/install/notebook-classic.html)
|
|
||||||
|
|
||||||
And, of course, you should install `Thun` (see the main project README or the
|
|
||||||
[online docs](https://joypy.osdn.io/#quick-start)).
|
|
||||||
|
|
||||||
Once that's done you should be able to start Jupyter Notebook server
|
|
||||||
in the `joypy/docs` directory and run the notebooks.
|
|
||||||
|
|
||||||
|
|
||||||
## ``notebook_preamble.py``
|
|
||||||
|
|
||||||
...something about the `notebook_preamble.py` file.
|
|
||||||
|
|
||||||
|
|
||||||
## Installing the [Joy Jupyter Kernel](https://osdn.net/projects/joypy/scm/git/Thun/tree/master/docs/jupyter_kernel/)
|
|
||||||
|
|
||||||
[Joy Jupyter Kernel](https://osdn.net/projects/joypy/scm/git/Thun/tree/master/docs/jupyter_kernel/)
|
|
||||||
|
|
||||||
Tracking down the deets:
|
|
||||||
- [Installing Kernels](https://jupyter.readthedocs.io/en/latest/install/kernels.html)
|
|
||||||
- [Kernels](https://jupyter.readthedocs.io/en/latest/projects/kernels.html#kernels-langs)
|
|
||||||
- [Jupyter kernels](https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages)
|
|
||||||
- [Jupyter kernels](https://github.com/jupyter/jupyter/wiki/Jupyter-kernels)
|
|
||||||
- [Creating new Jupyter kernels](https://github.com/jupyter/jupyter/wiki/Jupyter-kernels#creating-new-jupyter-kernels)
|
|
||||||
- "[Making kernels for Jupyter](https://jupyter-client.readthedocs.io/en/latest/kernels.html) in the documentation."
|
|
||||||
|
|
||||||
From [Making simple Python wrapper kernels](https://jupyter-client.readthedocs.io/en/latest/wrapperkernels.html):
|
|
||||||
|
|
||||||
> Now create a JSON kernel spec file and install it using
|
|
||||||
``jupyter kernelspec install </path/to/kernel>``.
|
|
||||||
Place your kernel module anywhere Python can import it
|
|
||||||
(try current directory for testing).
|
|
||||||
|
|
||||||
The current list of kernels:
|
|
||||||
|
|
||||||
% jupyter kernelspec list
|
|
||||||
Available kernels:
|
|
||||||
python3 /usr/home/sforman/.local/share/jupyter/kernels/python3
|
|
||||||
|
|
||||||
> Place your kernel module anywhere Python can import it
|
|
||||||
|
|
||||||
Yah, okay.
|
|
||||||
|
|
||||||
sforman@bock:~/src/Joypy/docs/jupyter_kernel % setenv PYTHONPATH `pwd`
|
|
||||||
|
|
||||||
Let's go!
|
|
||||||
|
|
||||||
sforman@bock:~/src/Joypy/docs/jupyter_kernel % jupyter kernelspec install .
|
|
||||||
[Errno 13] Permission denied: '/usr/local/share/jupyter'
|
|
||||||
Perhaps you want to install with `sudo` or `--user`?
|
|
||||||
|
|
||||||
Okay
|
|
||||||
|
|
||||||
sforman@bock:~/src/Joypy/docs/jupyter_kernel % jupyter kernelspec install --user .
|
|
||||||
[InstallKernelSpec] Removing existing kernelspec in /usr/home/sforman/.local/share/jupyter/kernels/.
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "/home/sforman/.local/bin/jupyter-kernelspec", line 8, in <module>
|
|
||||||
sys.exit(KernelSpecApp.launch_instance())
|
|
||||||
File "/home/sforman/.local/lib/python3.7/site-packages/traitlets/config/application.py", line 846, in launch_instance
|
|
||||||
app.start()
|
|
||||||
File "/home/sforman/.local/lib/python3.7/site-packages/jupyter_client/kernelspecapp.py", line 323, in start
|
|
||||||
return self.subapp.start()
|
|
||||||
File "/home/sforman/.local/lib/python3.7/site-packages/jupyter_client/kernelspecapp.py", line 151, in start
|
|
||||||
replace=self.replace,
|
|
||||||
File "/home/sforman/.local/lib/python3.7/site-packages/jupyter_client/kernelspec.py", line 404, in install_kernel_spec
|
|
||||||
shutil.rmtree(destination)
|
|
||||||
File "/usr/local/lib/python3.7/shutil.py", line 498, in rmtree
|
|
||||||
onerror(os.rmdir, path, sys.exc_info())
|
|
||||||
File "/usr/local/lib/python3.7/shutil.py", line 496, in rmtree
|
|
||||||
os.rmdir(path)
|
|
||||||
OSError: [Errno 22] Invalid argument: '/usr/home/sforman/.local/share/jupyter/kernels/.'
|
|
||||||
|
|
||||||
__Looks at code__ Oh FFS
|
|
||||||
|
|
||||||
cp -Rv /usr/home/sforman/src/Joypy/docs/jupyter_kernel /usr/home/sforman/.local/share/jupyter/kernels/thun
|
|
||||||
|
|
||||||
/usr/home/sforman/src/Joypy/docs/jupyter_kernel -> /usr/home/sforman/.local/share/jupyter/kernels/thun
|
|
||||||
/usr/home/sforman/src/Joypy/docs/jupyter_kernel/Try out the Joypy Jupyter Kernel.ipynb -> /usr/home/sforman/.local/share/jupyter/kernels/thun/Try out the Joypy Jupyter Kernel.ipynb
|
|
||||||
/usr/home/sforman/src/Joypy/docs/jupyter_kernel/joy_kernel.py -> /usr/home/sforman/.local/share/jupyter/kernels/thun/joy_kernel.py
|
|
||||||
/usr/home/sforman/src/Joypy/docs/jupyter_kernel/kernel.json -> /usr/home/sforman/.local/share/jupyter/kernels/thun/kernel.json
|
|
||||||
|
|
||||||
Done. Can start joy kernal notebooks.
|
|
||||||
|
|
||||||
But really you want to use the `--name` switch:
|
|
||||||
|
|
||||||
In Joypy/docs/notebooks/jupyter_kernel:
|
|
||||||
% jupyter kernelspec install --user --name=thun .
|
|
||||||
[InstallKernelSpec] Installed kernelspec thun in /usr/home/sforman/.local/share/jupyter/kernels/thun
|
|
||||||
|
|
||||||
## Building and Uploading
|
|
||||||
|
|
||||||
This section is mostly for my own reference.
|
|
||||||
|
|
||||||
|
|
||||||
### `backup-and-remove-htdocs` script
|
|
||||||
|
|
||||||
On the OSDN server I have a little script I call `backup-and-remove-htdocs`
|
|
||||||
which does exactly what it says. Here are the current contents:
|
|
||||||
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
tar cvz --remove-files \
|
|
||||||
-f $HOME/site-backup-$(date -u +'%F-%T').tgz \
|
|
||||||
/home/groups/j/jo/joypy/htdocs/*
|
|
||||||
|
|
||||||
As you can see, all it does is move the existing site into a tarball (in case I want to refer to it later for some reason.)
|
|
||||||
Every once in a while they should be cleared out.
|
|
||||||
|
|
||||||
The main `Makefile` uses this script via ssh then uses rsync to upload the new version of the site.
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- 1. Basic Use of Joy in a Notebook
|
|
||||||
- 2. Library Examples - Short examples of each word in the dictionary.
|
|
||||||
Various formats.
|
|
||||||
- 3. Developing a Program - Working with the first problem from Project
|
|
||||||
Euler, "Find the sum of all the multiples of 3 or 5 below 1000",
|
|
||||||
several forms of the program are derived.
|
|
||||||
- 4. Replacing Functions in the Dictionary - Shows the basics of defining
|
|
||||||
new "primitive" functions in Python or as definitions and adding them
|
|
||||||
to the dictionary.
|
|
||||||
- Factorial Function and Paramorphisms - A basic pattern of recursive
|
|
||||||
control-flow.
|
|
||||||
- Generator Programs - Using the x combinator to make generator programs
|
|
||||||
which can be used to create unbounded streams of values.
|
|
||||||
- Hylo-, Ana-, Cata-morphisms - Some basic patterns of recursive
|
|
||||||
control-flow structures.
|
|
||||||
- Quadratic - Not-so-annoying Quadratic Formula.
|
|
||||||
- Trees - Ordered Binary Trees in Joy and more recursion.
|
|
||||||
- Zipper - A preliminary examination of the idea of data-structure
|
|
||||||
"zippers" for traversing datastructures.
|
|
||||||
- notebook_preamble.py - Imported into notebooks to simplify the preamble
|
|
||||||
code.
|
|
||||||
- pe1.py pe1.txt - Set up and execute a Joy program for the first problem
|
|
||||||
from Project Euler. The pe1.txt file is the trace. It's 2.8M
|
|
||||||
uncompressed. Compressed with gzip it becomes just 0.12M.
|
|
||||||
- repl.py - Run this script to start a REPL. Useful for e.g. running Joy
|
|
||||||
code in a debugger.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
One of the things that interests me about Joy is how programming becomes
|
|
||||||
less about writing code and more about sound reasoning about simple
|
|
||||||
(almost geometric) programs. Many of the notebooks in this collection
|
|
||||||
consist of several pages of discussion to arrive at a few lines of Joy
|
|
||||||
definitions. I think this is a good thing. This is "literate
|
|
||||||
programming". The "programs" resemble mathematical proofs. You aren't
|
|
||||||
implementing so much as deriving. The structure of Joy seems to force
|
|
||||||
you to think clearly about the task in a way that is reliable but
|
|
||||||
extremely flexible. It feels like a puzzle game, and the puzzles are
|
|
||||||
often simple, and the solutions build on each other.
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,523 +0,0 @@
|
||||||
```python
|
|
||||||
from notebook_preamble import D, DefinitionWrapper, J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
# Recursion Combinators
|
|
||||||
|
|
||||||
This article describes the `genrec` combinator, how to use it, and several generic specializations.
|
|
||||||
|
|
||||||
[if] [then] [rec1] [rec2] genrec
|
|
||||||
---------------------------------------------------------------------
|
|
||||||
[if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
|
|
||||||
|
|
||||||
|
|
||||||
From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun:
|
|
||||||
|
|
||||||
> "The genrec combinator takes four program parameters in addition to
|
|
||||||
whatever data parameters it needs. Fourth from the top is an if-part,
|
|
||||||
followed by a then-part. If the if-part yields true, then the then-part
|
|
||||||
is executed and the combinator terminates. The other two parameters are
|
|
||||||
the rec1-part and the rec2-part. If the if-part yields false, the
|
|
||||||
rec1-part is executed. Following that the four program parameters and
|
|
||||||
the combinator are again pushed onto the stack bundled up in a quoted
|
|
||||||
form. Then the rec2-part is executed, where it will find the bundled
|
|
||||||
form. Typically it will then execute the bundled form, either with i or
|
|
||||||
with app2, or some other combinator."
|
|
||||||
|
|
||||||
## Designing Recursive Functions
|
|
||||||
The way to design one of these is to fix your base case and
|
|
||||||
test and then treat `R1` and `R2` as an else-part "sandwiching"
|
|
||||||
a quotation of the whole function.
|
|
||||||
|
|
||||||
For example, given a (general recursive) function `F`:
|
|
||||||
|
|
||||||
F == [I] [T] [R1] [R2] genrec
|
|
||||||
== [I] [T] [R1 [F] R2] ifte
|
|
||||||
|
|
||||||
If the `[I]` predicate is false you must derive `R1` and `R2` from:
|
|
||||||
|
|
||||||
... R1 [F] R2
|
|
||||||
|
|
||||||
Set the stack arguments in front and figure out what `R1` and `R2`
|
|
||||||
have to do to apply the quoted `[F]` in the proper way.
|
|
||||||
|
|
||||||
## Primitive Recursive Functions
|
|
||||||
Primitive recursive functions are those where `R2 == i`.
|
|
||||||
|
|
||||||
P == [I] [T] [R] primrec
|
|
||||||
== [I] [T] [R [P] i] ifte
|
|
||||||
== [I] [T] [R P] ifte
|
|
||||||
|
|
||||||
## [Hylomorphism](https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29)
|
|
||||||
A [hylomorphism](https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29) is a recursive function `H :: A -> C` that converts a value of type `A` into a value of type `C` by means of:
|
|
||||||
|
|
||||||
- A generator `G :: A -> (B, A)`
|
|
||||||
- A combiner `F :: (B, C) -> C`
|
|
||||||
- A predicate `P :: A -> Bool` to detect the base case
|
|
||||||
- A base case value `c :: C`
|
|
||||||
- Recursive calls (zero or more); it has a "call stack in the form of a cons list".
|
|
||||||
|
|
||||||
It may be helpful to see this function implemented in imperative Python code.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def hylomorphism(c, F, P, G):
|
|
||||||
'''Return a hylomorphism function H.'''
|
|
||||||
|
|
||||||
def H(a):
|
|
||||||
if P(a):
|
|
||||||
result = c
|
|
||||||
else:
|
|
||||||
b, aa = G(a)
|
|
||||||
result = F(b, H(aa)) # b is stored in the stack frame during recursive call to H().
|
|
||||||
return result
|
|
||||||
|
|
||||||
return H
|
|
||||||
```
|
|
||||||
|
|
||||||
Cf. ["Bananas, Lenses, & Barbed Wire"](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125)
|
|
||||||
|
|
||||||
Note that during evaluation of `H()` the intermediate `b` values are stored in the Python call stack. This is what is meant by "call stack in the form of a cons list".
|
|
||||||
|
|
||||||
## Hylomorphism in Joy
|
|
||||||
We can define a combinator `hylomorphism` that will make a hylomorphism combinator `H` from constituent parts.
|
|
||||||
|
|
||||||
H == [P] c [G] [F] hylomorphism
|
|
||||||
|
|
||||||
The function `H` is recursive, so we start with `ifte` and set the else-part to
|
|
||||||
some function `J` that will contain a quoted copy of `H`. (The then-part just
|
|
||||||
discards the leftover `a` and replaces it with the base case value `c`.)
|
|
||||||
|
|
||||||
H == [P] [pop c] [J] ifte
|
|
||||||
|
|
||||||
The else-part `J` gets just the argument `a` on the stack.
|
|
||||||
|
|
||||||
a J
|
|
||||||
a G The first thing to do is use the generator G
|
|
||||||
aa b which produces b and a new aa
|
|
||||||
aa b [H] dip we recur with H on the new aa
|
|
||||||
aa H b F and run F on the result.
|
|
||||||
|
|
||||||
This gives us a definition for `J`.
|
|
||||||
|
|
||||||
J == G [H] dip F
|
|
||||||
|
|
||||||
Plug it in and convert to genrec.
|
|
||||||
|
|
||||||
H == [P] [pop c] [G [H] dip F] ifte
|
|
||||||
H == [P] [pop c] [G] [dip F] genrec
|
|
||||||
|
|
||||||
This is the form of a hylomorphism in Joy, which nicely illustrates that
|
|
||||||
it is a simple specialization of the general recursion combinator.
|
|
||||||
|
|
||||||
H == [P] c [G] [F] hylomorphism == [P] [pop c] [G] [dip F] genrec
|
|
||||||
|
|
||||||
## Derivation of `hylomorphism` combinator
|
|
||||||
|
|
||||||
Now we just need to derive a definition that builds the `genrec` arguments
|
|
||||||
out of the pieces given to the `hylomorphism` combinator.
|
|
||||||
|
|
||||||
[P] c [G] [F] hylomorphism
|
|
||||||
------------------------------------------
|
|
||||||
[P] [pop c] [G] [dip F] genrec
|
|
||||||
|
|
||||||
Working in reverse:
|
|
||||||
|
|
||||||
- Use `swoncat` twice to decouple `[c]` and `[F]`.
|
|
||||||
- Use `unit` to dequote `c`.
|
|
||||||
- Use `dipd` to untangle `[unit [pop] swoncat]` from the givens.
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
H == [P] [pop c] [G] [dip F] genrec
|
|
||||||
[P] [c] [pop] swoncat [G] [F] [dip] swoncat genrec
|
|
||||||
[P] c unit [pop] swoncat [G] [F] [dip] swoncat genrec
|
|
||||||
[P] c [G] [F] [unit [pop] swoncat] dipd [dip] swoncat genrec
|
|
||||||
|
|
||||||
At this point all of the arguments (givens) to the hylomorphism are to the left so we have
|
|
||||||
a definition for `hylomorphism`:
|
|
||||||
|
|
||||||
hylomorphism == [unit [pop] swoncat] dipd [dip] swoncat genrec
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('hylomorphism == [unit [pop] swoncat] dipd [dip] swoncat genrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example: Finding [Triangular Numbers](https://en.wikipedia.org/wiki/Triangular_number)
|
|
||||||
Let's write a function that, given a positive integer, returns the sum of all positive integers less than that one. (In this case the types `A`, `B` and `C` are all `int`.)
|
|
||||||
|
|
||||||
To sum a range of integers from 0 to *n* - 1:
|
|
||||||
|
|
||||||
- `[P]` is `[1 <=]`
|
|
||||||
- `c` is `0`
|
|
||||||
- `[G]` is `[-- dup]`
|
|
||||||
- `[F]` is `[+]`
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('triangular_number == [1 <=] 0 [-- dup] [+] hylomorphism')
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's try it:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('5 triangular_number')
|
|
||||||
```
|
|
||||||
|
|
||||||
10
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[0 1 2 3 4 5 6] [triangular_number] map')
|
|
||||||
```
|
|
||||||
|
|
||||||
[0 0 1 3 6 10 15]
|
|
||||||
|
|
||||||
|
|
||||||
## Four Specializations
|
|
||||||
There are at least four kinds of recursive combinator, depending on two choices. The first choice is whether the combiner function `F` should be evaluated during the recursion or pushed into the pending expression to be "collapsed" at the end. The second choice is whether the combiner needs to operate on the current value of the datastructure or the generator's output, in other words, whether `F` or `G` should run first in the recursive branch.
|
|
||||||
|
|
||||||
H1 == [P] [pop c] [G ] [dip F] genrec
|
|
||||||
H2 == c swap [P] [pop] [G [F] dip ] [i] genrec
|
|
||||||
H3 == [P] [pop c] [ [G] dupdip ] [dip F] genrec
|
|
||||||
H4 == c swap [P] [pop] [ [F] dupdip G] [i] genrec
|
|
||||||
|
|
||||||
The working of the generator function `G` differs slightly for each. Consider the recursive branches:
|
|
||||||
|
|
||||||
... a G [H1] dip F w/ a G == a′ b
|
|
||||||
|
|
||||||
... c a G [F] dip H2 a G == b a′
|
|
||||||
|
|
||||||
... a [G] dupdip [H3] dip F a G == a′
|
|
||||||
|
|
||||||
... c a [F] dupdip G H4 a G == a′
|
|
||||||
|
|
||||||
The following four sections illustrate how these work, omitting the predicate evaluation.
|
|
||||||
|
|
||||||
### `H1`
|
|
||||||
|
|
||||||
H1 == [P] [pop c] [G] [dip F] genrec
|
|
||||||
|
|
||||||
Iterate n times.
|
|
||||||
|
|
||||||
... a G [H1] dip F
|
|
||||||
... a′ b [H1] dip F
|
|
||||||
... a′ H1 b F
|
|
||||||
... a′ G [H1] dip F b F
|
|
||||||
... a″ b′ [H1] dip F b F
|
|
||||||
... a″ H1 b′ F b F
|
|
||||||
... a″ G [H1] dip F b′ F b F
|
|
||||||
... a‴ b″ [H1] dip F b′ F b F
|
|
||||||
... a‴ H1 b″ F b′ F b F
|
|
||||||
... a‴ pop c b″ F b′ F b F
|
|
||||||
... c b″ F b′ F b F
|
|
||||||
... d b′ F b F
|
|
||||||
... d′ b F
|
|
||||||
... d″
|
|
||||||
|
|
||||||
This form builds up a pending expression (continuation) that contains the intermediate results along with the pending combiner functions. When the base case is reached the last term is replaced by the identity value `c` and the continuation "collapses" into the final result using the combiner `F`.
|
|
||||||
|
|
||||||
### `H2`
|
|
||||||
When you can start with the identity value `c` on the stack and the combiner `F` can operate as you go using the intermediate results immediately rather than queuing them up, use this form. An important difference is that the generator function must return its results in the reverse order.
|
|
||||||
|
|
||||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
|
||||||
|
|
||||||
... c a G [F] dip H2
|
|
||||||
... c b a′ [F] dip H2
|
|
||||||
... c b F a′ H2
|
|
||||||
... d a′ H2
|
|
||||||
... d a′ G [F] dip H2
|
|
||||||
... d b′ a″ [F] dip H2
|
|
||||||
... d b′ F a″ H2
|
|
||||||
... d′ a″ H2
|
|
||||||
... d′ a″ G [F] dip H2
|
|
||||||
... d′ b″ a‴ [F] dip H2
|
|
||||||
... d′ b″ F a‴ H2
|
|
||||||
... d″ a‴ H2
|
|
||||||
... d″ a‴ pop
|
|
||||||
... d″
|
|
||||||
|
|
||||||
|
|
||||||
### `H3`
|
|
||||||
If you examine the traces above you'll see that the combiner `F` only gets to operate on the results of `G`, it never "sees" the first value `a`. If the combiner and the generator both need to work on the current value then `dup` must be used, and the generator must produce one item instead of two (the b is instead the duplicate of a.)
|
|
||||||
|
|
||||||
|
|
||||||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
|
||||||
|
|
||||||
... a [G] dupdip [H3] dip F
|
|
||||||
... a G a [H3] dip F
|
|
||||||
... a′ a [H3] dip F
|
|
||||||
... a′ H3 a F
|
|
||||||
... a′ [G] dupdip [H3] dip F a F
|
|
||||||
... a′ G a′ [H3] dip F a F
|
|
||||||
... a″ a′ [H3] dip F a F
|
|
||||||
... a″ H3 a′ F a F
|
|
||||||
... a″ [G] dupdip [H3] dip F a′ F a F
|
|
||||||
... a″ G a″ [H3] dip F a′ F a F
|
|
||||||
... a‴ a″ [H3] dip F a′ F a F
|
|
||||||
... a‴ H3 a″ F a′ F a F
|
|
||||||
... a‴ pop c a″ F a′ F a F
|
|
||||||
... c a″ F a′ F a F
|
|
||||||
... d a′ F a F
|
|
||||||
... d′ a F
|
|
||||||
... d″
|
|
||||||
|
|
||||||
### `H4`
|
|
||||||
And, last but not least, if you can combine as you go, starting with `c`, and the combiner `F` needs to work on the current item, this is the form:
|
|
||||||
|
|
||||||
H4 == c swap [P] [pop] [[F] dupdip G] primrec
|
|
||||||
|
|
||||||
... c a [F] dupdip G H4
|
|
||||||
... c a F a G H4
|
|
||||||
... d a G H4
|
|
||||||
... d a′ H4
|
|
||||||
... d a′ [F] dupdip G H4
|
|
||||||
... d a′ F a′ G H4
|
|
||||||
... d′ a′ G H4
|
|
||||||
... d′ a″ H4
|
|
||||||
... d′ a″ [F] dupdip G H4
|
|
||||||
... d′ a″ F a″ G H4
|
|
||||||
... d″ a″ G H4
|
|
||||||
... d″ a‴ H4
|
|
||||||
... d″ a‴ pop
|
|
||||||
... d″
|
|
||||||
|
|
||||||
## Anamorphism
|
|
||||||
An anamorphism can be defined as a hylomorphism that uses `[]` for `c` and
|
|
||||||
`swons` for `F`. An anamorphic function builds a list of values.
|
|
||||||
|
|
||||||
A == [P] [] [G] [swons] hylomorphism
|
|
||||||
|
|
||||||
### `range` et. al.
|
|
||||||
An example of an anamorphism is the `range` function which generates the list of integers from 0 to *n* - 1 given *n*.
|
|
||||||
|
|
||||||
Each of the above variations can be used to make four slightly different `range` functions.
|
|
||||||
|
|
||||||
#### `range` with `H1`
|
|
||||||
H1 == [P] [pop c] [G] [dip F] genrec
|
|
||||||
== [0 <=] [pop []] [-- dup] [dip swons] genrec
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('range == [0 <=] [] [-- dup] [swons] hylomorphism')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('5 range')
|
|
||||||
```
|
|
||||||
|
|
||||||
[4 3 2 1 0]
|
|
||||||
|
|
||||||
|
|
||||||
#### `range` with `H2`
|
|
||||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
|
||||||
== [] swap [0 <=] [pop] [-- dup [swons] dip] primrec
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('range_reverse == [] swap [0 <=] [pop] [-- dup [swons] dip] primrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('5 range_reverse')
|
|
||||||
```
|
|
||||||
|
|
||||||
[0 1 2 3 4]
|
|
||||||
|
|
||||||
|
|
||||||
#### `range` with `H3`
|
|
||||||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
|
||||||
== [0 <=] [pop []] [[--] dupdip] [dip swons] genrec
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('ranger == [0 <=] [pop []] [[--] dupdip] [dip swons] genrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('5 ranger')
|
|
||||||
```
|
|
||||||
|
|
||||||
[5 4 3 2 1]
|
|
||||||
|
|
||||||
|
|
||||||
#### `range` with `H4`
|
|
||||||
H4 == c swap [P] [pop] [[F] dupdip G ] primrec
|
|
||||||
== [] swap [0 <=] [pop] [[swons] dupdip --] primrec
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('ranger_reverse == [] swap [0 <=] [pop] [[swons] dupdip --] primrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('5 ranger_reverse')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 2 3 4 5]
|
|
||||||
|
|
||||||
|
|
||||||
Hopefully this illustrates the workings of the variations. For more insight you can run the cells using the `V()` function instead of the `J()` function to get a trace of the Joy evaluation.
|
|
||||||
|
|
||||||
## Catamorphism
|
|
||||||
A catamorphism can be defined as a hylomorphism that uses `[uncons swap]` for `[G]`
|
|
||||||
and `[[] =]` (or just `[not]`) for the predicate `[P]`. A catamorphic function tears down a list term-by-term and makes some new value.
|
|
||||||
|
|
||||||
C == [not] c [uncons swap] [F] hylomorphism
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('swuncons == uncons swap') # Awkward name.
|
|
||||||
```
|
|
||||||
|
|
||||||
An example of a catamorphism is the sum function.
|
|
||||||
|
|
||||||
sum == [not] 0 [swuncons] [+] hylomorphism
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('sum == [not] 0 [swuncons] [+] hylomorphism')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[5 4 3 2 1] sum')
|
|
||||||
```
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
||||||
### The `step` combinator
|
|
||||||
The `step` combinator will usually be better to use than `catamorphism`.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[step] help')
|
|
||||||
```
|
|
||||||
|
|
||||||
Run a quoted program on each item in a sequence.
|
|
||||||
::
|
|
||||||
|
|
||||||
... [] [Q] . step
|
|
||||||
-----------------------
|
|
||||||
... .
|
|
||||||
|
|
||||||
|
|
||||||
... [a] [Q] . step
|
|
||||||
------------------------
|
|
||||||
... a . Q
|
|
||||||
|
|
||||||
|
|
||||||
... [a b c] [Q] . step
|
|
||||||
----------------------------------------
|
|
||||||
... a . Q [b c] [Q] step
|
|
||||||
|
|
||||||
The step combinator executes the quotation on each member of the list
|
|
||||||
on top of the stack.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('sum == 0 swap [+] step')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[5 4 3 2 1] sum')
|
|
||||||
```
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
||||||
## Example: Factorial Function
|
|
||||||
|
|
||||||
For the Factorial function:
|
|
||||||
|
|
||||||
H4 == c swap [P] [pop] [[F] dupdip G] primrec
|
|
||||||
|
|
||||||
With:
|
|
||||||
|
|
||||||
c == 1
|
|
||||||
F == *
|
|
||||||
G == --
|
|
||||||
P == 1 <=
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('factorial == 1 swap [1 <=] [pop] [[*] dupdip --] primrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('5 factorial')
|
|
||||||
```
|
|
||||||
|
|
||||||
120
|
|
||||||
|
|
||||||
|
|
||||||
## Example: `tails`
|
|
||||||
An example of a paramorphism for lists given in the ["Bananas..." paper](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125) is `tails` which returns the list of "tails" of a list.
|
|
||||||
|
|
||||||
[1 2 3] tails
|
|
||||||
--------------------
|
|
||||||
[[] [3] [2 3]]
|
|
||||||
|
|
||||||
|
|
||||||
We can build as we go, and we want `F` to run after `G`, so we use pattern `H2`:
|
|
||||||
|
|
||||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
|
||||||
|
|
||||||
We would use:
|
|
||||||
|
|
||||||
c == []
|
|
||||||
F == swons
|
|
||||||
G == rest dup
|
|
||||||
P == not
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('tails == [] swap [not] [pop] [rest dup [swons] dip] primrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3] tails')
|
|
||||||
```
|
|
||||||
|
|
||||||
[[] [3] [2 3]]
|
|
||||||
|
|
||||||
|
|
||||||
## Conclusion: Patterns of Recursion
|
|
||||||
Our story so far...
|
|
||||||
|
|
||||||
|
|
||||||
### Hylo-, Ana-, Cata-
|
|
||||||
|
|
||||||
H == [P ] [pop c ] [G ] [dip F ] genrec
|
|
||||||
A == [P ] [pop []] [G ] [dip swap cons] genrec
|
|
||||||
C == [not] [pop c ] [uncons swap] [dip F ] genrec
|
|
||||||
|
|
||||||
### Para-, ?-, ?-
|
|
||||||
|
|
||||||
P == c swap [P ] [pop] [[F ] dupdip G ] primrec
|
|
||||||
? == [] swap [P ] [pop] [[swap cons] dupdip G ] primrec
|
|
||||||
? == c swap [not] [pop] [[F ] dupdip uncons swap] primrec
|
|
||||||
|
|
||||||
|
|
||||||
## Appendix: Fun with Symbols
|
|
||||||
|
|
||||||
|[ (c, F), (G, P) ]| == (|c, F|) • [(G, P)]
|
|
||||||
|
|
||||||
["Bananas, Lenses, & Barbed Wire"](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125)
|
|
||||||
|
|
||||||
(|...|) [(...)] [<...>]
|
|
||||||
|
|
||||||
I think they are having slightly too much fun with the symbols. However, "Too much is always better than not enough."
|
|
||||||
|
|
@ -1,690 +0,0 @@
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from notebook_preamble import D, DefinitionWrapper, J, V, define
|
|
||||||
|
|
||||||
Recursion Combinators
|
|
||||||
=====================
|
|
||||||
|
|
||||||
This article describes the ``genrec`` combinator, how to use it, and
|
|
||||||
several generic specializations.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[if] [then] [rec1] [rec2] genrec
|
|
||||||
---------------------------------------------------------------------
|
|
||||||
[if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
|
|
||||||
|
|
||||||
From “Recursion Theory and Joy” (j05cmp.html) by Manfred von Thun:
|
|
||||||
|
|
||||||
“The genrec combinator takes four program parameters in addition to
|
|
||||||
whatever data parameters it needs. Fourth from the top is an if-part,
|
|
||||||
followed by a then-part. If the if-part yields true, then the
|
|
||||||
then-part is executed and the combinator terminates. The other two
|
|
||||||
parameters are the rec1-part and the rec2-part. If the if-part yields
|
|
||||||
false, the rec1-part is executed. Following that the four program
|
|
||||||
parameters and the combinator are again pushed onto the stack bundled
|
|
||||||
up in a quoted form. Then the rec2-part is executed, where it will
|
|
||||||
find the bundled form. Typically it will then execute the bundled
|
|
||||||
form, either with i or with app2, or some other combinator.”
|
|
||||||
|
|
||||||
Designing Recursive Functions
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
The way to design one of these is to fix your base case and test and
|
|
||||||
then treat ``R1`` and ``R2`` as an else-part “sandwiching” a quotation
|
|
||||||
of the whole function.
|
|
||||||
|
|
||||||
For example, given a (general recursive) function ``F``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
F == [I] [T] [R1] [R2] genrec
|
|
||||||
== [I] [T] [R1 [F] R2] ifte
|
|
||||||
|
|
||||||
If the ``[I]`` predicate is false you must derive ``R1`` and ``R2``
|
|
||||||
from:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
... R1 [F] R2
|
|
||||||
|
|
||||||
Set the stack arguments in front and figure out what ``R1`` and ``R2``
|
|
||||||
have to do to apply the quoted ``[F]`` in the proper way.
|
|
||||||
|
|
||||||
Primitive Recursive Functions
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
Primitive recursive functions are those where ``R2 == i``.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
P == [I] [T] [R] primrec
|
|
||||||
== [I] [T] [R [P] i] ifte
|
|
||||||
== [I] [T] [R P] ifte
|
|
||||||
|
|
||||||
`Hylomorphism <https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29>`__
|
|
||||||
------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
A
|
|
||||||
`hylomorphism <https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29>`__
|
|
||||||
is a recursive function ``H :: A -> C`` that converts a value of type
|
|
||||||
``A`` into a value of type ``C`` by means of:
|
|
||||||
|
|
||||||
- A generator ``G :: A -> (B, A)``
|
|
||||||
- A combiner ``F :: (B, C) -> C``
|
|
||||||
- A predicate ``P :: A -> Bool`` to detect the base case
|
|
||||||
- A base case value ``c :: C``
|
|
||||||
- Recursive calls (zero or more); it has a “call stack in the form of a
|
|
||||||
cons list”.
|
|
||||||
|
|
||||||
It may be helpful to see this function implemented in imperative Python
|
|
||||||
code.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
def hylomorphism(c, F, P, G):
|
|
||||||
'''Return a hylomorphism function H.'''
|
|
||||||
|
|
||||||
def H(a):
|
|
||||||
if P(a):
|
|
||||||
result = c
|
|
||||||
else:
|
|
||||||
b, aa = G(a)
|
|
||||||
result = F(b, H(aa)) # b is stored in the stack frame during recursive call to H().
|
|
||||||
return result
|
|
||||||
|
|
||||||
return H
|
|
||||||
|
|
||||||
Cf. `“Bananas, Lenses, & Barbed
|
|
||||||
Wire” <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125>`__
|
|
||||||
|
|
||||||
Note that during evaluation of ``H()`` the intermediate ``b`` values are
|
|
||||||
stored in the Python call stack. This is what is meant by “call stack in
|
|
||||||
the form of a cons list”.
|
|
||||||
|
|
||||||
Hylomorphism in Joy
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
We can define a combinator ``hylomorphism`` that will make a
|
|
||||||
hylomorphism combinator ``H`` from constituent parts.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H == [P] c [G] [F] hylomorphism
|
|
||||||
|
|
||||||
The function ``H`` is recursive, so we start with ``ifte`` and set the
|
|
||||||
else-part to some function ``J`` that will contain a quoted copy of
|
|
||||||
``H``. (The then-part just discards the leftover ``a`` and replaces it
|
|
||||||
with the base case value ``c``.)
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H == [P] [pop c] [J] ifte
|
|
||||||
|
|
||||||
The else-part ``J`` gets just the argument ``a`` on the stack.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a J
|
|
||||||
a G The first thing to do is use the generator G
|
|
||||||
aa b which produces b and a new aa
|
|
||||||
aa b [H] dip we recur with H on the new aa
|
|
||||||
aa H b F and run F on the result.
|
|
||||||
|
|
||||||
This gives us a definition for ``J``.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
J == G [H] dip F
|
|
||||||
|
|
||||||
Plug it in and convert to genrec.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H == [P] [pop c] [G [H] dip F] ifte
|
|
||||||
H == [P] [pop c] [G] [dip F] genrec
|
|
||||||
|
|
||||||
This is the form of a hylomorphism in Joy, which nicely illustrates that
|
|
||||||
it is a simple specialization of the general recursion combinator.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H == [P] c [G] [F] hylomorphism == [P] [pop c] [G] [dip F] genrec
|
|
||||||
|
|
||||||
Derivation of ``hylomorphism`` combinator
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
Now we just need to derive a definition that builds the ``genrec``
|
|
||||||
arguments out of the pieces given to the ``hylomorphism`` combinator.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[P] c [G] [F] hylomorphism
|
|
||||||
------------------------------------------
|
|
||||||
[P] [pop c] [G] [dip F] genrec
|
|
||||||
|
|
||||||
Working in reverse:
|
|
||||||
|
|
||||||
- Use ``swoncat`` twice to decouple ``[c]`` and ``[F]``.
|
|
||||||
- Use ``unit`` to dequote ``c``.
|
|
||||||
- Use ``dipd`` to untangle ``[unit [pop] swoncat]`` from the givens.
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H == [P] [pop c] [G] [dip F] genrec
|
|
||||||
[P] [c] [pop] swoncat [G] [F] [dip] swoncat genrec
|
|
||||||
[P] c unit [pop] swoncat [G] [F] [dip] swoncat genrec
|
|
||||||
[P] c [G] [F] [unit [pop] swoncat] dipd [dip] swoncat genrec
|
|
||||||
|
|
||||||
At this point all of the arguments (givens) to the hylomorphism are to
|
|
||||||
the left so we have a definition for ``hylomorphism``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
hylomorphism == [unit [pop] swoncat] dipd [dip] swoncat genrec
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('hylomorphism == [unit [pop] swoncat] dipd [dip] swoncat genrec')
|
|
||||||
|
|
||||||
Example: Finding `Triangular Numbers <https://en.wikipedia.org/wiki/Triangular_number>`__
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Let’s write a function that, given a positive integer, returns the sum
|
|
||||||
of all positive integers less than that one. (In this case the types
|
|
||||||
``A``, ``B`` and ``C`` are all ``int``.)
|
|
||||||
|
|
||||||
To sum a range of integers from 0 to *n* - 1:
|
|
||||||
|
|
||||||
- ``[P]`` is ``[1 <=]``
|
|
||||||
- ``c`` is ``0``
|
|
||||||
- ``[G]`` is ``[-- dup]``
|
|
||||||
- ``[F]`` is ``[+]``
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('triangular_number == [1 <=] 0 [-- dup] [+] hylomorphism')
|
|
||||||
|
|
||||||
Let’s try it:
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('5 triangular_number')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
10
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[0 1 2 3 4 5 6] [triangular_number] map')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[0 0 1 3 6 10 15]
|
|
||||||
|
|
||||||
|
|
||||||
Four Specializations
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
There are at least four kinds of recursive combinator, depending on two
|
|
||||||
choices. The first choice is whether the combiner function ``F`` should
|
|
||||||
be evaluated during the recursion or pushed into the pending expression
|
|
||||||
to be “collapsed” at the end. The second choice is whether the combiner
|
|
||||||
needs to operate on the current value of the datastructure or the
|
|
||||||
generator’s output, in other words, whether ``F`` or ``G`` should run
|
|
||||||
first in the recursive branch.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H1 == [P] [pop c] [G ] [dip F] genrec
|
|
||||||
H2 == c swap [P] [pop] [G [F] dip ] [i] genrec
|
|
||||||
H3 == [P] [pop c] [ [G] dupdip ] [dip F] genrec
|
|
||||||
H4 == c swap [P] [pop] [ [F] dupdip G] [i] genrec
|
|
||||||
|
|
||||||
The working of the generator function ``G`` differs slightly for each.
|
|
||||||
Consider the recursive branches:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
... a G [H1] dip F w/ a G == a′ b
|
|
||||||
|
|
||||||
... c a G [F] dip H2 a G == b a′
|
|
||||||
|
|
||||||
... a [G] dupdip [H3] dip F a G == a′
|
|
||||||
|
|
||||||
... c a [F] dupdip G H4 a G == a′
|
|
||||||
|
|
||||||
The following four sections illustrate how these work, omitting the
|
|
||||||
predicate evaluation.
|
|
||||||
|
|
||||||
``H1``
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H1 == [P] [pop c] [G] [dip F] genrec
|
|
||||||
|
|
||||||
Iterate n times.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
... a G [H1] dip F
|
|
||||||
... a′ b [H1] dip F
|
|
||||||
... a′ H1 b F
|
|
||||||
... a′ G [H1] dip F b F
|
|
||||||
... a″ b′ [H1] dip F b F
|
|
||||||
... a″ H1 b′ F b F
|
|
||||||
... a″ G [H1] dip F b′ F b F
|
|
||||||
... a‴ b″ [H1] dip F b′ F b F
|
|
||||||
... a‴ H1 b″ F b′ F b F
|
|
||||||
... a‴ pop c b″ F b′ F b F
|
|
||||||
... c b″ F b′ F b F
|
|
||||||
... d b′ F b F
|
|
||||||
... d′ b F
|
|
||||||
... d″
|
|
||||||
|
|
||||||
This form builds up a pending expression (continuation) that contains
|
|
||||||
the intermediate results along with the pending combiner functions. When
|
|
||||||
the base case is reached the last term is replaced by the identity value
|
|
||||||
``c`` and the continuation “collapses” into the final result using the
|
|
||||||
combiner ``F``.
|
|
||||||
|
|
||||||
``H2``
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
When you can start with the identity value ``c`` on the stack and the
|
|
||||||
combiner ``F`` can operate as you go using the intermediate results
|
|
||||||
immediately rather than queuing them up, use this form. An important
|
|
||||||
difference is that the generator function must return its results in the
|
|
||||||
reverse order.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
|
||||||
|
|
||||||
... c a G [F] dip H2
|
|
||||||
... c b a′ [F] dip H2
|
|
||||||
... c b F a′ H2
|
|
||||||
... d a′ H2
|
|
||||||
... d a′ G [F] dip H2
|
|
||||||
... d b′ a″ [F] dip H2
|
|
||||||
... d b′ F a″ H2
|
|
||||||
... d′ a″ H2
|
|
||||||
... d′ a″ G [F] dip H2
|
|
||||||
... d′ b″ a‴ [F] dip H2
|
|
||||||
... d′ b″ F a‴ H2
|
|
||||||
... d″ a‴ H2
|
|
||||||
... d″ a‴ pop
|
|
||||||
... d″
|
|
||||||
|
|
||||||
``H3``
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
If you examine the traces above you’ll see that the combiner ``F`` only
|
|
||||||
gets to operate on the results of ``G``, it never “sees” the first value
|
|
||||||
``a``. If the combiner and the generator both need to work on the
|
|
||||||
current value then ``dup`` must be used, and the generator must produce
|
|
||||||
one item instead of two (the b is instead the duplicate of a.)
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
|
||||||
|
|
||||||
... a [G] dupdip [H3] dip F
|
|
||||||
... a G a [H3] dip F
|
|
||||||
... a′ a [H3] dip F
|
|
||||||
... a′ H3 a F
|
|
||||||
... a′ [G] dupdip [H3] dip F a F
|
|
||||||
... a′ G a′ [H3] dip F a F
|
|
||||||
... a″ a′ [H3] dip F a F
|
|
||||||
... a″ H3 a′ F a F
|
|
||||||
... a″ [G] dupdip [H3] dip F a′ F a F
|
|
||||||
... a″ G a″ [H3] dip F a′ F a F
|
|
||||||
... a‴ a″ [H3] dip F a′ F a F
|
|
||||||
... a‴ H3 a″ F a′ F a F
|
|
||||||
... a‴ pop c a″ F a′ F a F
|
|
||||||
... c a″ F a′ F a F
|
|
||||||
... d a′ F a F
|
|
||||||
... d′ a F
|
|
||||||
... d″
|
|
||||||
|
|
||||||
``H4``
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
And, last but not least, if you can combine as you go, starting with
|
|
||||||
``c``, and the combiner ``F`` needs to work on the current item, this is
|
|
||||||
the form:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H4 == c swap [P] [pop] [[F] dupdip G] primrec
|
|
||||||
|
|
||||||
... c a [F] dupdip G H4
|
|
||||||
... c a F a G H4
|
|
||||||
... d a G H4
|
|
||||||
... d a′ H4
|
|
||||||
... d a′ [F] dupdip G H4
|
|
||||||
... d a′ F a′ G H4
|
|
||||||
... d′ a′ G H4
|
|
||||||
... d′ a″ H4
|
|
||||||
... d′ a″ [F] dupdip G H4
|
|
||||||
... d′ a″ F a″ G H4
|
|
||||||
... d″ a″ G H4
|
|
||||||
... d″ a‴ H4
|
|
||||||
... d″ a‴ pop
|
|
||||||
... d″
|
|
||||||
|
|
||||||
Anamorphism
|
|
||||||
-----------
|
|
||||||
|
|
||||||
An anamorphism can be defined as a hylomorphism that uses ``[]`` for
|
|
||||||
``c`` and ``swons`` for ``F``. An anamorphic function builds a list of
|
|
||||||
values.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
A == [P] [] [G] [swons] hylomorphism
|
|
||||||
|
|
||||||
``range`` et. al. An example of an anamorphism is the ``range`` function which generates the list of integers from 0 to *n* - 1 given *n*.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Each of the above variations can be used to make four slightly different
|
|
||||||
``range`` functions.
|
|
||||||
|
|
||||||
``range`` with ``H1``
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H1 == [P] [pop c] [G] [dip F] genrec
|
|
||||||
== [0 <=] [pop []] [-- dup] [dip swons] genrec
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('range == [0 <=] [] [-- dup] [swons] hylomorphism')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('5 range')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[4 3 2 1 0]
|
|
||||||
|
|
||||||
|
|
||||||
``range`` with ``H2``
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
|
||||||
== [] swap [0 <=] [pop] [-- dup [swons] dip] primrec
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('range_reverse == [] swap [0 <=] [pop] [-- dup [swons] dip] primrec')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('5 range_reverse')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[0 1 2 3 4]
|
|
||||||
|
|
||||||
|
|
||||||
``range`` with ``H3``
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
|
||||||
== [0 <=] [pop []] [[--] dupdip] [dip swons] genrec
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('ranger == [0 <=] [pop []] [[--] dupdip] [dip swons] genrec')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('5 ranger')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[5 4 3 2 1]
|
|
||||||
|
|
||||||
|
|
||||||
``range`` with ``H4``
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H4 == c swap [P] [pop] [[F] dupdip G ] primrec
|
|
||||||
== [] swap [0 <=] [pop] [[swons] dupdip --] primrec
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('ranger_reverse == [] swap [0 <=] [pop] [[swons] dupdip --] primrec')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('5 ranger_reverse')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 2 3 4 5]
|
|
||||||
|
|
||||||
|
|
||||||
Hopefully this illustrates the workings of the variations. For more
|
|
||||||
insight you can run the cells using the ``V()`` function instead of the
|
|
||||||
``J()`` function to get a trace of the Joy evaluation.
|
|
||||||
|
|
||||||
Catamorphism
|
|
||||||
------------
|
|
||||||
|
|
||||||
A catamorphism can be defined as a hylomorphism that uses
|
|
||||||
``[uncons swap]`` for ``[G]`` and ``[[] =]`` (or just ``[not]``) for the
|
|
||||||
predicate ``[P]``. A catamorphic function tears down a list term-by-term
|
|
||||||
and makes some new value.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
C == [not] c [uncons swap] [F] hylomorphism
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('swuncons == uncons swap') # Awkward name.
|
|
||||||
|
|
||||||
An example of a catamorphism is the sum function.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
sum == [not] 0 [swuncons] [+] hylomorphism
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('sum == [not] 0 [swuncons] [+] hylomorphism')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[5 4 3 2 1] sum')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
||||||
The ``step`` combinator
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``step`` combinator will usually be better to use than
|
|
||||||
``catamorphism``.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[step] help')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
Run a quoted program on each item in a sequence.
|
|
||||||
::
|
|
||||||
|
|
||||||
... [] [Q] . step
|
|
||||||
-----------------------
|
|
||||||
... .
|
|
||||||
|
|
||||||
|
|
||||||
... [a] [Q] . step
|
|
||||||
------------------------
|
|
||||||
... a . Q
|
|
||||||
|
|
||||||
|
|
||||||
... [a b c] [Q] . step
|
|
||||||
----------------------------------------
|
|
||||||
... a . Q [b c] [Q] step
|
|
||||||
|
|
||||||
The step combinator executes the quotation on each member of the list
|
|
||||||
on top of the stack.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('sum == 0 swap [+] step')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[5 4 3 2 1] sum')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
||||||
Example: Factorial Function
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
For the Factorial function:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H4 == c swap [P] [pop] [[F] dupdip G] primrec
|
|
||||||
|
|
||||||
With:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
c == 1
|
|
||||||
F == *
|
|
||||||
G == --
|
|
||||||
P == 1 <=
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('factorial == 1 swap [1 <=] [pop] [[*] dupdip --] primrec')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('5 factorial')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
120
|
|
||||||
|
|
||||||
|
|
||||||
Example: ``tails``
|
|
||||||
------------------
|
|
||||||
|
|
||||||
An example of a paramorphism for lists given in the `“Bananas…”
|
|
||||||
paper <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125>`__
|
|
||||||
is ``tails`` which returns the list of “tails” of a list.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[1 2 3] tails
|
|
||||||
--------------------
|
|
||||||
[[] [3] [2 3]]
|
|
||||||
|
|
||||||
We can build as we go, and we want ``F`` to run after ``G``, so we use
|
|
||||||
pattern ``H2``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
|
||||||
|
|
||||||
We would use:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
c == []
|
|
||||||
F == swons
|
|
||||||
G == rest dup
|
|
||||||
P == not
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('tails == [] swap [not] [pop] [rest dup [swons] dip] primrec')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1 2 3] tails')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[[] [3] [2 3]]
|
|
||||||
|
|
||||||
|
|
||||||
Conclusion: Patterns of Recursion
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
Our story so far…
|
|
||||||
|
|
||||||
Hylo-, Ana-, Cata-
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H == [P ] [pop c ] [G ] [dip F ] genrec
|
|
||||||
A == [P ] [pop []] [G ] [dip swap cons] genrec
|
|
||||||
C == [not] [pop c ] [uncons swap] [dip F ] genrec
|
|
||||||
|
|
||||||
Para-, ?-, ?-
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
P == c swap [P ] [pop] [[F ] dupdip G ] primrec
|
|
||||||
? == [] swap [P ] [pop] [[swap cons] dupdip G ] primrec
|
|
||||||
? == c swap [not] [pop] [[F ] dupdip uncons swap] primrec
|
|
||||||
|
|
||||||
Appendix: Fun with Symbols
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
|[ (c, F), (G, P) ]| == (|c, F|) • [(G, P)]
|
|
||||||
|
|
||||||
`“Bananas, Lenses, & Barbed
|
|
||||||
Wire” <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125>`__
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
(|...|) [(...)] [<...>]
|
|
||||||
|
|
||||||
I think they are having slightly too much fun with the symbols. However,
|
|
||||||
“Too much is always better than not enough.”
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,129 +0,0 @@
|
||||||
# Replacing Functions in the Dictionary
|
|
||||||
For now, there is no way to define new functions from within the Joy language. All functions (and the interpreter) all accept and return a dictionary parameter (in addition to the stack and expression) so that we can implement e.g. a function that adds new functions to the dictionary. However, there's no function that does that. Adding a new function to the dictionary is a meta-interpreter action, you have to do it in Python, not Joy.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from notebook_preamble import D, J, V
|
|
||||||
```
|
|
||||||
|
|
||||||
## A long trace
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[23 18] average')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [23 18] average
|
|
||||||
[23 18] . average
|
|
||||||
[23 18] . [sum 1.0 *] [size] cleave /
|
|
||||||
[23 18] [sum 1.0 *] . [size] cleave /
|
|
||||||
[23 18] [sum 1.0 *] [size] . cleave /
|
|
||||||
[23 18] [sum 1.0 *] [size] . [i] app2 [popd] dip /
|
|
||||||
[23 18] [sum 1.0 *] [size] [i] . app2 [popd] dip /
|
|
||||||
[23 18] [[sum 1.0 *] [23 18]] [i] . infra first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] [sum 1.0 *] . i [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] . sum 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41 . 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41 1.0 . * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41.0 . [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41.0 [[23 18]] . swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] [41.0] . first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 . [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 [[size] [23 18]] . [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 [[size] [23 18]] [i] . infra first [popd] dip /
|
|
||||||
[23 18] [size] . i [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
[23 18] . size [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
[23 18] . 0 swap [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
[23 18] 0 . swap [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 [23 18] . [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 [23 18] [pop ++] . step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 23 [pop ++] . i [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 23 . pop ++ [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 . ++ [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 . [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 [18] . [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 [18] [pop ++] . step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 18 [pop ++] . i [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 18 . pop ++ [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 . ++ [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
2 . [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
2 [41.0 [23 18]] . swaack first [popd] dip /
|
|
||||||
[23 18] 41.0 [2] . first [popd] dip /
|
|
||||||
[23 18] 41.0 2 . [popd] dip /
|
|
||||||
[23 18] 41.0 2 [popd] . dip /
|
|
||||||
[23 18] 41.0 . popd 2 /
|
|
||||||
41.0 . 2 /
|
|
||||||
41.0 2 . /
|
|
||||||
20.5 .
|
|
||||||
|
|
||||||
|
|
||||||
## Replacing `size` with a Python version
|
|
||||||
|
|
||||||
Both `sum` and `size` each convert a sequence to a single value.
|
|
||||||
|
|
||||||
sum == 0 swap [+] step
|
|
||||||
size == 0 swap [pop ++] step
|
|
||||||
|
|
||||||
An efficient `sum` function is already in the library. But for `size` we can use a “compiled” version hand-written in Python to speed up evaluation and make the trace more readable.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from joy.library import SimpleFunctionWrapper
|
|
||||||
from joy.utils.stack import iter_stack
|
|
||||||
|
|
||||||
|
|
||||||
@SimpleFunctionWrapper
|
|
||||||
def size(stack):
|
|
||||||
'''Return the size of the sequence on the stack.'''
|
|
||||||
sequence, stack = stack
|
|
||||||
n = 0
|
|
||||||
for _ in iter_stack(sequence):
|
|
||||||
n += 1
|
|
||||||
return n, stack
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we replace the old version in the dictionary with the new version, and re-evaluate the expression.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
D['size'] = size
|
|
||||||
```
|
|
||||||
|
|
||||||
## A shorter trace
|
|
||||||
You can see that `size` now executes in a single step.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[23 18] average')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [23 18] average
|
|
||||||
[23 18] . average
|
|
||||||
[23 18] . [sum 1.0 *] [size] cleave /
|
|
||||||
[23 18] [sum 1.0 *] . [size] cleave /
|
|
||||||
[23 18] [sum 1.0 *] [size] . cleave /
|
|
||||||
[23 18] [sum 1.0 *] [size] . [i] app2 [popd] dip /
|
|
||||||
[23 18] [sum 1.0 *] [size] [i] . app2 [popd] dip /
|
|
||||||
[23 18] [[sum 1.0 *] [23 18]] [i] . infra first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] [sum 1.0 *] . i [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] . sum 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41 . 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41 1.0 . * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41.0 . [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41.0 [[23 18]] . swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] [41.0] . first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 . [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 [[size] [23 18]] . [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 [[size] [23 18]] [i] . infra first [popd] dip /
|
|
||||||
[23 18] [size] . i [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
[23 18] . size [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
2 . [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
2 [41.0 [23 18]] . swaack first [popd] dip /
|
|
||||||
[23 18] 41.0 [2] . first [popd] dip /
|
|
||||||
[23 18] 41.0 2 . [popd] dip /
|
|
||||||
[23 18] 41.0 2 [popd] . dip /
|
|
||||||
[23 18] 41.0 . popd 2 /
|
|
||||||
41.0 . 2 /
|
|
||||||
41.0 2 . /
|
|
||||||
20.5 .
|
|
||||||
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
Replacing Functions in the Dictionary
|
|
||||||
=====================================
|
|
||||||
|
|
||||||
For now, there is no way to define new functions from within the Joy
|
|
||||||
language. All functions (and the interpreter) all accept and return a
|
|
||||||
dictionary parameter (in addition to the stack and expression) so that
|
|
||||||
we can implement e.g. a function that adds new functions to the
|
|
||||||
dictionary. However, there’s no function that does that. Adding a new
|
|
||||||
function to the dictionary is a meta-interpreter action, you have to do
|
|
||||||
it in Python, not Joy.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from notebook_preamble import D, J, V
|
|
||||||
|
|
||||||
A long trace
|
|
||||||
------------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[23 18] average')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [23 18] average
|
|
||||||
[23 18] . average
|
|
||||||
[23 18] . [sum 1.0 *] [size] cleave /
|
|
||||||
[23 18] [sum 1.0 *] . [size] cleave /
|
|
||||||
[23 18] [sum 1.0 *] [size] . cleave /
|
|
||||||
[23 18] [sum 1.0 *] [size] . [i] app2 [popd] dip /
|
|
||||||
[23 18] [sum 1.0 *] [size] [i] . app2 [popd] dip /
|
|
||||||
[23 18] [[sum 1.0 *] [23 18]] [i] . infra first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] [sum 1.0 *] . i [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] . sum 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41 . 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41 1.0 . * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41.0 . [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41.0 [[23 18]] . swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] [41.0] . first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 . [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 [[size] [23 18]] . [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 [[size] [23 18]] [i] . infra first [popd] dip /
|
|
||||||
[23 18] [size] . i [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
[23 18] . size [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
[23 18] . 0 swap [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
[23 18] 0 . swap [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 [23 18] . [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 [23 18] [pop ++] . step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 23 [pop ++] . i [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 23 . pop ++ [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
0 . ++ [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 . [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 [18] . [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 [18] [pop ++] . step [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 18 [pop ++] . i [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 18 . pop ++ [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
1 . ++ [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
2 . [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
2 [41.0 [23 18]] . swaack first [popd] dip /
|
|
||||||
[23 18] 41.0 [2] . first [popd] dip /
|
|
||||||
[23 18] 41.0 2 . [popd] dip /
|
|
||||||
[23 18] 41.0 2 [popd] . dip /
|
|
||||||
[23 18] 41.0 . popd 2 /
|
|
||||||
41.0 . 2 /
|
|
||||||
41.0 2 . /
|
|
||||||
20.5 .
|
|
||||||
|
|
||||||
|
|
||||||
Replacing ``size`` with a Python version
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
Both ``sum`` and ``size`` each convert a sequence to a single value.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
sum == 0 swap [+] step
|
|
||||||
size == 0 swap [pop ++] step
|
|
||||||
|
|
||||||
An efficient ``sum`` function is already in the library. But for
|
|
||||||
``size`` we can use a “compiled” version hand-written in Python to speed
|
|
||||||
up evaluation and make the trace more readable.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from joy.library import SimpleFunctionWrapper
|
|
||||||
from joy.utils.stack import iter_stack
|
|
||||||
|
|
||||||
|
|
||||||
@SimpleFunctionWrapper
|
|
||||||
def size(stack):
|
|
||||||
'''Return the size of the sequence on the stack.'''
|
|
||||||
sequence, stack = stack
|
|
||||||
n = 0
|
|
||||||
for _ in iter_stack(sequence):
|
|
||||||
n += 1
|
|
||||||
return n, stack
|
|
||||||
|
|
||||||
Now we replace the old version in the dictionary with the new version,
|
|
||||||
and re-evaluate the expression.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
D['size'] = size
|
|
||||||
|
|
||||||
A shorter trace
|
|
||||||
---------------
|
|
||||||
|
|
||||||
You can see that ``size`` now executes in a single step.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[23 18] average')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [23 18] average
|
|
||||||
[23 18] . average
|
|
||||||
[23 18] . [sum 1.0 *] [size] cleave /
|
|
||||||
[23 18] [sum 1.0 *] . [size] cleave /
|
|
||||||
[23 18] [sum 1.0 *] [size] . cleave /
|
|
||||||
[23 18] [sum 1.0 *] [size] . [i] app2 [popd] dip /
|
|
||||||
[23 18] [sum 1.0 *] [size] [i] . app2 [popd] dip /
|
|
||||||
[23 18] [[sum 1.0 *] [23 18]] [i] . infra first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] [sum 1.0 *] . i [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] . sum 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41 . 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41 1.0 . * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41.0 . [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
41.0 [[23 18]] . swaack first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] [41.0] . first [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 . [[size] [23 18]] [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 [[size] [23 18]] . [i] infra first [popd] dip /
|
|
||||||
[23 18] 41.0 [[size] [23 18]] [i] . infra first [popd] dip /
|
|
||||||
[23 18] [size] . i [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
[23 18] . size [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
2 . [41.0 [23 18]] swaack first [popd] dip /
|
|
||||||
2 [41.0 [23 18]] . swaack first [popd] dip /
|
|
||||||
[23 18] 41.0 [2] . first [popd] dip /
|
|
||||||
[23 18] 41.0 2 . [popd] dip /
|
|
||||||
[23 18] 41.0 2 [popd] . dip /
|
|
||||||
[23 18] 41.0 . popd 2 /
|
|
||||||
41.0 . 2 /
|
|
||||||
41.0 2 . /
|
|
||||||
20.5 .
|
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,545 +0,0 @@
|
||||||
# Square Spiral Example Joy Code
|
|
||||||
|
|
||||||
|
|
||||||
Here is the example of Joy code from the `README` file:
|
|
||||||
|
|
||||||
[[[abs]ii <=][[<>][pop !-]||]&&][[!-][[++]][[--]]ifte dip][[pop !-][--][++]ifte]ifte
|
|
||||||
|
|
||||||
It might seem unreadable but with a little familiarity it becomes just as
|
|
||||||
legible as any other notation. Some layout helps:
|
|
||||||
|
|
||||||
[ [[abs] ii <=]
|
|
||||||
[
|
|
||||||
[<>] [pop !-] ||
|
|
||||||
] &&
|
|
||||||
]
|
|
||||||
[[ !-] [[++]] [[--]] ifte dip]
|
|
||||||
[[pop !-] [--] [++] ifte ]
|
|
||||||
ifte
|
|
||||||
|
|
||||||
This function accepts two integers on the stack and increments or
|
|
||||||
decrements one of them such that the new pair of numbers is the next
|
|
||||||
coordinate pair in a square spiral (like the kind used to construct an
|
|
||||||
Ulam Spiral).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Original Form
|
|
||||||
|
|
||||||
It's adapted from [the original code on StackOverflow](https://stackoverflow.com/questions/398299/looping-in-a-spiral/31864777#31864777):
|
|
||||||
|
|
||||||
|
|
||||||
> If all you're trying to do is generate the first N points in the spiral
|
|
||||||
> (without the original problem's constraint of masking to an N x M
|
|
||||||
> region), the code becomes very simple:
|
|
||||||
|
|
||||||
void spiral(const int N)
|
|
||||||
{
|
|
||||||
int x = 0;
|
|
||||||
int y = 0;
|
|
||||||
for(int i = 0; i < N; ++i)
|
|
||||||
{
|
|
||||||
cout << x << '\t' << y << '\n';
|
|
||||||
if(abs(x) <= abs(y) && (x != y || x >= 0))
|
|
||||||
x += ((y >= 0) ? 1 : -1);
|
|
||||||
else
|
|
||||||
y += ((x >= 0) ? -1 : 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
## Translation to Joy
|
|
||||||
|
|
||||||
I'm going to make a function that take two ints (`x` and `y`) and
|
|
||||||
generates the next pair, we'll turn it into a generator later using the
|
|
||||||
`x` combinator.
|
|
||||||
|
|
||||||
### First Boolean Predicate
|
|
||||||
|
|
||||||
We need a function that computes `abs(x) <= abs(y)`, we can use `ii` to
|
|
||||||
apply `abs` to both values and then compare them
|
|
||||||
with `<=`:
|
|
||||||
|
|
||||||
[abs] ii <=
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[_p [abs] ii <=] inscribe
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear 23 -18
|
|
||||||
```
|
|
||||||
|
|
||||||
23 -18
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[_p] trace
|
|
||||||
```
|
|
||||||
|
|
||||||
23 -18 • _p
|
|
||||||
23 -18 • [abs] ii <=
|
|
||||||
23 -18 [abs] • ii <=
|
|
||||||
23 • abs -18 abs <=
|
|
||||||
23 • -18 abs <=
|
|
||||||
23 -18 • abs <=
|
|
||||||
23 18 • <=
|
|
||||||
false •
|
|
||||||
|
|
||||||
false
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Short-Circuiting Boolean Combinators
|
|
||||||
|
|
||||||
I've defined two short-circuiting Boolean combinators `&&` and `||` that
|
|
||||||
each accept two quoted predicate programs, run the first, and
|
|
||||||
conditionally run the second only if required (to compute the final
|
|
||||||
Boolean value). They run their predicate arguments `nullary`.
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[&& [nullary] cons [nullary [false]] dip branch] inscribe
|
|
||||||
[|| [nullary] cons [nullary] dip [true] branch] inscribe
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
[true] [false] &&
|
|
||||||
```
|
|
||||||
|
|
||||||
false
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
[false] [true] &&
|
|
||||||
```
|
|
||||||
|
|
||||||
false
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
[true] [false] ||
|
|
||||||
```
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
[false] [true] ||
|
|
||||||
```
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Translating the Conditionals
|
|
||||||
|
|
||||||
Given those, we can define `x != y || x >= 0` as:
|
|
||||||
|
|
||||||
_a == [!=] [pop 0 >=] ||
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[_a [!=] [pop 0 >=] ||] inscribe
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
And `(abs(x) <= abs(y) && (x != y || x >= 0))` as:
|
|
||||||
|
|
||||||
_b == [_p] [_a] &&
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[_b [_p] [_a] &&] inscribe
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
It's a little rough, but, as I say, with a little familiarity it becomes
|
|
||||||
legible.
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear 23 -18
|
|
||||||
```
|
|
||||||
|
|
||||||
23 -18
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[_b] trace
|
|
||||||
```
|
|
||||||
|
|
||||||
23 -18 • _b
|
|
||||||
23 -18 • [_p] [_a] &&
|
|
||||||
23 -18 [_p] • [_a] &&
|
|
||||||
23 -18 [_p] [_a] • &&
|
|
||||||
23 -18 [_p] [_a] • [nullary] cons [nullary [false]] dip branch
|
|
||||||
23 -18 [_p] [_a] [nullary] • cons [nullary [false]] dip branch
|
|
||||||
23 -18 [_p] [[_a] nullary] • [nullary [false]] dip branch
|
|
||||||
23 -18 [_p] [[_a] nullary] [nullary [false]] • dip branch
|
|
||||||
23 -18 [_p] • nullary [false] [[_a] nullary] branch
|
|
||||||
23 -18 [_p] • [stack] dinfrirst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [_p] [stack] • dinfrirst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [_p] [stack] • dip infrst [false] [[_a] nullary] branch
|
|
||||||
23 -18 • stack [_p] infrst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [-18 23] • [_p] infrst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [-18 23] [_p] • infrst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [-18 23] [_p] • infra first [false] [[_a] nullary] branch
|
|
||||||
23 -18 • _p [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 -18 • [abs] ii <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 -18 [abs] • ii <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 • abs -18 abs <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 • -18 abs <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 -18 • abs <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 18 • <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
false • [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
false [-18 23] • swaack first [false] [[_a] nullary] branch
|
|
||||||
23 -18 [false] • first [false] [[_a] nullary] branch
|
|
||||||
23 -18 false • [false] [[_a] nullary] branch
|
|
||||||
23 -18 false [false] • [[_a] nullary] branch
|
|
||||||
23 -18 false [false] [[_a] nullary] • branch
|
|
||||||
23 -18 • false
|
|
||||||
23 -18 false •
|
|
||||||
|
|
||||||
23 -18 false
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### The Increment / Decrement Branches
|
|
||||||
|
|
||||||
Turning to the branches of the main `if` statement:
|
|
||||||
|
|
||||||
x += ((y >= 0) ? 1 : -1);
|
|
||||||
|
|
||||||
Rewrite as a hybrid (pseudo-code) `ifte` expression:
|
|
||||||
|
|
||||||
[y >= 0] [x += 1] [X -= 1] ifte
|
|
||||||
|
|
||||||
Change each C phrase to Joy code:
|
|
||||||
|
|
||||||
[0 >=] [[++] dip] [[--] dip] ifte
|
|
||||||
|
|
||||||
Factor out the dip from each branch:
|
|
||||||
|
|
||||||
[0 >=] [[++]] [[--]] ifte dip
|
|
||||||
|
|
||||||
Similar logic applies to the other branch:
|
|
||||||
|
|
||||||
y += ((x >= 0) ? -1 : 1);
|
|
||||||
|
|
||||||
[x >= 0] [y -= 1] [y += 1] ifte
|
|
||||||
|
|
||||||
[pop 0 >=] [--] [++] ifte
|
|
||||||
|
|
||||||
## Putting the Pieces Together
|
|
||||||
|
|
||||||
We can assemble the three functions we just defined in quotes and give
|
|
||||||
them them to the `ifte` combinator. With some arrangement to show off
|
|
||||||
the symmetry of the two branches, we have:
|
|
||||||
|
|
||||||
[[[abs] ii <=] [[<>] [pop !-] ||] &&]
|
|
||||||
[[ !-] [[++]] [[--]] ifte dip]
|
|
||||||
[[pop !-] [--] [++] ifte ]
|
|
||||||
ifte
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[spiral_next
|
|
||||||
|
|
||||||
[_b]
|
|
||||||
[[ !-] [[++]] [[--]] ifte dip]
|
|
||||||
[[pop !-] [--] [++] ifte ]
|
|
||||||
ifte
|
|
||||||
|
|
||||||
] inscribe
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
As I was writing this up I realized that, since the `&&` combinator
|
|
||||||
doesn't consume the stack (below its quoted args), I can unquote the
|
|
||||||
predicate, swap the branches, and use the `branch` combinator instead of
|
|
||||||
`ifte`:
|
|
||||||
|
|
||||||
[[abs] ii <=] [[<>] [pop !-] ||] &&
|
|
||||||
[[pop !-] [--] [++] ifte ]
|
|
||||||
[[ !-] [[++]] [[--]] ifte dip]
|
|
||||||
branch
|
|
||||||
|
|
||||||
Let's try it out:
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear 0 0
|
|
||||||
```
|
|
||||||
|
|
||||||
0 0
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
1 0
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
1 -1
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
0 -1
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
-1 -1
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
-1 0
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
-1 1
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
0 1
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
1 1
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
2 1
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
2 0
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
2 -1
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
2 -2
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
1 -2
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
0 -2
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
spiral_next
|
|
||||||
```
|
|
||||||
|
|
||||||
-1 -2
|
|
||||||
|
|
||||||
## Turning it into a Generator with `x`
|
|
||||||
|
|
||||||
It can be used with the x combinator to make a kind of generator for
|
|
||||||
spiral square coordinates.
|
|
||||||
|
|
||||||
|
|
||||||
We can use `codireco` to make a generator
|
|
||||||
|
|
||||||
codireco == cons dip rest cons
|
|
||||||
|
|
||||||
It will look like this:
|
|
||||||
|
|
||||||
[value [F] codireco]
|
|
||||||
|
|
||||||
Here's a trace of how it works:
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
|
|
||||||
[0 [dup ++] codireco] [x] trace
|
|
||||||
```
|
|
||||||
|
|
||||||
[0 [dup ++] codireco] • x
|
|
||||||
[0 [dup ++] codireco] • 0 [dup ++] codireco
|
|
||||||
[0 [dup ++] codireco] 0 • [dup ++] codireco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] • codireco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] • codi reco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] • cons dip reco
|
|
||||||
[0 [dup ++] codireco] [0 dup ++] • dip reco
|
|
||||||
• 0 dup ++ [0 [dup ++] codireco] reco
|
|
||||||
0 • dup ++ [0 [dup ++] codireco] reco
|
|
||||||
0 0 • ++ [0 [dup ++] codireco] reco
|
|
||||||
0 1 • [0 [dup ++] codireco] reco
|
|
||||||
0 1 [0 [dup ++] codireco] • reco
|
|
||||||
0 1 [0 [dup ++] codireco] • rest cons
|
|
||||||
0 1 [[dup ++] codireco] • cons
|
|
||||||
0 [1 [dup ++] codireco] •
|
|
||||||
|
|
||||||
0 [1 [dup ++] codireco]
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
But first we have to change the `spiral_next` function to work on a
|
|
||||||
quoted pair of integers, and leave a copy of the pair on the stack.
|
|
||||||
From:
|
|
||||||
|
|
||||||
y x spiral_next
|
|
||||||
---------------------
|
|
||||||
y' x'
|
|
||||||
|
|
||||||
to:
|
|
||||||
|
|
||||||
[x y] [spiral_next] infra
|
|
||||||
-------------------------------
|
|
||||||
[x' y']
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[0 0] [spiral_next] infra
|
|
||||||
```
|
|
||||||
|
|
||||||
[0 1]
|
|
||||||
|
|
||||||
So our generator is:
|
|
||||||
|
|
||||||
[[x y] [dup [spiral_next] infra] codireco]
|
|
||||||
|
|
||||||
Or rather:
|
|
||||||
|
|
||||||
[[0 0] [dup [spiral_next] infra] codireco]
|
|
||||||
|
|
||||||
There is a function `make_generator` that will build the generator for us
|
|
||||||
out of the value and stepper function:
|
|
||||||
|
|
||||||
[0 0] [dup [spiral_next] infra] make_generator
|
|
||||||
----------------------------------------------------
|
|
||||||
[[0 0] [dup [spiral_next] infra] codireco]
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Here it is in action:
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
[0 0] [dup [spiral_next] infra] make_generator x x x x pop
|
|
||||||
```
|
|
||||||
|
|
||||||
[0 0] [0 1] [-1 1] [-1 0]
|
|
||||||
|
|
||||||
Four `x` combinators, four pairs of coordinates.
|
|
||||||
|
|
||||||
Or you can leave out `dup` and let the value stay in the generator until you want it:
|
|
||||||
|
|
||||||
|
|
||||||
```Joy
|
|
||||||
clear
|
|
||||||
|
|
||||||
[0 0] [[spiral_next] infra] make_generator 50 [x] times first
|
|
||||||
```
|
|
||||||
|
|
||||||
[2 4]
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
So that's an example of Joy code. It's a straightforward translation of
|
|
||||||
the original. It's a little long for a single definition, you might
|
|
||||||
break it up like so:
|
|
||||||
|
|
||||||
_spn_Pa == [abs] ii <=
|
|
||||||
_spn_Pb == [!=] [pop 0 >=] ||
|
|
||||||
_spn_P == [_spn_Pa] [_spn_Pb] &&
|
|
||||||
|
|
||||||
_spn_T == [ !-] [[++]] [[--]] ifte dip
|
|
||||||
_spn_E == [pop !-] [--] [++] ifte
|
|
||||||
|
|
||||||
spiral_next == _spn_P [_spn_E] [_spn_T] branch
|
|
||||||
|
|
||||||
This way it's easy to see that the function is a branch with two
|
|
||||||
quasi-symmetrical paths.
|
|
||||||
|
|
||||||
We then used this function to make a simple generator of coordinate
|
|
||||||
pairs, where the next pair in the series can be generated at any time by
|
|
||||||
using the `x` combinator on the generator (which is just a quoted
|
|
||||||
expression containing a copy of the current pair and the "stepper
|
|
||||||
function" to generate the next pair from that.)
|
|
||||||
|
|
@ -1,669 +0,0 @@
|
||||||
Square Spiral Example Joy Code
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Here is the example of Joy code from the ``README`` file:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[[[abs]ii <=][[<>][pop !-]||]&&][[!-][[++]][[--]]ifte dip][[pop !-][--][++]ifte]ifte
|
|
||||||
|
|
||||||
It might seem unreadable but with a little familiarity it becomes just
|
|
||||||
as legible as any other notation. Some layout helps:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[ [[abs] ii <=]
|
|
||||||
[
|
|
||||||
[<>] [pop !-] ||
|
|
||||||
] &&
|
|
||||||
]
|
|
||||||
[[ !-] [[++]] [[--]] ifte dip]
|
|
||||||
[[pop !-] [--] [++] ifte ]
|
|
||||||
ifte
|
|
||||||
|
|
||||||
This function accepts two integers on the stack and increments or
|
|
||||||
decrements one of them such that the new pair of numbers is the next
|
|
||||||
coordinate pair in a square spiral (like the kind used to construct an
|
|
||||||
Ulam Spiral).
|
|
||||||
|
|
||||||
Original Form
|
|
||||||
-------------
|
|
||||||
|
|
||||||
It’s adapted from `the original code on
|
|
||||||
StackOverflow <https://stackoverflow.com/questions/398299/looping-in-a-spiral/31864777#31864777>`__:
|
|
||||||
|
|
||||||
If all you’re trying to do is generate the first N points in the
|
|
||||||
spiral (without the original problem’s constraint of masking to an N
|
|
||||||
x M region), the code becomes very simple:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
void spiral(const int N)
|
|
||||||
{
|
|
||||||
int x = 0;
|
|
||||||
int y = 0;
|
|
||||||
for(int i = 0; i < N; ++i)
|
|
||||||
{
|
|
||||||
cout << x << '\t' << y << '\n';
|
|
||||||
if(abs(x) <= abs(y) && (x != y || x >= 0))
|
|
||||||
x += ((y >= 0) ? 1 : -1);
|
|
||||||
else
|
|
||||||
y += ((x >= 0) ? -1 : 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Translation to Joy
|
|
||||||
------------------
|
|
||||||
|
|
||||||
I’m going to make a function that take two ints (``x`` and ``y``) and
|
|
||||||
generates the next pair, we’ll turn it into a generator later using the
|
|
||||||
``x`` combinator.
|
|
||||||
|
|
||||||
First Boolean Predicate
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
We need a function that computes ``abs(x) <= abs(y)``, we can use ``ii``
|
|
||||||
to apply ``abs`` to both values and then compare them with ``<=``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[abs] ii <=
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[_p [abs] ii <=] inscribe
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear 23 -18
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
23 -18
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[_p] trace
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
23 -18 • _p
|
|
||||||
23 -18 • [abs] ii <=
|
|
||||||
23 -18 [abs] • ii <=
|
|
||||||
23 • abs -18 abs <=
|
|
||||||
23 • -18 abs <=
|
|
||||||
23 -18 • abs <=
|
|
||||||
23 18 • <=
|
|
||||||
false •
|
|
||||||
|
|
||||||
false
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Short-Circuiting Boolean Combinators
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
I’ve defined two short-circuiting Boolean combinators ``&&`` and ``||``
|
|
||||||
that each accept two quoted predicate programs, run the first, and
|
|
||||||
conditionally run the second only if required (to compute the final
|
|
||||||
Boolean value). They run their predicate arguments ``nullary``.
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[&& [nullary] cons [nullary [false]] dip branch] inscribe
|
|
||||||
[|| [nullary] cons [nullary] dip [true] branch] inscribe
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
[true] [false] &&
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
false
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
[false] [true] &&
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
false
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
[true] [false] ||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
[false] [true] ||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Translating the Conditionals
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Given those, we can define ``x != y || x >= 0`` as:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
_a == [!=] [pop 0 >=] ||
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[_a [!=] [pop 0 >=] ||] inscribe
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
And ``(abs(x) <= abs(y) && (x != y || x >= 0))`` as:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
_b == [_p] [_a] &&
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[_b [_p] [_a] &&] inscribe
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
It’s a little rough, but, as I say, with a little familiarity it becomes
|
|
||||||
legible.
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear 23 -18
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
23 -18
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[_b] trace
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
23 -18 • _b
|
|
||||||
23 -18 • [_p] [_a] &&
|
|
||||||
23 -18 [_p] • [_a] &&
|
|
||||||
23 -18 [_p] [_a] • &&
|
|
||||||
23 -18 [_p] [_a] • [nullary] cons [nullary [false]] dip branch
|
|
||||||
23 -18 [_p] [_a] [nullary] • cons [nullary [false]] dip branch
|
|
||||||
23 -18 [_p] [[_a] nullary] • [nullary [false]] dip branch
|
|
||||||
23 -18 [_p] [[_a] nullary] [nullary [false]] • dip branch
|
|
||||||
23 -18 [_p] • nullary [false] [[_a] nullary] branch
|
|
||||||
23 -18 [_p] • [stack] dinfrirst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [_p] [stack] • dinfrirst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [_p] [stack] • dip infrst [false] [[_a] nullary] branch
|
|
||||||
23 -18 • stack [_p] infrst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [-18 23] • [_p] infrst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [-18 23] [_p] • infrst [false] [[_a] nullary] branch
|
|
||||||
23 -18 [-18 23] [_p] • infra first [false] [[_a] nullary] branch
|
|
||||||
23 -18 • _p [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 -18 • [abs] ii <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 -18 [abs] • ii <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 • abs -18 abs <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 • -18 abs <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 -18 • abs <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
23 18 • <= [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
false • [-18 23] swaack first [false] [[_a] nullary] branch
|
|
||||||
false [-18 23] • swaack first [false] [[_a] nullary] branch
|
|
||||||
23 -18 [false] • first [false] [[_a] nullary] branch
|
|
||||||
23 -18 false • [false] [[_a] nullary] branch
|
|
||||||
23 -18 false [false] • [[_a] nullary] branch
|
|
||||||
23 -18 false [false] [[_a] nullary] • branch
|
|
||||||
23 -18 • false
|
|
||||||
23 -18 false •
|
|
||||||
|
|
||||||
23 -18 false
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The Increment / Decrement Branches
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Turning to the branches of the main ``if`` statement:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
x += ((y >= 0) ? 1 : -1);
|
|
||||||
|
|
||||||
Rewrite as a hybrid (pseudo-code) ``ifte`` expression:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[y >= 0] [x += 1] [X -= 1] ifte
|
|
||||||
|
|
||||||
Change each C phrase to Joy code:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[0 >=] [[++] dip] [[--] dip] ifte
|
|
||||||
|
|
||||||
Factor out the dip from each branch:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[0 >=] [[++]] [[--]] ifte dip
|
|
||||||
|
|
||||||
Similar logic applies to the other branch:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
y += ((x >= 0) ? -1 : 1);
|
|
||||||
|
|
||||||
[x >= 0] [y -= 1] [y += 1] ifte
|
|
||||||
|
|
||||||
[pop 0 >=] [--] [++] ifte
|
|
||||||
|
|
||||||
Putting the Pieces Together
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
We can assemble the three functions we just defined in quotes and give
|
|
||||||
them them to the ``ifte`` combinator. With some arrangement to show off
|
|
||||||
the symmetry of the two branches, we have:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[[[abs] ii <=] [[<>] [pop !-] ||] &&]
|
|
||||||
[[ !-] [[++]] [[--]] ifte dip]
|
|
||||||
[[pop !-] [--] [++] ifte ]
|
|
||||||
ifte
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[spiral_next
|
|
||||||
|
|
||||||
[_b]
|
|
||||||
[[ !-] [[++]] [[--]] ifte dip]
|
|
||||||
[[pop !-] [--] [++] ifte ]
|
|
||||||
ifte
|
|
||||||
|
|
||||||
] inscribe
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
As I was writing this up I realized that, since the ``&&`` combinator
|
|
||||||
doesn’t consume the stack (below its quoted args), I can unquote the
|
|
||||||
predicate, swap the branches, and use the ``branch`` combinator instead
|
|
||||||
of ``ifte``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[[abs] ii <=] [[<>] [pop !-] ||] &&
|
|
||||||
[[pop !-] [--] [++] ifte ]
|
|
||||||
[[ !-] [[++]] [[--]] ifte dip]
|
|
||||||
branch
|
|
||||||
|
|
||||||
Let’s try it out:
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear 0 0
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0 0
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
1 0
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
1 -1
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0 -1
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
-1 -1
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
-1 0
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
-1 1
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0 1
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
1 1
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2 1
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2 0
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2 -1
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
2 -2
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
1 -2
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0 -2
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
spiral_next
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
-1 -2
|
|
||||||
|
|
||||||
Turning it into a Generator with ``x``
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
It can be used with the x combinator to make a kind of generator for
|
|
||||||
spiral square coordinates.
|
|
||||||
|
|
||||||
We can use ``codireco`` to make a generator
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
codireco == cons dip rest cons
|
|
||||||
|
|
||||||
It will look like this:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[value [F] codireco]
|
|
||||||
|
|
||||||
Here’s a trace of how it works:
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
|
|
||||||
[0 [dup ++] codireco] [x] trace
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[0 [dup ++] codireco] • x
|
|
||||||
[0 [dup ++] codireco] • 0 [dup ++] codireco
|
|
||||||
[0 [dup ++] codireco] 0 • [dup ++] codireco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] • codireco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] • codi reco
|
|
||||||
[0 [dup ++] codireco] 0 [dup ++] • cons dip reco
|
|
||||||
[0 [dup ++] codireco] [0 dup ++] • dip reco
|
|
||||||
• 0 dup ++ [0 [dup ++] codireco] reco
|
|
||||||
0 • dup ++ [0 [dup ++] codireco] reco
|
|
||||||
0 0 • ++ [0 [dup ++] codireco] reco
|
|
||||||
0 1 • [0 [dup ++] codireco] reco
|
|
||||||
0 1 [0 [dup ++] codireco] • reco
|
|
||||||
0 1 [0 [dup ++] codireco] • rest cons
|
|
||||||
0 1 [[dup ++] codireco] • cons
|
|
||||||
0 [1 [dup ++] codireco] •
|
|
||||||
|
|
||||||
0 [1 [dup ++] codireco]
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
But first we have to change the ``spiral_next`` function to work on a
|
|
||||||
quoted pair of integers, and leave a copy of the pair on the stack.
|
|
||||||
From:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
y x spiral_next
|
|
||||||
---------------------
|
|
||||||
y' x'
|
|
||||||
|
|
||||||
to:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[x y] [spiral_next] infra
|
|
||||||
-------------------------------
|
|
||||||
[x' y']
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[0 0] [spiral_next] infra
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[0 1]
|
|
||||||
|
|
||||||
So our generator is:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[[x y] [dup [spiral_next] infra] codireco]
|
|
||||||
|
|
||||||
Or rather:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[[0 0] [dup [spiral_next] infra] codireco]
|
|
||||||
|
|
||||||
There is a function ``make_generator`` that will build the generator for
|
|
||||||
us out of the value and stepper function:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[0 0] [dup [spiral_next] infra] make_generator
|
|
||||||
----------------------------------------------------
|
|
||||||
[[0 0] [dup [spiral_next] infra] codireco]
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Here it is in action:
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
[0 0] [dup [spiral_next] infra] make_generator x x x x pop
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[0 0] [0 1] [-1 1] [-1 0]
|
|
||||||
|
|
||||||
Four ``x`` combinators, four pairs of coordinates.
|
|
||||||
|
|
||||||
Or you can leave out ``dup`` and let the value stay in the generator
|
|
||||||
until you want it:
|
|
||||||
|
|
||||||
.. code:: Joy
|
|
||||||
|
|
||||||
clear
|
|
||||||
|
|
||||||
[0 0] [[spiral_next] infra] make_generator 50 [x] times first
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[2 4]
|
|
||||||
|
|
||||||
Conclusion
|
|
||||||
----------
|
|
||||||
|
|
||||||
So that’s an example of Joy code. It’s a straightforward translation of
|
|
||||||
the original. It’s a little long for a single definition, you might
|
|
||||||
break it up like so:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
_spn_Pa == [abs] ii <=
|
|
||||||
_spn_Pb == [!=] [pop 0 >=] ||
|
|
||||||
_spn_P == [_spn_Pa] [_spn_Pb] &&
|
|
||||||
|
|
||||||
_spn_T == [ !-] [[++]] [[--]] ifte dip
|
|
||||||
_spn_E == [pop !-] [--] [++] ifte
|
|
||||||
|
|
||||||
spiral_next == _spn_P [_spn_E] [_spn_T] branch
|
|
||||||
|
|
||||||
This way it’s easy to see that the function is a branch with two
|
|
||||||
quasi-symmetrical paths.
|
|
||||||
|
|
||||||
We then used this function to make a simple generator of coordinate
|
|
||||||
pairs, where the next pair in the series can be generated at any time by
|
|
||||||
using the ``x`` combinator on the generator (which is just a quoted
|
|
||||||
expression containing a copy of the current pair and the “stepper
|
|
||||||
function” to generate the next pair from that.)
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,229 +0,0 @@
|
||||||
# The Four Fundamental Operations of Definite Action
|
|
||||||
|
|
||||||
All definite actions (computer program) can be defined by four fundamental patterns of combination:
|
|
||||||
|
|
||||||
1. Sequence
|
|
||||||
2. Branch
|
|
||||||
3. Loop
|
|
||||||
4. Parallel
|
|
||||||
|
|
||||||
## Sequence
|
|
||||||
|
|
||||||
Do one thing after another. In joy this is represented by putting two symbols together, juxtaposition:
|
|
||||||
|
|
||||||
foo bar
|
|
||||||
|
|
||||||
Operations have inputs and outputs. The outputs of `foo` must be compatible in "arity", type, and shape with the inputs of `bar`.
|
|
||||||
|
|
||||||
## Branch
|
|
||||||
|
|
||||||
Do one thing or another.
|
|
||||||
|
|
||||||
boolean [F] [T] branch
|
|
||||||
|
|
||||||
|
|
||||||
t [F] [T] branch
|
|
||||||
----------------------
|
|
||||||
T
|
|
||||||
|
|
||||||
|
|
||||||
f [F] [T] branch
|
|
||||||
----------------------
|
|
||||||
F
|
|
||||||
|
|
||||||
|
|
||||||
branch == unit cons swap pick i
|
|
||||||
|
|
||||||
boolean [F] [T] branch
|
|
||||||
boolean [F] [T] unit cons swap pick i
|
|
||||||
boolean [F] [[T]] cons swap pick i
|
|
||||||
boolean [[F] [T]] swap pick i
|
|
||||||
[[F] [T]] boolean pick i
|
|
||||||
[F-or-T] i
|
|
||||||
|
|
||||||
Given some branch function `G`:
|
|
||||||
|
|
||||||
G == [F] [T] branch
|
|
||||||
|
|
||||||
Used in a sequence like so:
|
|
||||||
|
|
||||||
foo G bar
|
|
||||||
|
|
||||||
The inputs and outputs of `F` and `T` must be compatible with the outputs for `foo` and the inputs of `bar`, respectively.
|
|
||||||
|
|
||||||
foo F bar
|
|
||||||
|
|
||||||
foo T bar
|
|
||||||
|
|
||||||
### `ifte`
|
|
||||||
|
|
||||||
Often it will be easier on the programmer to write branching code with the predicate specified in a quote. The `ifte` combinator provides this (`T` for "then" and `E` for "else"):
|
|
||||||
|
|
||||||
[P] [T] [E] ifte
|
|
||||||
|
|
||||||
Defined in terms of `branch`:
|
|
||||||
|
|
||||||
ifte == [nullary not] dip branch
|
|
||||||
|
|
||||||
|
|
||||||
In this case, `P` must be compatible with the stack and return a Boolean value, and `T` and `E` both must be compatible with the preceeding and following functions, as described above for `F` and `T`. (Note that in the current implementation we are depending on Python for the underlying semantics, so the Boolean value doesn't *have* to be Boolean because Python's rules for "truthiness" will be used to evaluate it. I reflect this in the structure of the stack effect comment of `branch`, it will only accept Boolean values, and in the definition of `ifte` above by including `not` in the quote, which also has the effect that the subject quotes are in the proper order for `branch`.)
|
|
||||||
|
|
||||||
## Loop
|
|
||||||
|
|
||||||
Do one thing zero or more times.
|
|
||||||
|
|
||||||
boolean [Q] loop
|
|
||||||
|
|
||||||
|
|
||||||
t [Q] loop
|
|
||||||
----------------
|
|
||||||
Q [Q] loop
|
|
||||||
|
|
||||||
|
|
||||||
... f [Q] loop
|
|
||||||
--------------------
|
|
||||||
...
|
|
||||||
|
|
||||||
The `loop` combinator generates a copy of itself in the true branch. This is the hallmark of recursive defintions. In Thun there is no equivalent to conventional loops. (There is, however, the `x` combinator, defined as `x == dup i`, which permits recursive constructs that do not need to be directly self-referential, unlike `loop` and `genrec`.)
|
|
||||||
|
|
||||||
loop == [] swap [dup dip loop] cons branch
|
|
||||||
|
|
||||||
boolean [Q] loop
|
|
||||||
boolean [Q] [] swap [dup dip loop] cons branch
|
|
||||||
boolean [] [Q] [dup dip loop] cons branch
|
|
||||||
boolean [] [[Q] dup dip loop] branch
|
|
||||||
|
|
||||||
In action the false branch does nothing while the true branch does:
|
|
||||||
|
|
||||||
t [] [[Q] dup dip loop] branch
|
|
||||||
[Q] dup dip loop
|
|
||||||
[Q] [Q] dip loop
|
|
||||||
Q [Q] loop
|
|
||||||
|
|
||||||
Because `loop` expects and consumes a Boolean value, the `Q` function must be compatible with the previous stack *and itself* with a boolean flag for the next iteration:
|
|
||||||
|
|
||||||
Q == G b
|
|
||||||
|
|
||||||
Q [Q] loop
|
|
||||||
G b [Q] loop
|
|
||||||
G Q [Q] loop
|
|
||||||
G G b [Q] loop
|
|
||||||
G G Q [Q] loop
|
|
||||||
G G G b [Q] loop
|
|
||||||
G G G
|
|
||||||
|
|
||||||
|
|
||||||
### `while`
|
|
||||||
|
|
||||||
Keep doing `B` _while_ some predicate `P` is true. This is convenient as the predicate function is made nullary automatically and the body function can be designed without regard to leaving a Boolean flag for the next iteration:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[P] [B] while
|
|
||||||
--------------------------------------
|
|
||||||
[P] nullary [B [P] nullary] loop
|
|
||||||
|
|
||||||
|
|
||||||
while == swap [nullary] cons dup dipd concat loop
|
|
||||||
|
|
||||||
|
|
||||||
[P] [B] while
|
|
||||||
[P] [B] swap [nullary] cons dup dipd concat loop
|
|
||||||
[B] [P] [nullary] cons dup dipd concat loop
|
|
||||||
[B] [[P] nullary] dup dipd concat loop
|
|
||||||
[B] [[P] nullary] [[P] nullary] dipd concat loop
|
|
||||||
[P] nullary [B] [[P] nullary] concat loop
|
|
||||||
[P] nullary [B [P] nullary] loop
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Parallel
|
|
||||||
|
|
||||||
The *parallel* operation indicates that two (or more) functions *do not interfere* with each other and so can run in parallel. The main difficulty in this sort of thing is orchestrating the recombining ("join" or "wait") of the results of the functions after they finish.
|
|
||||||
|
|
||||||
The current implementaions and the following definitions *are not actually parallel* (yet), but there is no reason they couldn't be reimplemented in terms of e.g. Python threads. I am not concerned with performance of the system just yet, only the elegance of the code it allows us to write.
|
|
||||||
|
|
||||||
### `cleave`
|
|
||||||
|
|
||||||
Joy has a few parallel combinators, the main one being `cleave`:
|
|
||||||
|
|
||||||
... x [A] [B] cleave
|
|
||||||
---------------------------------------------------------
|
|
||||||
... [x ...] [A] infra first [x ...] [B] infra first
|
|
||||||
---------------------------------------------------------
|
|
||||||
... a b
|
|
||||||
|
|
||||||
The `cleave` combinator expects a value and two quotes and it executes each quote in "separate universes" such that neither can affect the other, then it takes the first item from the stack in each universe and replaces the value and quotes with their respective results.
|
|
||||||
|
|
||||||
(I think this corresponds to the "fork" operator, the little upward-pointed triangle, that takes two functions `A :: x -> a` and `B :: x -> b` and returns a function `F :: x -> (a, b)`, in Conal Elliott's "Compiling to Categories" paper, et. al.)
|
|
||||||
|
|
||||||
Just a thought, if you `cleave` two jobs and one requires more time to finish than the other you'd like to be able to assign resources accordingly so that they both finish at the same time.
|
|
||||||
|
|
||||||
### "Apply" Functions
|
|
||||||
|
|
||||||
There are also `app2` and `app3` which run a single quote on more than one value:
|
|
||||||
|
|
||||||
... y x [Q] app2
|
|
||||||
---------------------------------------------------------
|
|
||||||
... [y ...] [Q] infra first [x ...] [Q] infra first
|
|
||||||
|
|
||||||
|
|
||||||
... z y x [Q] app3
|
|
||||||
---------------------------------
|
|
||||||
... [z ...] [Q] infra first
|
|
||||||
[y ...] [Q] infra first
|
|
||||||
[x ...] [Q] infra first
|
|
||||||
|
|
||||||
Because the quoted program can be `i` we can define `cleave` in terms of `app2`:
|
|
||||||
|
|
||||||
cleave == [i] app2 [popd] dip
|
|
||||||
|
|
||||||
(I'm not sure why `cleave` was specified to take that value, I may make a combinator that does the same thing but without expecting a value.)
|
|
||||||
|
|
||||||
clv == [i] app2
|
|
||||||
|
|
||||||
[A] [B] clv
|
|
||||||
------------------
|
|
||||||
a b
|
|
||||||
|
|
||||||
### `map`
|
|
||||||
|
|
||||||
The common `map` function in Joy should also be though of as a *parallel* operator:
|
|
||||||
|
|
||||||
[a b c ...] [Q] map
|
|
||||||
|
|
||||||
There is no reason why the implementation of `map` couldn't distribute the `Q` function over e.g. a pool of worker CPUs.
|
|
||||||
|
|
||||||
### `pam`
|
|
||||||
|
|
||||||
One of my favorite combinators, the `pam` combinator is just:
|
|
||||||
|
|
||||||
pam == [i] map
|
|
||||||
|
|
||||||
This can be used to run any number of programs separately on the current stack and combine their (first) outputs in a result list.
|
|
||||||
|
|
||||||
[[A] [B] [C] ...] [i] map
|
|
||||||
-------------------------------
|
|
||||||
[ a b c ...]
|
|
||||||
|
|
||||||
### Handling Other Kinds of Join
|
|
||||||
|
|
||||||
The `cleave` operators and others all have pretty brutal join semantics: everything works and we always wait for every sub-computation. We can imagine a few different potentially useful patterns of "joining" results from parallel combinators.
|
|
||||||
|
|
||||||
#### first-to-finish
|
|
||||||
|
|
||||||
Thinking about variations of `pam` there could be one that only returns the first result of the first-to-finish sub-program, or the stack could be replaced by its output stack.
|
|
||||||
|
|
||||||
The other sub-programs would be cancelled.
|
|
||||||
|
|
||||||
#### "Fulminators"
|
|
||||||
|
|
||||||
Also known as "Futures" or "Promises" (by *everybody* else. "Fulinators" is what I was going to call them when I was thinking about implementing them in Thun.)
|
|
||||||
|
|
||||||
The runtime could be amended to permit "thunks" representing the results of in-progress computations to be left on the stack and picked up by subsequent functions. These would themselves be able to leave behind more "thunks", the values of which depend on the eventual resolution of the values of the previous thunks.
|
|
||||||
|
|
||||||
In this way you can create "chains" (and more complex shapes) out of normal-looking code that consist of a kind of call-graph interspersed with "asyncronous" ... events?
|
|
||||||
|
|
||||||
In any case, until I can find a rigorous theory that shows that this sort of thing works perfectly in Joy code I'm not going to worry about it. (And I think the Categories can deal with it anyhow? Incremental evaluation, yeah?)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,336 +0,0 @@
|
||||||
The Four Fundamental Operations of Definite Action
|
|
||||||
==================================================
|
|
||||||
|
|
||||||
All definite actions (computer program) can be defined by four
|
|
||||||
fundamental patterns of combination:
|
|
||||||
|
|
||||||
1. Sequence
|
|
||||||
2. Branch
|
|
||||||
3. Loop
|
|
||||||
4. Parallel
|
|
||||||
|
|
||||||
Sequence
|
|
||||||
--------
|
|
||||||
|
|
||||||
Do one thing after another. In joy this is represented by putting two
|
|
||||||
symbols together, juxtaposition:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
foo bar
|
|
||||||
|
|
||||||
Operations have inputs and outputs. The outputs of ``foo`` must be
|
|
||||||
compatible in “arity”, type, and shape with the inputs of ``bar``.
|
|
||||||
|
|
||||||
Branch
|
|
||||||
------
|
|
||||||
|
|
||||||
Do one thing or another.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
boolean [F] [T] branch
|
|
||||||
|
|
||||||
|
|
||||||
t [F] [T] branch
|
|
||||||
----------------------
|
|
||||||
T
|
|
||||||
|
|
||||||
|
|
||||||
f [F] [T] branch
|
|
||||||
----------------------
|
|
||||||
F
|
|
||||||
|
|
||||||
|
|
||||||
branch == unit cons swap pick i
|
|
||||||
|
|
||||||
boolean [F] [T] branch
|
|
||||||
boolean [F] [T] unit cons swap pick i
|
|
||||||
boolean [F] [[T]] cons swap pick i
|
|
||||||
boolean [[F] [T]] swap pick i
|
|
||||||
[[F] [T]] boolean pick i
|
|
||||||
[F-or-T] i
|
|
||||||
|
|
||||||
Given some branch function ``G``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
G == [F] [T] branch
|
|
||||||
|
|
||||||
Used in a sequence like so:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
foo G bar
|
|
||||||
|
|
||||||
The inputs and outputs of ``F`` and ``T`` must be compatible with the
|
|
||||||
outputs for ``foo`` and the inputs of ``bar``, respectively.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
foo F bar
|
|
||||||
|
|
||||||
foo T bar
|
|
||||||
|
|
||||||
``ifte``
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
Often it will be easier on the programmer to write branching code with
|
|
||||||
the predicate specified in a quote. The ``ifte`` combinator provides
|
|
||||||
this (``T`` for “then” and ``E`` for “else”):
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[P] [T] [E] ifte
|
|
||||||
|
|
||||||
Defined in terms of ``branch``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
ifte == [nullary not] dip branch
|
|
||||||
|
|
||||||
In this case, ``P`` must be compatible with the stack and return a
|
|
||||||
Boolean value, and ``T`` and ``E`` both must be compatible with the
|
|
||||||
preceeding and following functions, as described above for ``F`` and
|
|
||||||
``T``. (Note that in the current implementation we are depending on
|
|
||||||
Python for the underlying semantics, so the Boolean value doesn’t *have*
|
|
||||||
to be Boolean because Python’s rules for “truthiness” will be used to
|
|
||||||
evaluate it. I reflect this in the structure of the stack effect comment
|
|
||||||
of ``branch``, it will only accept Boolean values, and in the definition
|
|
||||||
of ``ifte`` above by including ``not`` in the quote, which also has the
|
|
||||||
effect that the subject quotes are in the proper order for ``branch``.)
|
|
||||||
|
|
||||||
Loop
|
|
||||||
----
|
|
||||||
|
|
||||||
Do one thing zero or more times.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
boolean [Q] loop
|
|
||||||
|
|
||||||
|
|
||||||
t [Q] loop
|
|
||||||
----------------
|
|
||||||
Q [Q] loop
|
|
||||||
|
|
||||||
|
|
||||||
... f [Q] loop
|
|
||||||
--------------------
|
|
||||||
...
|
|
||||||
|
|
||||||
The ``loop`` combinator generates a copy of itself in the true branch.
|
|
||||||
This is the hallmark of recursive defintions. In Thun there is no
|
|
||||||
equivalent to conventional loops. (There is, however, the ``x``
|
|
||||||
combinator, defined as ``x == dup i``, which permits recursive
|
|
||||||
constructs that do not need to be directly self-referential, unlike
|
|
||||||
``loop`` and ``genrec``.)
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
loop == [] swap [dup dip loop] cons branch
|
|
||||||
|
|
||||||
boolean [Q] loop
|
|
||||||
boolean [Q] [] swap [dup dip loop] cons branch
|
|
||||||
boolean [] [Q] [dup dip loop] cons branch
|
|
||||||
boolean [] [[Q] dup dip loop] branch
|
|
||||||
|
|
||||||
In action the false branch does nothing while the true branch does:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
t [] [[Q] dup dip loop] branch
|
|
||||||
[Q] dup dip loop
|
|
||||||
[Q] [Q] dip loop
|
|
||||||
Q [Q] loop
|
|
||||||
|
|
||||||
Because ``loop`` expects and consumes a Boolean value, the ``Q``
|
|
||||||
function must be compatible with the previous stack *and itself* with a
|
|
||||||
boolean flag for the next iteration:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Q == G b
|
|
||||||
|
|
||||||
Q [Q] loop
|
|
||||||
G b [Q] loop
|
|
||||||
G Q [Q] loop
|
|
||||||
G G b [Q] loop
|
|
||||||
G G Q [Q] loop
|
|
||||||
G G G b [Q] loop
|
|
||||||
G G G
|
|
||||||
|
|
||||||
``while``
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
Keep doing ``B`` *while* some predicate ``P`` is true. This is
|
|
||||||
convenient as the predicate function is made nullary automatically and
|
|
||||||
the body function can be designed without regard to leaving a Boolean
|
|
||||||
flag for the next iteration:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[P] [B] while
|
|
||||||
--------------------------------------
|
|
||||||
[P] nullary [B [P] nullary] loop
|
|
||||||
|
|
||||||
|
|
||||||
while == swap [nullary] cons dup dipd concat loop
|
|
||||||
|
|
||||||
|
|
||||||
[P] [B] while
|
|
||||||
[P] [B] swap [nullary] cons dup dipd concat loop
|
|
||||||
[B] [P] [nullary] cons dup dipd concat loop
|
|
||||||
[B] [[P] nullary] dup dipd concat loop
|
|
||||||
[B] [[P] nullary] [[P] nullary] dipd concat loop
|
|
||||||
[P] nullary [B] [[P] nullary] concat loop
|
|
||||||
[P] nullary [B [P] nullary] loop
|
|
||||||
|
|
||||||
Parallel
|
|
||||||
--------
|
|
||||||
|
|
||||||
The *parallel* operation indicates that two (or more) functions *do not
|
|
||||||
interfere* with each other and so can run in parallel. The main
|
|
||||||
difficulty in this sort of thing is orchestrating the recombining
|
|
||||||
(“join” or “wait”) of the results of the functions after they finish.
|
|
||||||
|
|
||||||
The current implementaions and the following definitions *are not
|
|
||||||
actually parallel* (yet), but there is no reason they couldn’t be
|
|
||||||
reimplemented in terms of e.g. Python threads. I am not concerned with
|
|
||||||
performance of the system just yet, only the elegance of the code it
|
|
||||||
allows us to write.
|
|
||||||
|
|
||||||
``cleave``
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
Joy has a few parallel combinators, the main one being ``cleave``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
... x [A] [B] cleave
|
|
||||||
---------------------------------------------------------
|
|
||||||
... [x ...] [A] infra first [x ...] [B] infra first
|
|
||||||
---------------------------------------------------------
|
|
||||||
... a b
|
|
||||||
|
|
||||||
The ``cleave`` combinator expects a value and two quotes and it executes
|
|
||||||
each quote in “separate universes” such that neither can affect the
|
|
||||||
other, then it takes the first item from the stack in each universe and
|
|
||||||
replaces the value and quotes with their respective results.
|
|
||||||
|
|
||||||
(I think this corresponds to the “fork” operator, the little
|
|
||||||
upward-pointed triangle, that takes two functions ``A :: x -> a`` and
|
|
||||||
``B :: x -> b`` and returns a function ``F :: x -> (a, b)``, in Conal
|
|
||||||
Elliott’s “Compiling to Categories” paper, et. al.)
|
|
||||||
|
|
||||||
Just a thought, if you ``cleave`` two jobs and one requires more time to
|
|
||||||
finish than the other you’d like to be able to assign resources
|
|
||||||
accordingly so that they both finish at the same time.
|
|
||||||
|
|
||||||
“Apply” Functions
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
There are also ``app2`` and ``app3`` which run a single quote on more
|
|
||||||
than one value:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
... y x [Q] app2
|
|
||||||
---------------------------------------------------------
|
|
||||||
... [y ...] [Q] infra first [x ...] [Q] infra first
|
|
||||||
|
|
||||||
|
|
||||||
... z y x [Q] app3
|
|
||||||
---------------------------------
|
|
||||||
... [z ...] [Q] infra first
|
|
||||||
[y ...] [Q] infra first
|
|
||||||
[x ...] [Q] infra first
|
|
||||||
|
|
||||||
Because the quoted program can be ``i`` we can define ``cleave`` in
|
|
||||||
terms of ``app2``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
cleave == [i] app2 [popd] dip
|
|
||||||
|
|
||||||
(I’m not sure why ``cleave`` was specified to take that value, I may
|
|
||||||
make a combinator that does the same thing but without expecting a
|
|
||||||
value.)
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
clv == [i] app2
|
|
||||||
|
|
||||||
[A] [B] clv
|
|
||||||
------------------
|
|
||||||
a b
|
|
||||||
|
|
||||||
``map``
|
|
||||||
~~~~~~~
|
|
||||||
|
|
||||||
The common ``map`` function in Joy should also be though of as a
|
|
||||||
*parallel* operator:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[a b c ...] [Q] map
|
|
||||||
|
|
||||||
There is no reason why the implementation of ``map`` couldn’t distribute
|
|
||||||
the ``Q`` function over e.g. a pool of worker CPUs.
|
|
||||||
|
|
||||||
``pam``
|
|
||||||
~~~~~~~
|
|
||||||
|
|
||||||
One of my favorite combinators, the ``pam`` combinator is just:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
pam == [i] map
|
|
||||||
|
|
||||||
This can be used to run any number of programs separately on the current
|
|
||||||
stack and combine their (first) outputs in a result list.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[[A] [B] [C] ...] [i] map
|
|
||||||
-------------------------------
|
|
||||||
[ a b c ...]
|
|
||||||
|
|
||||||
Handling Other Kinds of Join
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``cleave`` operators and others all have pretty brutal join
|
|
||||||
semantics: everything works and we always wait for every
|
|
||||||
sub-computation. We can imagine a few different potentially useful
|
|
||||||
patterns of “joining” results from parallel combinators.
|
|
||||||
|
|
||||||
first-to-finish
|
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Thinking about variations of ``pam`` there could be one that only
|
|
||||||
returns the first result of the first-to-finish sub-program, or the
|
|
||||||
stack could be replaced by its output stack.
|
|
||||||
|
|
||||||
The other sub-programs would be cancelled.
|
|
||||||
|
|
||||||
“Fulminators”
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Also known as “Futures” or “Promises” (by *everybody* else. “Fulinators”
|
|
||||||
is what I was going to call them when I was thinking about implementing
|
|
||||||
them in Thun.)
|
|
||||||
|
|
||||||
The runtime could be amended to permit “thunks” representing the results
|
|
||||||
of in-progress computations to be left on the stack and picked up by
|
|
||||||
subsequent functions. These would themselves be able to leave behind
|
|
||||||
more “thunks”, the values of which depend on the eventual resolution of
|
|
||||||
the values of the previous thunks.
|
|
||||||
|
|
||||||
In this way you can create “chains” (and more complex shapes) out of
|
|
||||||
normal-looking code that consist of a kind of call-graph interspersed
|
|
||||||
with “asyncronous” … events?
|
|
||||||
|
|
||||||
In any case, until I can find a rigorous theory that shows that this
|
|
||||||
sort of thing works perfectly in Joy code I’m not going to worry about
|
|
||||||
it. (And I think the Categories can deal with it anyhow? Incremental
|
|
||||||
evaluation, yeah?)
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,465 +0,0 @@
|
||||||
# Treating Trees II: `treestep`
|
|
||||||
Let's consider a tree structure, similar to one described ["Why functional programming matters" by John Hughes](https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf), that consists of a node value followed by zero or more child trees. (The asterisk is meant to indicate the [Kleene star](https://en.wikipedia.org/wiki/Kleene_star).)
|
|
||||||
|
|
||||||
tree = [] | [node tree*]
|
|
||||||
|
|
||||||
In the spirit of `step` we are going to define a combinator `treestep` which expects a tree and three additional items: a base-case function `[B]`, and two quoted programs `[N]` and `[C]`.
|
|
||||||
|
|
||||||
tree [B] [N] [C] treestep
|
|
||||||
|
|
||||||
If the current tree node is empty then just execute `B`:
|
|
||||||
|
|
||||||
[] [B] [N] [C] treestep
|
|
||||||
---------------------------
|
|
||||||
[] B
|
|
||||||
|
|
||||||
Otherwise, evaluate `N` on the node value, `map` the whole function (abbreviated here as `K`) over the child trees recursively, and then combine the result with `C`.
|
|
||||||
|
|
||||||
[node tree*] [B] [N] [C] treestep
|
|
||||||
--------------------------------------- w/ K == [B] [N] [C] treestep
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
|
|
||||||
(Later on we'll experiment with making `map` part of `C` so you can use other combinators.)
|
|
||||||
|
|
||||||
## Derive the recursive function.
|
|
||||||
We can begin to derive it by finding the `ifte` stage that `genrec` will produce.
|
|
||||||
|
|
||||||
K == [not] [B] [R0] [R1] genrec
|
|
||||||
== [not] [B] [R0 [K] R1] ifte
|
|
||||||
|
|
||||||
So we just have to derive `J`:
|
|
||||||
|
|
||||||
J == R0 [K] R1
|
|
||||||
|
|
||||||
The behavior of `J` is to accept a (non-empty) tree node and arrive at the desired outcome.
|
|
||||||
|
|
||||||
[node tree*] J
|
|
||||||
------------------------------
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
|
|
||||||
So `J` will have some form like:
|
|
||||||
|
|
||||||
J == ... [N] ... [K] ... [C] ...
|
|
||||||
|
|
||||||
Let's dive in. First, unquote the node and `dip` `N`.
|
|
||||||
|
|
||||||
[node tree*] uncons [N] dip
|
|
||||||
node [tree*] [N] dip
|
|
||||||
node N [tree*]
|
|
||||||
|
|
||||||
Next, `map` `K` over the child trees and combine with `C`.
|
|
||||||
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
node N [K.tree*] C
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
J == uncons [N] dip [K] map C
|
|
||||||
|
|
||||||
Plug it in and convert to `genrec`:
|
|
||||||
|
|
||||||
K == [not] [B] [J ] ifte
|
|
||||||
== [not] [B] [uncons [N] dip [K] map C] ifte
|
|
||||||
== [not] [B] [uncons [N] dip] [map C] genrec
|
|
||||||
|
|
||||||
## Extract the givens to parameterize the program.
|
|
||||||
Working backwards:
|
|
||||||
|
|
||||||
[not] [B] [uncons [N] dip] [map C] genrec
|
|
||||||
[B] [not] swap [uncons [N] dip] [map C] genrec
|
|
||||||
[B] [uncons [N] dip] [[not] swap] dip [map C] genrec
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
[B] [[N] dip] [uncons] swoncat [[not] swap] dip [map C] genrec
|
|
||||||
[B] [N] [dip] cons [uncons] swoncat [[not] swap] dip [map C] genrec
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Extract a couple of auxiliary definitions:
|
|
||||||
|
|
||||||
TS.0 == [[not] swap] dip
|
|
||||||
TS.1 == [dip] cons [uncons] swoncat
|
|
||||||
|
|
||||||
[B] [N] TS.1 TS.0 [map C] genrec
|
|
||||||
[B] [N] [map C] [TS.1 TS.0] dip genrec
|
|
||||||
[B] [N] [C] [map] swoncat [TS.1 TS.0] dip genrec
|
|
||||||
|
|
||||||
The givens are all to the left so we have our definition.
|
|
||||||
|
|
||||||
### (alternate) Extract the givens to parameterize the program.
|
|
||||||
Working backwards:
|
|
||||||
|
|
||||||
[not] [B] [uncons [N] dip] [map C] genrec
|
|
||||||
[not] [B] [N] [dip] cons [uncons] swoncat [map C] genrec
|
|
||||||
[B] [N] [not] roll> [dip] cons [uncons] swoncat [map C] genrec
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
## Define `treestep`
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from notebook_preamble import D, J, V, define, DefinitionWrapper
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
DefinitionWrapper.add_definitions('''
|
|
||||||
|
|
||||||
_treestep_0 == [[not] swap] dip
|
|
||||||
_treestep_1 == [dip] cons [uncons] swoncat
|
|
||||||
treegrind == [_treestep_1 _treestep_0] dip genrec
|
|
||||||
treestep == [map] swoncat treegrind
|
|
||||||
|
|
||||||
''', D)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
Consider trees, the nodes of which are integers. We can find the sum of all nodes in a tree with this function:
|
|
||||||
|
|
||||||
sumtree == [pop 0] [] [sum +] treestep
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('sumtree == [pop 0] [] [sum +] treestep')
|
|
||||||
```
|
|
||||||
|
|
||||||
Running this function on an empty tree value gives zero:
|
|
||||||
|
|
||||||
[] [pop 0] [] [sum +] treestep
|
|
||||||
------------------------------------
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[] sumtree') # Empty tree.
|
|
||||||
```
|
|
||||||
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
Running it on a non-empty node:
|
|
||||||
|
|
||||||
[n tree*] [pop 0] [] [sum +] treestep
|
|
||||||
n [tree*] [[pop 0] [] [sum +] treestep] map sum +
|
|
||||||
n [ ... ] sum +
|
|
||||||
n m +
|
|
||||||
n+m
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23] sumtree') # No child trees.
|
|
||||||
```
|
|
||||||
|
|
||||||
23
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23 []] sumtree') # Child tree, empty.
|
|
||||||
```
|
|
||||||
|
|
||||||
23
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23 [2 [4]] [3]] sumtree') # Non-empty child trees.
|
|
||||||
```
|
|
||||||
|
|
||||||
32
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] sumtree') # Etc...
|
|
||||||
```
|
|
||||||
|
|
||||||
49
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [] [cons sum] treestep') # Alternate "spelling".
|
|
||||||
```
|
|
||||||
|
|
||||||
49
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 23] [cons] treestep') # Replace each node.
|
|
||||||
```
|
|
||||||
|
|
||||||
[23 [23 [23] [23]] [23] [23 []]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 [1 [1] [1]] [1] [1 []]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep sumtree')
|
|
||||||
```
|
|
||||||
|
|
||||||
6
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [pop 1] [sum +] treestep') # Combine replace and sum into one function.
|
|
||||||
```
|
|
||||||
|
|
||||||
6
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[4 [3 [] [7]]] [pop 0] [pop 1] [sum +] treestep') # Combine replace and sum into one function.
|
|
||||||
```
|
|
||||||
|
|
||||||
3
|
|
||||||
|
|
||||||
|
|
||||||
## Redefining the Ordered Binary Tree in terms of `treestep`.
|
|
||||||
|
|
||||||
Tree = [] | [[key value] left right]
|
|
||||||
|
|
||||||
What kind of functions can we write for this with our `treestep`?
|
|
||||||
|
|
||||||
The pattern for processing a non-empty node is:
|
|
||||||
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
|
|
||||||
Plugging in our BTree structure:
|
|
||||||
|
|
||||||
[key value] N [left right] [K] map C
|
|
||||||
|
|
||||||
### Traversal
|
|
||||||
[key value] first [left right] [K] map i
|
|
||||||
key [value] [left right] [K] map i
|
|
||||||
key [left right] [K] map i
|
|
||||||
key [lkey rkey ] i
|
|
||||||
key lkey rkey
|
|
||||||
|
|
||||||
This doesn't quite work:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[[3 0] [[2 0] [][]] [[9 0] [[5 0] [[4 0] [][]] [[8 0] [[6 0] [] [[7 0] [][]]][]]][]]] ["B"] [first] [i] treestep')
|
|
||||||
```
|
|
||||||
|
|
||||||
3 'B' 'B'
|
|
||||||
|
|
||||||
|
|
||||||
Doesn't work because `map` extracts the `first` item of whatever its mapped function produces. We have to return a list, rather than depositing our results directly on the stack.
|
|
||||||
|
|
||||||
|
|
||||||
[key value] N [left right] [K] map C
|
|
||||||
|
|
||||||
[key value] first [left right] [K] map flatten cons
|
|
||||||
key [left right] [K] map flatten cons
|
|
||||||
key [[lk] [rk] ] flatten cons
|
|
||||||
key [ lk rk ] cons
|
|
||||||
[key lk rk ]
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
[] [first] [flatten cons] treestep
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [first] [flatten cons] treestep')
|
|
||||||
```
|
|
||||||
|
|
||||||
[3 2 9 5 4 8 6 7]
|
|
||||||
|
|
||||||
|
|
||||||
There we go.
|
|
||||||
|
|
||||||
### In-order traversal
|
|
||||||
|
|
||||||
From here:
|
|
||||||
|
|
||||||
key [[lk] [rk]] C
|
|
||||||
key [[lk] [rk]] i
|
|
||||||
key [lk] [rk] roll<
|
|
||||||
[lk] [rk] key swons concat
|
|
||||||
[lk] [key rk] concat
|
|
||||||
[lk key rk]
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
[] [i roll< swons concat] [first] treestep
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [uncons pop] [i roll< swons concat] treestep')
|
|
||||||
```
|
|
||||||
|
|
||||||
[2 3 4 5 6 7 8 9]
|
|
||||||
|
|
||||||
|
|
||||||
## With `treegrind`?
|
|
||||||
The `treegrind` function doesn't include the `map` combinator, so the `[C]` function must arrange to use some combinator on the quoted recursive copy `[K]`. With this function, the pattern for processing a non-empty node is:
|
|
||||||
|
|
||||||
node N [tree*] [K] C
|
|
||||||
|
|
||||||
Plugging in our BTree structure:
|
|
||||||
|
|
||||||
[key value] N [left right] [K] C
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[["key" "value"] ["left"] ["right"] ] ["B"] ["N"] ["C"] treegrind')
|
|
||||||
```
|
|
||||||
|
|
||||||
['key' 'value'] 'N' [['left'] ['right']] [[not] ['B'] [uncons ['N'] dip] ['C'] genrec] 'C'
|
|
||||||
|
|
||||||
|
|
||||||
## `treegrind` with `step`
|
|
||||||
|
|
||||||
Iteration through the nodes
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [pop] ["N"] [step] treegrind')
|
|
||||||
```
|
|
||||||
|
|
||||||
[3 0] 'N' [2 0] 'N' [9 0] 'N' [5 0] 'N' [4 0] 'N' [8 0] 'N' [6 0] 'N' [7 0] 'N'
|
|
||||||
|
|
||||||
|
|
||||||
Sum the nodes' keys.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('0 [[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [pop] [first +] [step] treegrind')
|
|
||||||
```
|
|
||||||
|
|
||||||
44
|
|
||||||
|
|
||||||
|
|
||||||
Rebuild the tree using `map` (imitating `treestep`.)
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [[100 +] infra] [map cons] treegrind')
|
|
||||||
```
|
|
||||||
|
|
||||||
[[103 0] [[102 0] [] []] [[109 0] [[105 0] [[104 0] [] []] [[108 0] [[106 0] [] [[107 0] [] []]] []]] []]]
|
|
||||||
|
|
||||||
|
|
||||||
## Do we have the flexibility to reimplement `Tree-get`?
|
|
||||||
I think we do:
|
|
||||||
|
|
||||||
[B] [N] [C] treegrind
|
|
||||||
|
|
||||||
We'll start by saying that the base-case (the key is not in the tree) is user defined, and the per-node function is just the query key literal:
|
|
||||||
|
|
||||||
[B] [query_key] [C] treegrind
|
|
||||||
|
|
||||||
This means we just have to define `C` from:
|
|
||||||
|
|
||||||
[key value] query_key [left right] [K] C
|
|
||||||
|
|
||||||
|
|
||||||
Let's try `cmp`:
|
|
||||||
|
|
||||||
C == P [T>] [E] [T<] cmp
|
|
||||||
|
|
||||||
[key value] query_key [left right] [K] P [T>] [E] [T<] cmp
|
|
||||||
|
|
||||||
### The predicate `P`
|
|
||||||
Seems pretty easy (we must preserve the value in case the keys are equal):
|
|
||||||
|
|
||||||
[key value] query_key [left right] [K] P
|
|
||||||
[key value] query_key [left right] [K] roll<
|
|
||||||
[key value] [left right] [K] query_key [roll< uncons swap] dip
|
|
||||||
|
|
||||||
[key value] [left right] [K] roll< uncons swap query_key
|
|
||||||
[left right] [K] [key value] uncons swap query_key
|
|
||||||
[left right] [K] key [value] swap query_key
|
|
||||||
[left right] [K] [value] key query_key
|
|
||||||
|
|
||||||
P == roll< [roll< uncons swap] dip
|
|
||||||
|
|
||||||
(Possibly with a swap at the end? Or just swap `T<` and `T>`.)
|
|
||||||
|
|
||||||
So now:
|
|
||||||
|
|
||||||
[left right] [K] [value] key query_key [T>] [E] [T<] cmp
|
|
||||||
|
|
||||||
Becomes one of these three:
|
|
||||||
|
|
||||||
[left right] [K] [value] T>
|
|
||||||
[left right] [K] [value] E
|
|
||||||
[left right] [K] [value] T<
|
|
||||||
|
|
||||||
|
|
||||||
### `E`
|
|
||||||
Easy.
|
|
||||||
|
|
||||||
E == roll> popop first
|
|
||||||
|
|
||||||
### `T<` and `T>`
|
|
||||||
|
|
||||||
T< == pop [first] dip i
|
|
||||||
T> == pop [second] dip i
|
|
||||||
|
|
||||||
## Putting it together
|
|
||||||
|
|
||||||
|
|
||||||
T> == pop [first] dip i
|
|
||||||
T< == pop [second] dip i
|
|
||||||
E == roll> popop first
|
|
||||||
P == roll< [roll< uncons swap] dip
|
|
||||||
|
|
||||||
Tree-get == [P [T>] [E] [T<] cmp] treegrind
|
|
||||||
|
|
||||||
To me, that seems simpler than the `genrec` version.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
DefinitionWrapper.add_definitions('''
|
|
||||||
|
|
||||||
T> == pop [first] dip i
|
|
||||||
T< == pop [second] dip i
|
|
||||||
E == roll> popop first
|
|
||||||
P == roll< [roll< uncons swap] dip
|
|
||||||
|
|
||||||
Tree-get == [P [T>] [E] [T<] cmp] treegrind
|
|
||||||
|
|
||||||
''', D)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('''\
|
|
||||||
|
|
||||||
[[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]
|
|
||||||
|
|
||||||
[] [5] Tree-get
|
|
||||||
|
|
||||||
''')
|
|
||||||
```
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('''\
|
|
||||||
|
|
||||||
[[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]
|
|
||||||
|
|
||||||
[pop "nope"] [25] Tree-get
|
|
||||||
|
|
||||||
''')
|
|
||||||
```
|
|
||||||
|
|
||||||
'nope'
|
|
||||||
|
|
||||||
|
|
@ -1,620 +0,0 @@
|
||||||
Treating Trees II: ``treestep``
|
|
||||||
===============================
|
|
||||||
|
|
||||||
Let’s consider a tree structure, similar to one described `“Why
|
|
||||||
functional programming matters” by John
|
|
||||||
Hughes <https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf>`__,
|
|
||||||
that consists of a node value followed by zero or more child trees. (The
|
|
||||||
asterisk is meant to indicate the `Kleene
|
|
||||||
star <https://en.wikipedia.org/wiki/Kleene_star>`__.)
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
tree = [] | [node tree*]
|
|
||||||
|
|
||||||
In the spirit of ``step`` we are going to define a combinator
|
|
||||||
``treestep`` which expects a tree and three additional items: a
|
|
||||||
base-case function ``[B]``, and two quoted programs ``[N]`` and ``[C]``.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
tree [B] [N] [C] treestep
|
|
||||||
|
|
||||||
If the current tree node is empty then just execute ``B``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[] [B] [N] [C] treestep
|
|
||||||
---------------------------
|
|
||||||
[] B
|
|
||||||
|
|
||||||
Otherwise, evaluate ``N`` on the node value, ``map`` the whole function
|
|
||||||
(abbreviated here as ``K``) over the child trees recursively, and then
|
|
||||||
combine the result with ``C``.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[node tree*] [B] [N] [C] treestep
|
|
||||||
--------------------------------------- w/ K == [B] [N] [C] treestep
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
|
|
||||||
(Later on we’ll experiment with making ``map`` part of ``C`` so you can
|
|
||||||
use other combinators.)
|
|
||||||
|
|
||||||
Derive the recursive function.
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
We can begin to derive it by finding the ``ifte`` stage that ``genrec``
|
|
||||||
will produce.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
K == [not] [B] [R0] [R1] genrec
|
|
||||||
== [not] [B] [R0 [K] R1] ifte
|
|
||||||
|
|
||||||
So we just have to derive ``J``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
J == R0 [K] R1
|
|
||||||
|
|
||||||
The behavior of ``J`` is to accept a (non-empty) tree node and arrive at
|
|
||||||
the desired outcome.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[node tree*] J
|
|
||||||
------------------------------
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
|
|
||||||
So ``J`` will have some form like:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
J == ... [N] ... [K] ... [C] ...
|
|
||||||
|
|
||||||
Let’s dive in. First, unquote the node and ``dip`` ``N``.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[node tree*] uncons [N] dip
|
|
||||||
node [tree*] [N] dip
|
|
||||||
node N [tree*]
|
|
||||||
|
|
||||||
Next, ``map`` ``K`` over the child trees and combine with ``C``.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
node N [K.tree*] C
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
J == uncons [N] dip [K] map C
|
|
||||||
|
|
||||||
Plug it in and convert to ``genrec``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
K == [not] [B] [J ] ifte
|
|
||||||
== [not] [B] [uncons [N] dip [K] map C] ifte
|
|
||||||
== [not] [B] [uncons [N] dip] [map C] genrec
|
|
||||||
|
|
||||||
Extract the givens to parameterize the program.
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
Working backwards:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[not] [B] [uncons [N] dip] [map C] genrec
|
|
||||||
[B] [not] swap [uncons [N] dip] [map C] genrec
|
|
||||||
[B] [uncons [N] dip] [[not] swap] dip [map C] genrec
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
[B] [[N] dip] [uncons] swoncat [[not] swap] dip [map C] genrec
|
|
||||||
[B] [N] [dip] cons [uncons] swoncat [[not] swap] dip [map C] genrec
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Extract a couple of auxiliary definitions:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
TS.0 == [[not] swap] dip
|
|
||||||
TS.1 == [dip] cons [uncons] swoncat
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[B] [N] TS.1 TS.0 [map C] genrec
|
|
||||||
[B] [N] [map C] [TS.1 TS.0] dip genrec
|
|
||||||
[B] [N] [C] [map] swoncat [TS.1 TS.0] dip genrec
|
|
||||||
|
|
||||||
The givens are all to the left so we have our definition.
|
|
||||||
|
|
||||||
(alternate) Extract the givens to parameterize the program.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Working backwards:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[not] [B] [uncons [N] dip] [map C] genrec
|
|
||||||
[not] [B] [N] [dip] cons [uncons] swoncat [map C] genrec
|
|
||||||
[B] [N] [not] roll> [dip] cons [uncons] swoncat [map C] genrec
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Define ``treestep``
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from notebook_preamble import D, J, V, define, DefinitionWrapper
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
DefinitionWrapper.add_definitions('''
|
|
||||||
|
|
||||||
_treestep_0 == [[not] swap] dip
|
|
||||||
_treestep_1 == [dip] cons [uncons] swoncat
|
|
||||||
treegrind == [_treestep_1 _treestep_0] dip genrec
|
|
||||||
treestep == [map] swoncat treegrind
|
|
||||||
|
|
||||||
''', D)
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
Consider trees, the nodes of which are integers. We can find the sum of
|
|
||||||
all nodes in a tree with this function:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
sumtree == [pop 0] [] [sum +] treestep
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('sumtree == [pop 0] [] [sum +] treestep')
|
|
||||||
|
|
||||||
Running this function on an empty tree value gives zero:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[] [pop 0] [] [sum +] treestep
|
|
||||||
------------------------------------
|
|
||||||
0
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[] sumtree') # Empty tree.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
Running it on a non-empty node:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[n tree*] [pop 0] [] [sum +] treestep
|
|
||||||
n [tree*] [[pop 0] [] [sum +] treestep] map sum +
|
|
||||||
n [ ... ] sum +
|
|
||||||
n m +
|
|
||||||
n+m
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23] sumtree') # No child trees.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
23
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23 []] sumtree') # Child tree, empty.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
23
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23 [2 [4]] [3]] sumtree') # Non-empty child trees.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
32
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] sumtree') # Etc...
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
49
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [] [cons sum] treestep') # Alternate "spelling".
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
49
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 23] [cons] treestep') # Replace each node.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[23 [23 [23] [23]] [23] [23 []]]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 [1 [1] [1]] [1] [1 []]]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep sumtree')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
6
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [pop 1] [sum +] treestep') # Combine replace and sum into one function.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
6
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[4 [3 [] [7]]] [pop 0] [pop 1] [sum +] treestep') # Combine replace and sum into one function.
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3
|
|
||||||
|
|
||||||
|
|
||||||
Redefining the Ordered Binary Tree in terms of ``treestep``.
|
|
||||||
------------------------------------------------------------
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Tree = [] | [[key value] left right]
|
|
||||||
|
|
||||||
What kind of functions can we write for this with our ``treestep``?
|
|
||||||
|
|
||||||
The pattern for processing a non-empty node is:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
node N [tree*] [K] map C
|
|
||||||
|
|
||||||
Plugging in our BTree structure:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[key value] N [left right] [K] map C
|
|
||||||
|
|
||||||
Traversal
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[key value] first [left right] [K] map i
|
|
||||||
key [value] [left right] [K] map i
|
|
||||||
key [left right] [K] map i
|
|
||||||
key [lkey rkey ] i
|
|
||||||
key lkey rkey
|
|
||||||
|
|
||||||
This doesn’t quite work:
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[[3 0] [[2 0] [][]] [[9 0] [[5 0] [[4 0] [][]] [[8 0] [[6 0] [] [[7 0] [][]]][]]][]]] ["B"] [first] [i] treestep')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
3 'B' 'B'
|
|
||||||
|
|
||||||
|
|
||||||
Doesn’t work because ``map`` extracts the ``first`` item of whatever its
|
|
||||||
mapped function produces. We have to return a list, rather than
|
|
||||||
depositing our results directly on the stack.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[key value] N [left right] [K] map C
|
|
||||||
|
|
||||||
[key value] first [left right] [K] map flatten cons
|
|
||||||
key [left right] [K] map flatten cons
|
|
||||||
key [[lk] [rk] ] flatten cons
|
|
||||||
key [ lk rk ] cons
|
|
||||||
[key lk rk ]
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[] [first] [flatten cons] treestep
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [first] [flatten cons] treestep')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[3 2 9 5 4 8 6 7]
|
|
||||||
|
|
||||||
|
|
||||||
There we go.
|
|
||||||
|
|
||||||
In-order traversal
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
From here:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
key [[lk] [rk]] C
|
|
||||||
key [[lk] [rk]] i
|
|
||||||
key [lk] [rk] roll<
|
|
||||||
[lk] [rk] key swons concat
|
|
||||||
[lk] [key rk] concat
|
|
||||||
[lk key rk]
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[] [i roll< swons concat] [first] treestep
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [uncons pop] [i roll< swons concat] treestep')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[2 3 4 5 6 7 8 9]
|
|
||||||
|
|
||||||
|
|
||||||
With ``treegrind``?
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
The ``treegrind`` function doesn’t include the ``map`` combinator, so
|
|
||||||
the ``[C]`` function must arrange to use some combinator on the quoted
|
|
||||||
recursive copy ``[K]``. With this function, the pattern for processing a
|
|
||||||
non-empty node is:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
node N [tree*] [K] C
|
|
||||||
|
|
||||||
Plugging in our BTree structure:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[key value] N [left right] [K] C
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[["key" "value"] ["left"] ["right"] ] ["B"] ["N"] ["C"] treegrind')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
['key' 'value'] 'N' [['left'] ['right']] [[not] ['B'] [uncons ['N'] dip] ['C'] genrec] 'C'
|
|
||||||
|
|
||||||
|
|
||||||
``treegrind`` with ``step``
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
Iteration through the nodes
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [pop] ["N"] [step] treegrind')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[3 0] 'N' [2 0] 'N' [9 0] 'N' [5 0] 'N' [4 0] 'N' [8 0] 'N' [6 0] 'N' [7 0] 'N'
|
|
||||||
|
|
||||||
|
|
||||||
Sum the nodes’ keys.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('0 [[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [pop] [first +] [step] treegrind')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
44
|
|
||||||
|
|
||||||
|
|
||||||
Rebuild the tree using ``map`` (imitating ``treestep``.)
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [[100 +] infra] [map cons] treegrind')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[[103 0] [[102 0] [] []] [[109 0] [[105 0] [[104 0] [] []] [[108 0] [[106 0] [] [[107 0] [] []]] []]] []]]
|
|
||||||
|
|
||||||
|
|
||||||
Do we have the flexibility to reimplement ``Tree-get``?
|
|
||||||
-------------------------------------------------------
|
|
||||||
|
|
||||||
I think we do:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[B] [N] [C] treegrind
|
|
||||||
|
|
||||||
We’ll start by saying that the base-case (the key is not in the tree) is
|
|
||||||
user defined, and the per-node function is just the query key literal:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[B] [query_key] [C] treegrind
|
|
||||||
|
|
||||||
This means we just have to define ``C`` from:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[key value] query_key [left right] [K] C
|
|
||||||
|
|
||||||
Let’s try ``cmp``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
C == P [T>] [E] [T<] cmp
|
|
||||||
|
|
||||||
[key value] query_key [left right] [K] P [T>] [E] [T<] cmp
|
|
||||||
|
|
||||||
The predicate ``P``
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Seems pretty easy (we must preserve the value in case the keys are
|
|
||||||
equal):
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[key value] query_key [left right] [K] P
|
|
||||||
[key value] query_key [left right] [K] roll<
|
|
||||||
[key value] [left right] [K] query_key [roll< uncons swap] dip
|
|
||||||
|
|
||||||
[key value] [left right] [K] roll< uncons swap query_key
|
|
||||||
[left right] [K] [key value] uncons swap query_key
|
|
||||||
[left right] [K] key [value] swap query_key
|
|
||||||
[left right] [K] [value] key query_key
|
|
||||||
|
|
||||||
P == roll< [roll< uncons swap] dip
|
|
||||||
|
|
||||||
(Possibly with a swap at the end? Or just swap ``T<`` and ``T>``.)
|
|
||||||
|
|
||||||
So now:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[left right] [K] [value] key query_key [T>] [E] [T<] cmp
|
|
||||||
|
|
||||||
Becomes one of these three:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[left right] [K] [value] T>
|
|
||||||
[left right] [K] [value] E
|
|
||||||
[left right] [K] [value] T<
|
|
||||||
|
|
||||||
``E``
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
Easy.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
E == roll> popop first
|
|
||||||
|
|
||||||
``T<`` and ``T>``
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
T< == pop [first] dip i
|
|
||||||
T> == pop [second] dip i
|
|
||||||
|
|
||||||
Putting it together
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
T> == pop [first] dip i
|
|
||||||
T< == pop [second] dip i
|
|
||||||
E == roll> popop first
|
|
||||||
P == roll< [roll< uncons swap] dip
|
|
||||||
|
|
||||||
Tree-get == [P [T>] [E] [T<] cmp] treegrind
|
|
||||||
|
|
||||||
To me, that seems simpler than the ``genrec`` version.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
DefinitionWrapper.add_definitions('''
|
|
||||||
|
|
||||||
T> == pop [first] dip i
|
|
||||||
T< == pop [second] dip i
|
|
||||||
E == roll> popop first
|
|
||||||
P == roll< [roll< uncons swap] dip
|
|
||||||
|
|
||||||
Tree-get == [P [T>] [E] [T<] cmp] treegrind
|
|
||||||
|
|
||||||
''', D)
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('''\
|
|
||||||
|
|
||||||
[[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]
|
|
||||||
|
|
||||||
[] [5] Tree-get
|
|
||||||
|
|
||||||
''')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
15
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('''\
|
|
||||||
|
|
||||||
[[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]
|
|
||||||
|
|
||||||
[pop "nope"] [25] Tree-get
|
|
||||||
|
|
||||||
''')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
'nope'
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,154 +0,0 @@
|
||||||
# Type Checking
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
import logging, sys
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
format='%(message)s',
|
|
||||||
stream=sys.stdout,
|
|
||||||
level=logging.INFO,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from joy.utils.types import (
|
|
||||||
doc_from_stack_effect,
|
|
||||||
infer,
|
|
||||||
reify,
|
|
||||||
unify,
|
|
||||||
FUNCTIONS,
|
|
||||||
JoyTypeError,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
D = FUNCTIONS.copy()
|
|
||||||
del D['product']
|
|
||||||
globals().update(D)
|
|
||||||
```
|
|
||||||
|
|
||||||
## An Example
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
fi, fo = infer(pop, swap, rolldown, rrest, ccons)[0]
|
|
||||||
```
|
|
||||||
|
|
||||||
25 (--) ∘ pop swap rolldown rrest ccons
|
|
||||||
28 (a1 --) ∘ swap rolldown rrest ccons
|
|
||||||
31 (a3 a2 a1 -- a2 a3) ∘ rolldown rrest ccons
|
|
||||||
34 (a4 a3 a2 a1 -- a2 a3 a4) ∘ rrest ccons
|
|
||||||
37 ([a4 a5 ...1] a3 a2 a1 -- a2 a3 [...1]) ∘ ccons
|
|
||||||
40 ([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1]) ∘
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
```
|
|
||||||
|
|
||||||
([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from joy.parser import text_to_expression
|
|
||||||
from joy.utils.stack import stack_to_string
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
e = text_to_expression('0 1 2 [3 4]') # reverse order
|
|
||||||
print stack_to_string(e)
|
|
||||||
```
|
|
||||||
|
|
||||||
[3 4] 2 1 0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
u = unify(e, fi)[0]
|
|
||||||
u
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{a1: 0, a2: 1, a3: 2, a4: 3, a5: 4, s2: (), s1: ()}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
g = reify(u, (fi, fo))
|
|
||||||
print doc_from_stack_effect(*g)
|
|
||||||
```
|
|
||||||
|
|
||||||
(... [3 4 ] 2 1 0 -- ... [1 2 ])
|
|
||||||
|
|
||||||
|
|
||||||
## Unification Works "in Reverse"
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
e = text_to_expression('[2 3]')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
u = unify(e, fo)[0] # output side, not input side
|
|
||||||
u
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{a2: 2, a3: 3, s2: (), s1: ()}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
g = reify(u, (fi, fo))
|
|
||||||
print doc_from_stack_effect(*g)
|
|
||||||
```
|
|
||||||
|
|
||||||
(... [a4 a5 ] 3 2 a1 -- ... [2 3 ])
|
|
||||||
|
|
||||||
|
|
||||||
## Failing a Check
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
fi, fo = infer(dup, mul)[0]
|
|
||||||
```
|
|
||||||
|
|
||||||
25 (--) ∘ dup mul
|
|
||||||
28 (a1 -- a1 a1) ∘ mul
|
|
||||||
31 (f1 -- f2) ∘
|
|
||||||
31 (i1 -- i2) ∘
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
e = text_to_expression('"two"')
|
|
||||||
print stack_to_string(e)
|
|
||||||
```
|
|
||||||
|
|
||||||
'two'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
try:
|
|
||||||
unify(e, fi)
|
|
||||||
except JoyTypeError, err:
|
|
||||||
print err
|
|
||||||
```
|
|
||||||
|
|
||||||
Cannot unify 'two' and f1.
|
|
||||||
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
Type Checking
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
import logging, sys
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
format='%(message)s',
|
|
||||||
stream=sys.stdout,
|
|
||||||
level=logging.INFO,
|
|
||||||
)
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from joy.utils.types import (
|
|
||||||
doc_from_stack_effect,
|
|
||||||
infer,
|
|
||||||
reify,
|
|
||||||
unify,
|
|
||||||
FUNCTIONS,
|
|
||||||
JoyTypeError,
|
|
||||||
)
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
D = FUNCTIONS.copy()
|
|
||||||
del D['product']
|
|
||||||
globals().update(D)
|
|
||||||
|
|
||||||
An Example
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
fi, fo = infer(pop, swap, rolldown, rrest, ccons)[0]
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
25 (--) ∘ pop swap rolldown rrest ccons
|
|
||||||
28 (a1 --) ∘ swap rolldown rrest ccons
|
|
||||||
31 (a3 a2 a1 -- a2 a3) ∘ rolldown rrest ccons
|
|
||||||
34 (a4 a3 a2 a1 -- a2 a3 a4) ∘ rrest ccons
|
|
||||||
37 ([a4 a5 ...1] a3 a2 a1 -- a2 a3 [...1]) ∘ ccons
|
|
||||||
40 ([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1]) ∘
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
print doc_from_stack_effect(fi, fo)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1])
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from joy.parser import text_to_expression
|
|
||||||
from joy.utils.stack import stack_to_string
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
e = text_to_expression('0 1 2 [3 4]') # reverse order
|
|
||||||
print stack_to_string(e)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[3 4] 2 1 0
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
u = unify(e, fi)[0]
|
|
||||||
u
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
{a1: 0, a2: 1, a3: 2, a4: 3, a5: 4, s2: (), s1: ()}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
g = reify(u, (fi, fo))
|
|
||||||
print doc_from_stack_effect(*g)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
(... [3 4 ] 2 1 0 -- ... [1 2 ])
|
|
||||||
|
|
||||||
|
|
||||||
Unification Works “in Reverse”
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
e = text_to_expression('[2 3]')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
u = unify(e, fo)[0] # output side, not input side
|
|
||||||
u
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
{a2: 2, a3: 3, s2: (), s1: ()}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
g = reify(u, (fi, fo))
|
|
||||||
print doc_from_stack_effect(*g)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
(... [a4 a5 ] 3 2 a1 -- ... [2 3 ])
|
|
||||||
|
|
||||||
|
|
||||||
Failing a Check
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
fi, fo = infer(dup, mul)[0]
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
25 (--) ∘ dup mul
|
|
||||||
28 (a1 -- a1 a1) ∘ mul
|
|
||||||
31 (f1 -- f2) ∘
|
|
||||||
31 (i1 -- i2) ∘
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
e = text_to_expression('"two"')
|
|
||||||
print stack_to_string(e)
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
'two'
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
try:
|
|
||||||
unify(e, fi)
|
|
||||||
except JoyTypeError, err:
|
|
||||||
print err
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
Cannot unify 'two' and f1.
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,281 +0,0 @@
|
||||||
# Traversing Datastructures with Zippers
|
|
||||||
This notebook is about using the "zipper" with joy datastructures. See the [Zipper wikipedia entry](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29) or the original paper: ["FUNCTIONAL PEARL The Zipper" by Gérard Huet](https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf)
|
|
||||||
|
|
||||||
|
|
||||||
Given a datastructure on the stack we can navigate through it, modify it, and rebuild it using the "zipper" technique.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
## Trees
|
|
||||||
In Joypy there aren't any complex datastructures, just ints, floats, strings, Symbols (strings that are names of functions) and sequences (aka lists, aka quoted literals, aka aggregates, etc...), but we can build [trees](https://en.wikipedia.org/wiki/Tree_%28data_structure%29) out of sequences.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 [2 [3 4 25 6] 7] 8]')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 [2 [3 4 25 6] 7] 8]
|
|
||||||
|
|
||||||
|
|
||||||
## Zipper in Joy
|
|
||||||
Zippers work by keeping track of the current item, the already-seen items, and the yet-to-be seen items as you traverse a datastructure (the datastructure used to keep track of these items is the zipper.)
|
|
||||||
|
|
||||||
In Joy we can do this with the following words:
|
|
||||||
|
|
||||||
z-down == [] swap uncons swap
|
|
||||||
z-up == swons swap shunt
|
|
||||||
z-right == [swons] cons dip uncons swap
|
|
||||||
z-left == swons [uncons swap] dip swap
|
|
||||||
|
|
||||||
Let's use them to change 25 into 625. The first time a word is used I show the trace so you can see how it works. If we were going to use these a lot it would make sense to write Python versions for efficiency, but see below.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('z-down == [] swap uncons swap')
|
|
||||||
define('z-up == swons swap shunt')
|
|
||||||
define('z-right == [swons] cons dip uncons swap')
|
|
||||||
define('z-left == swons [uncons swap] dip swap')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[1 [2 [3 4 25 6] 7] 8] z-down')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [1 [2 [3 4 25 6] 7] 8] z-down
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] . z-down
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] . [] swap uncons swap
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] [] . swap uncons swap
|
|
||||||
[] [1 [2 [3 4 25 6] 7] 8] . uncons swap
|
|
||||||
[] 1 [[2 [3 4 25 6] 7] 8] . swap
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] 1 .
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[] [[2 [3 4 25 6] 7] 8] 1 z-right')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [] [[2 [3 4 25 6] 7] 8] 1 z-right
|
|
||||||
[] . [[2 [3 4 25 6] 7] 8] 1 z-right
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] . 1 z-right
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] 1 . z-right
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] 1 . [swons] cons dip uncons swap
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] 1 [swons] . cons dip uncons swap
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] [1 swons] . dip uncons swap
|
|
||||||
[] . 1 swons [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
[] 1 . swons [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
[] 1 . swap cons [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
1 [] . cons [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
[1] . [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
[1] [[2 [3 4 25 6] 7] 8] . uncons swap
|
|
||||||
[1] [2 [3 4 25 6] 7] [8] . swap
|
|
||||||
[1] [8] [2 [3 4 25 6] 7] .
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1] [8] [2 [3 4 25 6] 7] z-down')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1] [8] [] [[3 4 25 6] 7] 2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1] [8] [] [[3 4 25 6] 7] 2 z-right')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [3 4 25 6]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1] [8] [2] [7] [3 4 25 6] z-down')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [] [4 25 6] 3
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1] [8] [2] [7] [] [4 25 6] 3 z-right')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [3] [25 6] 4
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1] [8] [2] [7] [3] [25 6] 4 z-right')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 25
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1] [8] [2] [7] [4 3] [6] 25 sqr')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 625
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[1] [8] [2] [7] [4 3] [6] 625 z-up')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [1] [8] [2] [7] [4 3] [6] 625 z-up
|
|
||||||
[1] . [8] [2] [7] [4 3] [6] 625 z-up
|
|
||||||
[1] [8] . [2] [7] [4 3] [6] 625 z-up
|
|
||||||
[1] [8] [2] . [7] [4 3] [6] 625 z-up
|
|
||||||
[1] [8] [2] [7] . [4 3] [6] 625 z-up
|
|
||||||
[1] [8] [2] [7] [4 3] . [6] 625 z-up
|
|
||||||
[1] [8] [2] [7] [4 3] [6] . 625 z-up
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 625 . z-up
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 625 . swons swap shunt
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 625 . swap cons swap shunt
|
|
||||||
[1] [8] [2] [7] [4 3] 625 [6] . cons swap shunt
|
|
||||||
[1] [8] [2] [7] [4 3] [625 6] . swap shunt
|
|
||||||
[1] [8] [2] [7] [625 6] [4 3] . shunt
|
|
||||||
[1] [8] [2] [7] [3 4 625 6] .
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1] [8] [2] [7] [3 4 625 6] z-up')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1] [8] [2 [3 4 625 6] 7]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1] [8] [2 [3 4 625 6] 7] z-up')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 [2 [3 4 625 6] 7] 8]
|
|
||||||
|
|
||||||
|
|
||||||
## `dip` and `infra`
|
|
||||||
In Joy we have the `dip` and `infra` combinators which can "target" or "address" any particular item in a Joy tree structure.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('[1 [2 [3 4 25 6] 7] 8] [[[[[[sqr] dipd] infra] dip] infra] dip] infra')
|
|
||||||
```
|
|
||||||
|
|
||||||
. [1 [2 [3 4 25 6] 7] 8] [[[[[[sqr] dipd] infra] dip] infra] dip] infra
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] . [[[[[[sqr] dipd] infra] dip] infra] dip] infra
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] [[[[[[sqr] dipd] infra] dip] infra] dip] . infra
|
|
||||||
8 [2 [3 4 25 6] 7] 1 . [[[[[sqr] dipd] infra] dip] infra] dip [] swaack
|
|
||||||
8 [2 [3 4 25 6] 7] 1 [[[[[sqr] dipd] infra] dip] infra] . dip [] swaack
|
|
||||||
8 [2 [3 4 25 6] 7] . [[[[sqr] dipd] infra] dip] infra 1 [] swaack
|
|
||||||
8 [2 [3 4 25 6] 7] [[[[sqr] dipd] infra] dip] . infra 1 [] swaack
|
|
||||||
7 [3 4 25 6] 2 . [[[sqr] dipd] infra] dip [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 25 6] 2 [[[sqr] dipd] infra] . dip [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 25 6] . [[sqr] dipd] infra 2 [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 25 6] [[sqr] dipd] . infra 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 4 3 . [sqr] dipd [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 4 3 [sqr] . dipd [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 . sqr 4 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 . dup mul 4 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 25 . mul 4 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 625 . 4 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 625 4 . 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 625 4 3 . [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 625 4 3 [7] . swaack 2 [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 625 6] . 2 [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 625 6] 2 . [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 625 6] 2 [8] . swaack 1 [] swaack
|
|
||||||
8 [2 [3 4 625 6] 7] . 1 [] swaack
|
|
||||||
8 [2 [3 4 625 6] 7] 1 . [] swaack
|
|
||||||
8 [2 [3 4 625 6] 7] 1 [] . swaack
|
|
||||||
[1 [2 [3 4 625 6] 7] 8] .
|
|
||||||
|
|
||||||
|
|
||||||
If you read the trace carefully you'll see that about half of it is the `dip` and `infra` combinators de-quoting programs and "digging" into the subject datastructure. Instead of maintaining temporary results on the stack they are pushed into the pending expression (continuation). When `sqr` has run the rest of the pending expression rebuilds the datastructure.
|
|
||||||
|
|
||||||
## `Z`
|
|
||||||
Imagine a function `Z` that accepts a sequence of `dip` and `infra` combinators, a quoted program `[Q]`, and a datastructure to work on. It would effectively execute the quoted program as if it had been embedded in a nested series of quoted programs, e.g.:
|
|
||||||
|
|
||||||
[...] [Q] [dip dip infra dip infra dip infra] Z
|
|
||||||
-------------------------------------------------------------
|
|
||||||
[...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra
|
|
||||||
|
|
||||||
The `Z` function isn't hard to make.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('Z == [[] cons cons] step i')
|
|
||||||
```
|
|
||||||
|
|
||||||
Here it is in action in a simplified scenario.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
V('1 [2 3 4] Z')
|
|
||||||
```
|
|
||||||
|
|
||||||
. 1 [2 3 4] Z
|
|
||||||
1 . [2 3 4] Z
|
|
||||||
1 [2 3 4] . Z
|
|
||||||
1 [2 3 4] . [[] cons cons] step i
|
|
||||||
1 [2 3 4] [[] cons cons] . step i
|
|
||||||
1 2 [[] cons cons] . i [3 4] [[] cons cons] step i
|
|
||||||
1 2 . [] cons cons [3 4] [[] cons cons] step i
|
|
||||||
1 2 [] . cons cons [3 4] [[] cons cons] step i
|
|
||||||
1 [2] . cons [3 4] [[] cons cons] step i
|
|
||||||
[1 2] . [3 4] [[] cons cons] step i
|
|
||||||
[1 2] [3 4] . [[] cons cons] step i
|
|
||||||
[1 2] [3 4] [[] cons cons] . step i
|
|
||||||
[1 2] 3 [[] cons cons] . i [4] [[] cons cons] step i
|
|
||||||
[1 2] 3 . [] cons cons [4] [[] cons cons] step i
|
|
||||||
[1 2] 3 [] . cons cons [4] [[] cons cons] step i
|
|
||||||
[1 2] [3] . cons [4] [[] cons cons] step i
|
|
||||||
[[1 2] 3] . [4] [[] cons cons] step i
|
|
||||||
[[1 2] 3] [4] . [[] cons cons] step i
|
|
||||||
[[1 2] 3] [4] [[] cons cons] . step i
|
|
||||||
[[1 2] 3] 4 [[] cons cons] . i i
|
|
||||||
[[1 2] 3] 4 . [] cons cons i
|
|
||||||
[[1 2] 3] 4 [] . cons cons i
|
|
||||||
[[1 2] 3] [4] . cons i
|
|
||||||
[[[1 2] 3] 4] . i
|
|
||||||
. [[1 2] 3] 4
|
|
||||||
[[1 2] 3] . 4
|
|
||||||
[[1 2] 3] 4 .
|
|
||||||
|
|
||||||
|
|
||||||
And here it is doing the main thing.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 [2 [3 4 25 6] 7] 8] [sqr] [dip dip infra dip infra dip infra] Z')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 [2 [3 4 625 6] 7] 8]
|
|
||||||
|
|
||||||
|
|
||||||
## Addressing
|
|
||||||
Because we are only using two combinators we could replace the list with a string made from only two characters.
|
|
||||||
|
|
||||||
[...] [Q] 'ddididi' Zstr
|
|
||||||
-------------------------------------------------------------
|
|
||||||
[...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra
|
|
||||||
|
|
||||||
The string can be considered a name or address for an item in the subject datastructure.
|
|
||||||
|
|
||||||
## Determining the right "path" for an item in a tree.
|
|
||||||
It's easy to read off (in reverse) the right sequence of "d" and "i" from the subject datastructure:
|
|
||||||
|
|
||||||
[ n [ n [ n n x ...
|
|
||||||
i d i d i d d Bingo!
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
@ -1,352 +0,0 @@
|
||||||
Traversing Datastructures with Zippers
|
|
||||||
======================================
|
|
||||||
|
|
||||||
This notebook is about using the “zipper” with joy datastructures. See
|
|
||||||
the `Zipper wikipedia
|
|
||||||
entry <https://en.wikipedia.org/wiki/Zipper_%28data_structure%29>`__ or
|
|
||||||
the original paper: `“FUNCTIONAL PEARL The Zipper” by Gérard
|
|
||||||
Huet <https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf>`__
|
|
||||||
|
|
||||||
Given a datastructure on the stack we can navigate through it, modify
|
|
||||||
it, and rebuild it using the “zipper” technique.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
from notebook_preamble import J, V, define
|
|
||||||
|
|
||||||
Trees
|
|
||||||
-----
|
|
||||||
|
|
||||||
In Joypy there aren’t any complex datastructures, just ints, floats,
|
|
||||||
strings, Symbols (strings that are names of functions) and sequences
|
|
||||||
(aka lists, aka quoted literals, aka aggregates, etc…), but we can build
|
|
||||||
`trees <https://en.wikipedia.org/wiki/Tree_%28data_structure%29>`__ out
|
|
||||||
of sequences.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1 [2 [3 4 25 6] 7] 8]')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 [2 [3 4 25 6] 7] 8]
|
|
||||||
|
|
||||||
|
|
||||||
Zipper in Joy
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Zippers work by keeping track of the current item, the already-seen
|
|
||||||
items, and the yet-to-be seen items as you traverse a datastructure (the
|
|
||||||
datastructure used to keep track of these items is the zipper.)
|
|
||||||
|
|
||||||
In Joy we can do this with the following words:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
z-down == [] swap uncons swap
|
|
||||||
z-up == swons swap shunt
|
|
||||||
z-right == [swons] cons dip uncons swap
|
|
||||||
z-left == swons [uncons swap] dip swap
|
|
||||||
|
|
||||||
Let’s use them to change 25 into 625. The first time a word is used I
|
|
||||||
show the trace so you can see how it works. If we were going to use
|
|
||||||
these a lot it would make sense to write Python versions for efficiency,
|
|
||||||
but see below.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('z-down == [] swap uncons swap')
|
|
||||||
define('z-up == swons swap shunt')
|
|
||||||
define('z-right == [swons] cons dip uncons swap')
|
|
||||||
define('z-left == swons [uncons swap] dip swap')
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[1 [2 [3 4 25 6] 7] 8] z-down')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [1 [2 [3 4 25 6] 7] 8] z-down
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] . z-down
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] . [] swap uncons swap
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] [] . swap uncons swap
|
|
||||||
[] [1 [2 [3 4 25 6] 7] 8] . uncons swap
|
|
||||||
[] 1 [[2 [3 4 25 6] 7] 8] . swap
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] 1 .
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[] [[2 [3 4 25 6] 7] 8] 1 z-right')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [] [[2 [3 4 25 6] 7] 8] 1 z-right
|
|
||||||
[] . [[2 [3 4 25 6] 7] 8] 1 z-right
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] . 1 z-right
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] 1 . z-right
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] 1 . [swons] cons dip uncons swap
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] 1 [swons] . cons dip uncons swap
|
|
||||||
[] [[2 [3 4 25 6] 7] 8] [1 swons] . dip uncons swap
|
|
||||||
[] . 1 swons [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
[] 1 . swons [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
[] 1 . swap cons [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
1 [] . cons [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
[1] . [[2 [3 4 25 6] 7] 8] uncons swap
|
|
||||||
[1] [[2 [3 4 25 6] 7] 8] . uncons swap
|
|
||||||
[1] [2 [3 4 25 6] 7] [8] . swap
|
|
||||||
[1] [8] [2 [3 4 25 6] 7] .
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1] [8] [2 [3 4 25 6] 7] z-down')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1] [8] [] [[3 4 25 6] 7] 2
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1] [8] [] [[3 4 25 6] 7] 2 z-right')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [3 4 25 6]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1] [8] [2] [7] [3 4 25 6] z-down')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [] [4 25 6] 3
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1] [8] [2] [7] [] [4 25 6] 3 z-right')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [3] [25 6] 4
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1] [8] [2] [7] [3] [25 6] 4 z-right')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 25
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1] [8] [2] [7] [4 3] [6] 25 sqr')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 625
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[1] [8] [2] [7] [4 3] [6] 625 z-up')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [1] [8] [2] [7] [4 3] [6] 625 z-up
|
|
||||||
[1] . [8] [2] [7] [4 3] [6] 625 z-up
|
|
||||||
[1] [8] . [2] [7] [4 3] [6] 625 z-up
|
|
||||||
[1] [8] [2] . [7] [4 3] [6] 625 z-up
|
|
||||||
[1] [8] [2] [7] . [4 3] [6] 625 z-up
|
|
||||||
[1] [8] [2] [7] [4 3] . [6] 625 z-up
|
|
||||||
[1] [8] [2] [7] [4 3] [6] . 625 z-up
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 625 . z-up
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 625 . swons swap shunt
|
|
||||||
[1] [8] [2] [7] [4 3] [6] 625 . swap cons swap shunt
|
|
||||||
[1] [8] [2] [7] [4 3] 625 [6] . cons swap shunt
|
|
||||||
[1] [8] [2] [7] [4 3] [625 6] . swap shunt
|
|
||||||
[1] [8] [2] [7] [625 6] [4 3] . shunt
|
|
||||||
[1] [8] [2] [7] [3 4 625 6] .
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1] [8] [2] [7] [3 4 625 6] z-up')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1] [8] [2 [3 4 625 6] 7]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1] [8] [2 [3 4 625 6] 7] z-up')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 [2 [3 4 625 6] 7] 8]
|
|
||||||
|
|
||||||
|
|
||||||
``dip`` and ``infra``
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
In Joy we have the ``dip`` and ``infra`` combinators which can “target”
|
|
||||||
or “address” any particular item in a Joy tree structure.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('[1 [2 [3 4 25 6] 7] 8] [[[[[[sqr] dipd] infra] dip] infra] dip] infra')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. [1 [2 [3 4 25 6] 7] 8] [[[[[[sqr] dipd] infra] dip] infra] dip] infra
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] . [[[[[[sqr] dipd] infra] dip] infra] dip] infra
|
|
||||||
[1 [2 [3 4 25 6] 7] 8] [[[[[[sqr] dipd] infra] dip] infra] dip] . infra
|
|
||||||
8 [2 [3 4 25 6] 7] 1 . [[[[[sqr] dipd] infra] dip] infra] dip [] swaack
|
|
||||||
8 [2 [3 4 25 6] 7] 1 [[[[[sqr] dipd] infra] dip] infra] . dip [] swaack
|
|
||||||
8 [2 [3 4 25 6] 7] . [[[[sqr] dipd] infra] dip] infra 1 [] swaack
|
|
||||||
8 [2 [3 4 25 6] 7] [[[[sqr] dipd] infra] dip] . infra 1 [] swaack
|
|
||||||
7 [3 4 25 6] 2 . [[[sqr] dipd] infra] dip [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 25 6] 2 [[[sqr] dipd] infra] . dip [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 25 6] . [[sqr] dipd] infra 2 [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 25 6] [[sqr] dipd] . infra 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 4 3 . [sqr] dipd [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 4 3 [sqr] . dipd [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 . sqr 4 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 . dup mul 4 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 25 25 . mul 4 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 625 . 4 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 625 4 . 3 [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 625 4 3 . [7] swaack 2 [8] swaack 1 [] swaack
|
|
||||||
6 625 4 3 [7] . swaack 2 [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 625 6] . 2 [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 625 6] 2 . [8] swaack 1 [] swaack
|
|
||||||
7 [3 4 625 6] 2 [8] . swaack 1 [] swaack
|
|
||||||
8 [2 [3 4 625 6] 7] . 1 [] swaack
|
|
||||||
8 [2 [3 4 625 6] 7] 1 . [] swaack
|
|
||||||
8 [2 [3 4 625 6] 7] 1 [] . swaack
|
|
||||||
[1 [2 [3 4 625 6] 7] 8] .
|
|
||||||
|
|
||||||
|
|
||||||
If you read the trace carefully you’ll see that about half of it is the
|
|
||||||
``dip`` and ``infra`` combinators de-quoting programs and “digging” into
|
|
||||||
the subject datastructure. Instead of maintaining temporary results on
|
|
||||||
the stack they are pushed into the pending expression (continuation).
|
|
||||||
When ``sqr`` has run the rest of the pending expression rebuilds the
|
|
||||||
datastructure.
|
|
||||||
|
|
||||||
``Z``
|
|
||||||
-----
|
|
||||||
|
|
||||||
Imagine a function ``Z`` that accepts a sequence of ``dip`` and
|
|
||||||
``infra`` combinators, a quoted program ``[Q]``, and a datastructure to
|
|
||||||
work on. It would effectively execute the quoted program as if it had
|
|
||||||
been embedded in a nested series of quoted programs, e.g.:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[...] [Q] [dip dip infra dip infra dip infra] Z
|
|
||||||
-------------------------------------------------------------
|
|
||||||
[...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra
|
|
||||||
|
|
||||||
|
|
||||||
The ``Z`` function isn’t hard to make.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
define('Z == [[] cons cons] step i')
|
|
||||||
|
|
||||||
Here it is in action in a simplified scenario.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
V('1 [2 3 4] Z')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
. 1 [2 3 4] Z
|
|
||||||
1 . [2 3 4] Z
|
|
||||||
1 [2 3 4] . Z
|
|
||||||
1 [2 3 4] . [[] cons cons] step i
|
|
||||||
1 [2 3 4] [[] cons cons] . step i
|
|
||||||
1 2 [[] cons cons] . i [3 4] [[] cons cons] step i
|
|
||||||
1 2 . [] cons cons [3 4] [[] cons cons] step i
|
|
||||||
1 2 [] . cons cons [3 4] [[] cons cons] step i
|
|
||||||
1 [2] . cons [3 4] [[] cons cons] step i
|
|
||||||
[1 2] . [3 4] [[] cons cons] step i
|
|
||||||
[1 2] [3 4] . [[] cons cons] step i
|
|
||||||
[1 2] [3 4] [[] cons cons] . step i
|
|
||||||
[1 2] 3 [[] cons cons] . i [4] [[] cons cons] step i
|
|
||||||
[1 2] 3 . [] cons cons [4] [[] cons cons] step i
|
|
||||||
[1 2] 3 [] . cons cons [4] [[] cons cons] step i
|
|
||||||
[1 2] [3] . cons [4] [[] cons cons] step i
|
|
||||||
[[1 2] 3] . [4] [[] cons cons] step i
|
|
||||||
[[1 2] 3] [4] . [[] cons cons] step i
|
|
||||||
[[1 2] 3] [4] [[] cons cons] . step i
|
|
||||||
[[1 2] 3] 4 [[] cons cons] . i i
|
|
||||||
[[1 2] 3] 4 . [] cons cons i
|
|
||||||
[[1 2] 3] 4 [] . cons cons i
|
|
||||||
[[1 2] 3] [4] . cons i
|
|
||||||
[[[1 2] 3] 4] . i
|
|
||||||
. [[1 2] 3] 4
|
|
||||||
[[1 2] 3] . 4
|
|
||||||
[[1 2] 3] 4 .
|
|
||||||
|
|
||||||
|
|
||||||
And here it is doing the main thing.
|
|
||||||
|
|
||||||
.. code:: ipython2
|
|
||||||
|
|
||||||
J('[1 [2 [3 4 25 6] 7] 8] [sqr] [dip dip infra dip infra dip infra] Z')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 [2 [3 4 625 6] 7] 8]
|
|
||||||
|
|
||||||
|
|
||||||
Addressing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Because we are only using two combinators we could replace the list with
|
|
||||||
a string made from only two characters.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[...] [Q] 'ddididi' Zstr
|
|
||||||
-------------------------------------------------------------
|
|
||||||
[...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra
|
|
||||||
|
|
||||||
The string can be considered a name or address for an item in the
|
|
||||||
subject datastructure.
|
|
||||||
|
|
||||||
Determining the right “path” for an item in a tree.
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
It’s easy to read off (in reverse) the right sequence of “d” and “i”
|
|
||||||
from the subject datastructure:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[ n [ n [ n n x ...
|
|
||||||
i d i d i d d Bingo!
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,234 +0,0 @@
|
||||||
```python
|
|
||||||
from notebook_preamble import D, DefinitionWrapper, J, V, define
|
|
||||||
```
|
|
||||||
|
|
||||||
# On "Two Exercises Found in a Book on Algorithmics"
|
|
||||||
|
|
||||||
Bird & Meertens
|
|
||||||
|
|
||||||
[PDF paper available here](https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.694.2614)
|
|
||||||
|
|
||||||
## Define `scan` in terms of a reduction.
|
|
||||||
|
|
||||||
> Problem I. The reduction operator `/` of APL takes some binary operator `⨁` on its left and a vector `x` of values on its right. The meaning of `⨁/x` for `x = [a b ... z]` is the value `a⨁b⨁...⨁z`. For this to be well-defined in the absence of brackets, the operation `⨁` has to be associative. Now there is another operator `\` of APL called `scan`. Its effect is closely related to reduction in that we have:
|
|
||||||
|
|
||||||
⨁\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z]
|
|
||||||
|
|
||||||
> The problem is to find some definition of `scan` as a reduction. In other words, we have to find some function `f` and an operator `⨂` so that
|
|
||||||
|
|
||||||
⨁\x = f(a)⨂f(b)⨂...⨂f(z)
|
|
||||||
|
|
||||||
## Designing the Recursive Function
|
|
||||||
Ignoring the exact requirements (finding `f` and `⨂`) can we implement `scan` as a hylomorphism in Joy?
|
|
||||||
|
|
||||||
Looking at the forms of hylomorphism, `H3` is the one to use:
|
|
||||||
|
|
||||||
### `H3`
|
|
||||||
If the combiner and the generator both need to work on the current value then `dup` must be used, and the generator must produce one item instead of two (the b is instead the duplicate of a.)
|
|
||||||
|
|
||||||
|
|
||||||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
|
||||||
|
|
||||||
... a [G] dupdip [H3] dip F
|
|
||||||
... a G a [H3] dip F
|
|
||||||
... a′ a [H3] dip F
|
|
||||||
... a′ H3 a F
|
|
||||||
... a′ [G] dupdip [H3] dip F a F
|
|
||||||
... a′ G a′ [H3] dip F a F
|
|
||||||
... a″ a′ [H3] dip F a F
|
|
||||||
... a″ H3 a′ F a F
|
|
||||||
... a″ [G] dupdip [H3] dip F a′ F a F
|
|
||||||
... a″ G a″ [H3] dip F a′ F a F
|
|
||||||
... a‴ a″ [H3] dip F a′ F a F
|
|
||||||
... a‴ H3 a″ F a′ F a F
|
|
||||||
... a‴ pop c a″ F a′ F a F
|
|
||||||
... c a″ F a′ F a F
|
|
||||||
... d a′ F a F
|
|
||||||
... d′ a F
|
|
||||||
... d″
|
|
||||||
|
|
||||||
### Initial Definition
|
|
||||||
We're building a list of values so this is an "anamorphism". (An anamorphism uses `[]` for `c` and `swons` for `F`.)
|
|
||||||
|
|
||||||
scan == [P] [pop []] [[G] dupdip] [dip swons] genrec
|
|
||||||
|
|
||||||
Convert to `ifte`:
|
|
||||||
|
|
||||||
scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte
|
|
||||||
|
|
||||||
On the recursive branch `[G] dupdip` doesn't cut it:
|
|
||||||
|
|
||||||
[1 2 3] [G] dupdip [scan] dip swons
|
|
||||||
[1 2 3] G [1 2 3] [scan] dip swons
|
|
||||||
|
|
||||||
### Use `first`
|
|
||||||
At this point, we want the copy of `[1 2 3]` to just be `1`, so we use `first`.
|
|
||||||
|
|
||||||
scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec
|
|
||||||
|
|
||||||
[1 2 3] [G] dupdip first [scan] dip swons
|
|
||||||
[1 2 3] G [1 2 3] first [scan] dip swons
|
|
||||||
[1 2 3] G 1 [scan] dip swons
|
|
||||||
|
|
||||||
### `G` applies `⨁`
|
|
||||||
Now what does `G` have to do? Just apply `⨁` to the first two terms in the list.
|
|
||||||
|
|
||||||
[1 2 3] G
|
|
||||||
[1 2 3] [⨁] infra
|
|
||||||
[1 2 3] [+] infra
|
|
||||||
[3 3]
|
|
||||||
|
|
||||||
### Predicate `P`
|
|
||||||
Which tells us that the predicate `[P]` must guard against lists with less that two items in them:
|
|
||||||
|
|
||||||
P == size 1 <=
|
|
||||||
|
|
||||||
Let's see what we've got so far:
|
|
||||||
|
|
||||||
scan == [P ] [pop []] [[G] dupdip first] [dip swons] genrec
|
|
||||||
scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec
|
|
||||||
|
|
||||||
### Handling the Last Term
|
|
||||||
This works to a point, but it throws away the last term:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 3]
|
|
||||||
|
|
||||||
|
|
||||||
Hmm... Let's take out the `pop` for a sec...
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
[6] [1 3]
|
|
||||||
|
|
||||||
|
|
||||||
That leaves the last item in our list, then it puts an empty list on the stack and `swons`'s the new terms onto that. If we leave out that empty list, they will be `swons`'d onto that list that already has the last item.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 3 6]
|
|
||||||
|
|
||||||
|
|
||||||
### Parameterize `⨁`
|
|
||||||
So we have:
|
|
||||||
|
|
||||||
[⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
|
|
||||||
|
|
||||||
Trivially:
|
|
||||||
|
|
||||||
== [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
|
|
||||||
== [[[⨁] infra] dupdip first] [size 1 <=] [] roll< [dip swons] genrec
|
|
||||||
== [[⨁] infra] [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
|
||||||
== [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
|
||||||
|
|
||||||
And so:
|
|
||||||
|
|
||||||
scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
define('scan [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3 4] [+] scan')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 3 6 10]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3 4] [*] scan')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 2 6 24]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('[1 2 3 4 5 6 7] [neg +] scan')
|
|
||||||
```
|
|
||||||
|
|
||||||
[1 1 2 2 3 3 4]
|
|
||||||
|
|
||||||
|
|
||||||
## Problem 2.
|
|
||||||
> Define a line to be a sequence of characters not containing the newline character. It is easy to define a function `Unlines` that converts a non-empty sequence of lines into a sequence of characters by inserting newline characters between every two lines.
|
|
||||||
>
|
|
||||||
> Since `Unlines` is injective, the function `Lines`, which converts a sequence of characters into a sequence of lines by splitting on newline characters, can be specified as the inverse of `Unlines`.
|
|
||||||
>
|
|
||||||
> The problem, just as in Problem 1. is to find a definition by reduction of the function `Lines`.
|
|
||||||
|
|
||||||
|
|
||||||
Unlines = uncons ['\n' swap + +] step
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
J('["hello" "world"] uncons ["\n" swap + +] step')
|
|
||||||
```
|
|
||||||
|
|
||||||
'hello\nworld'
|
|
||||||
|
|
||||||
|
|
||||||
Again ignoring the actual task let's just derive `Lines`:
|
|
||||||
|
|
||||||
"abc\nefg\nhij" Lines
|
|
||||||
---------------------------
|
|
||||||
["abc" "efg" "hij"]
|
|
||||||
|
|
||||||
Instead of `P == [size 1 <=]` we want `["\n" in]`, and for the base-case of a string with no newlines in it we want to use `unit`:
|
|
||||||
|
|
||||||
Lines == ["\n" in] [unit] [R0] [dip swons] genrec
|
|
||||||
Lines == ["\n" in] [unit] [R0 [Lines] dip swons] ifte
|
|
||||||
|
|
||||||
Derive `R0`:
|
|
||||||
|
|
||||||
"a \n b" R0 [Lines] dip swons
|
|
||||||
"a \n b" split-at-newline swap [Lines] dip swons
|
|
||||||
"a " " b" swap [Lines] dip swons
|
|
||||||
" b" "a " [Lines] dip swons
|
|
||||||
" b" Lines "a " swons
|
|
||||||
[" b"] "a " swons
|
|
||||||
["a " " b"]
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
R0 == split-at-newline swap
|
|
||||||
|
|
||||||
Lines == ["\n" in] [unit] [split-at-newline swap] [dip swons] genrec
|
|
||||||
|
|
||||||
## Missing the Point?
|
|
||||||
This is all good and well, but in the paper many interesting laws and properties are discussed. Am I missing the point?
|
|
||||||
|
|
||||||
0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat
|
|
||||||
|
|
||||||
For associative function `F` and a "unit" element for that function, here represented by `0`.
|
|
||||||
|
|
||||||
For functions that don't have a "unit" we can fake it (the example is given of infinity for the `min(a, b)` function.) We can also use:
|
|
||||||
|
|
||||||
safe_step == [size 1 <=] [] [uncons [F] step] ifte
|
|
||||||
|
|
||||||
Or:
|
|
||||||
|
|
||||||
safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte
|
|
||||||
|
|
||||||
[a b c] [F] safe_step
|
|
||||||
---------------------------
|
|
||||||
a [b c] [F] step
|
|
||||||
|
|
||||||
To limit `F` to working on pairs of terms from its domain.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,336 +0,0 @@
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
from notebook_preamble import D, DefinitionWrapper, J, V, define
|
|
||||||
|
|
||||||
On "Two Exercises Found in a Book on Algorithmics"
|
|
||||||
==================================================
|
|
||||||
|
|
||||||
Bird & Meertens
|
|
||||||
|
|
||||||
`PDF paper available
|
|
||||||
here <https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.694.2614>`__
|
|
||||||
|
|
||||||
Define ``scan`` in terms of a reduction.
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
Problem I. The reduction operator ``/`` of APL takes some binary
|
|
||||||
operator ``⨁`` on its left and a vector ``x`` of values on its
|
|
||||||
right. The meaning of ``⨁/x`` for ``x = [a b ... z]`` is the value
|
|
||||||
``a⨁b⨁...⨁z``. For this to be well-defined in the absence of
|
|
||||||
brackets, the operation ``⨁`` has to be associative. Now there is
|
|
||||||
another operator ``\`` of APL called ``scan``. Its effect is closely
|
|
||||||
related to reduction in that we have:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
⨁\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z]
|
|
||||||
|
|
||||||
The problem is to find some definition of ``scan`` as a reduction.
|
|
||||||
In other words, we have to find some function ``f`` and an operator
|
|
||||||
``⨂`` so that
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
⨁\x = f(a)⨂f(b)⨂...⨂f(z)
|
|
||||||
|
|
||||||
Designing the Recursive Function
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Ignoring the exact requirements (finding ``f`` and ``⨂``) can we
|
|
||||||
implement ``scan`` as a hylomorphism in Joy?
|
|
||||||
|
|
||||||
Looking at the forms of hylomorphism, ``H3`` is the one to use:
|
|
||||||
|
|
||||||
``H3``
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
If the combiner and the generator both need to work on the current value
|
|
||||||
then ``dup`` must be used, and the generator must produce one item
|
|
||||||
instead of two (the b is instead the duplicate of a.)
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
|
||||||
|
|
||||||
... a [G] dupdip [H3] dip F
|
|
||||||
... a G a [H3] dip F
|
|
||||||
... a′ a [H3] dip F
|
|
||||||
... a′ H3 a F
|
|
||||||
... a′ [G] dupdip [H3] dip F a F
|
|
||||||
... a′ G a′ [H3] dip F a F
|
|
||||||
... a″ a′ [H3] dip F a F
|
|
||||||
... a″ H3 a′ F a F
|
|
||||||
... a″ [G] dupdip [H3] dip F a′ F a F
|
|
||||||
... a″ G a″ [H3] dip F a′ F a F
|
|
||||||
... a‴ a″ [H3] dip F a′ F a F
|
|
||||||
... a‴ H3 a″ F a′ F a F
|
|
||||||
... a‴ pop c a″ F a′ F a F
|
|
||||||
... c a″ F a′ F a F
|
|
||||||
... d a′ F a F
|
|
||||||
... d′ a F
|
|
||||||
... d″
|
|
||||||
|
|
||||||
Initial Definition
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
We're building a list of values so this is an "anamorphism". (An
|
|
||||||
anamorphism uses ``[]`` for ``c`` and ``swons`` for ``F``.)
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
scan == [P] [pop []] [[G] dupdip] [dip swons] genrec
|
|
||||||
|
|
||||||
Convert to ``ifte``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte
|
|
||||||
|
|
||||||
On the recursive branch ``[G] dupdip`` doesn't cut it:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[1 2 3] [G] dupdip [scan] dip swons
|
|
||||||
[1 2 3] G [1 2 3] [scan] dip swons
|
|
||||||
|
|
||||||
Use ``first``
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
At this point, we want the copy of ``[1 2 3]`` to just be ``1``, so we
|
|
||||||
use ``first``.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec
|
|
||||||
|
|
||||||
[1 2 3] [G] dupdip first [scan] dip swons
|
|
||||||
[1 2 3] G [1 2 3] first [scan] dip swons
|
|
||||||
[1 2 3] G 1 [scan] dip swons
|
|
||||||
|
|
||||||
``G`` applies ``⨁``
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Now what does ``G`` have to do? Just apply ``⨁`` to the first two terms
|
|
||||||
in the list.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[1 2 3] G
|
|
||||||
[1 2 3] [⨁] infra
|
|
||||||
[1 2 3] [+] infra
|
|
||||||
[3 3]
|
|
||||||
|
|
||||||
Predicate ``P``
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Which tells us that the predicate ``[P]`` must guard against lists with
|
|
||||||
less that two items in them:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
P == size 1 <=
|
|
||||||
|
|
||||||
Let's see what we've got so far:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
scan == [P ] [pop []] [[G] dupdip first] [dip swons] genrec
|
|
||||||
scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec
|
|
||||||
|
|
||||||
Handling the Last Term
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This works to a point, but it throws away the last term:
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 3]
|
|
||||||
|
|
||||||
|
|
||||||
Hmm... Let's take out the ``pop`` for a sec...
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[6] [1 3]
|
|
||||||
|
|
||||||
|
|
||||||
That leaves the last item in our list, then it puts an empty list on the
|
|
||||||
stack and ``swons``'s the new terms onto that. If we leave out that
|
|
||||||
empty list, they will be ``swons``'d onto that list that already has the
|
|
||||||
last item.
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 3 6]
|
|
||||||
|
|
||||||
|
|
||||||
Parameterize ``⨁``
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
So we have:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
|
|
||||||
|
|
||||||
Trivially:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
== [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
|
|
||||||
== [[[⨁] infra] dupdip first] [size 1 <=] [] roll< [dip swons] genrec
|
|
||||||
== [[⨁] infra] [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
|
||||||
== [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
|
||||||
|
|
||||||
And so:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
define('scan [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec')
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3 4] [+] scan')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 3 6 10]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3 4] [*] scan')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 2 6 24]
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('[1 2 3 4 5 6 7] [neg +] scan')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
[1 1 2 2 3 3 4]
|
|
||||||
|
|
||||||
|
|
||||||
Problem 2.
|
|
||||||
----------
|
|
||||||
|
|
||||||
Define a line to be a sequence of characters not containing the
|
|
||||||
newline character. It is easy to define a function ``Unlines`` that
|
|
||||||
converts a non-empty sequence of lines into a sequence of characters
|
|
||||||
by inserting newline characters between every two lines.
|
|
||||||
|
|
||||||
Since ``Unlines`` is injective, the function ``Lines``, which
|
|
||||||
converts a sequence of characters into a sequence of lines by
|
|
||||||
splitting on newline characters, can be specified as the inverse of
|
|
||||||
``Unlines``.
|
|
||||||
|
|
||||||
The problem, just as in Problem 1. is to find a definition by
|
|
||||||
reduction of the function ``Lines``.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Unlines = uncons ['\n' swap + +] step
|
|
||||||
|
|
||||||
.. code:: ipython3
|
|
||||||
|
|
||||||
J('["hello" "world"] uncons ["\n" swap + +] step')
|
|
||||||
|
|
||||||
|
|
||||||
.. parsed-literal::
|
|
||||||
|
|
||||||
'hello\nworld'
|
|
||||||
|
|
||||||
|
|
||||||
Again ignoring the actual task let's just derive ``Lines``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
"abc\nefg\nhij" Lines
|
|
||||||
---------------------------
|
|
||||||
["abc" "efg" "hij"]
|
|
||||||
|
|
||||||
Instead of ``P == [size 1 <=]`` we want ``["\n" in]``, and for the
|
|
||||||
base-case of a string with no newlines in it we want to use ``unit``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Lines == ["\n" in] [unit] [R0] [dip swons] genrec
|
|
||||||
Lines == ["\n" in] [unit] [R0 [Lines] dip swons] ifte
|
|
||||||
|
|
||||||
Derive ``R0``:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
"a \n b" R0 [Lines] dip swons
|
|
||||||
"a \n b" split-at-newline swap [Lines] dip swons
|
|
||||||
"a " " b" swap [Lines] dip swons
|
|
||||||
" b" "a " [Lines] dip swons
|
|
||||||
" b" Lines "a " swons
|
|
||||||
[" b"] "a " swons
|
|
||||||
["a " " b"]
|
|
||||||
|
|
||||||
So:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
R0 == split-at-newline swap
|
|
||||||
|
|
||||||
Lines == ["\n" in] [unit] [split-at-newline swap] [dip swons] genrec
|
|
||||||
|
|
||||||
Missing the Point?
|
|
||||||
------------------
|
|
||||||
|
|
||||||
This is all good and well, but in the paper many interesting laws and
|
|
||||||
properties are discussed. Am I missing the point?
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat
|
|
||||||
|
|
||||||
For associative function ``F`` and a "unit" element for that function,
|
|
||||||
here represented by ``0``.
|
|
||||||
|
|
||||||
For functions that don't have a "unit" we can fake it (the example is
|
|
||||||
given of infinity for the ``min(a, b)`` function.) We can also use:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
safe_step == [size 1 <=] [] [uncons [F] step] ifte
|
|
||||||
|
|
||||||
Or:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte
|
|
||||||
|
|
||||||
[a b c] [F] safe_step
|
|
||||||
---------------------------
|
|
||||||
a [b c] [F] step
|
|
||||||
|
|
||||||
To limit ``F`` to working on pairs of terms from its domain.
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue