Move notebooks to own dir.

This commit is contained in:
Simon Forman
2022-02-19 07:28:27 -08:00
parent 05fff04fba
commit c467393bb7
156 changed files with 1 additions and 1 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,551 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Joypy\n",
"\n",
"## Joy in Python\n",
"\n",
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### [Read-Eval-Print Loop (REPL)](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)\n",
"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:\n",
"\n",
" $ python -m joy\n",
" Joypy - Copyright © 2017 Simon Forman\n",
" This program comes with ABSOLUTELY NO WARRANTY; for details type \"warranty\".\n",
" This is free software, and you are welcome to redistribute it\n",
" under certain conditions; type \"sharing\" for details.\n",
" Type \"words\" to see a list of all words, and \"[<name>] help\" to print the\n",
" docs for a word.\n",
"\n",
"\n",
" <-top\n",
"\n",
" joy? _\n",
"\n",
"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:\n",
"\n",
" joy? 23 sqr 18 +\n",
" . 23 sqr 18 +\n",
" 23 . sqr 18 +\n",
" 23 . dup mul 18 +\n",
" 23 23 . mul 18 +\n",
" 529 . 18 +\n",
" 529 18 . +\n",
" 547 . \n",
"\n",
" 547 <-top\n",
"\n",
" joy? \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Stacks (aka list, quote, sequence, etc.)\n",
"\n",
"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.\n",
"\n",
"[Documentation of Stack Module](https://joypy.osdn.io/stack.html)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The utility functions maintain order.\n",
"The 0th item in the list will be on the top of the stack and *vise versa*."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from joy.utils.stack import iter_stack, list_to_stack"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(1, (2, (3, ())))"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list_to_stack([1, 2, 3])"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(iter_stack((1, (2, (3, ())))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This requires reversing the sequence (or iterating backwards) otherwise:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(3, (2, (1, ())))\n",
"[3, 2, 1]\n"
]
}
],
"source": [
"stack = ()\n",
"\n",
"for n in (1, 2, 3):\n",
" stack = n, stack\n",
"\n",
"print(stack)\n",
"print(list(iter_stack(stack)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Purely Functional Datastructures.\n",
"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)*."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# The `joy()` function.\n",
"## An Interpreter\n",
"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.\n",
"\n",
"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.)\n",
"\n",
"[Documentation of Joy Module](https://joypy.osdn.io/joy.html)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### View function\n",
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The `TracePrinter`.\n",
"\n",
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### [Continuation-Passing Style](https://en.wikipedia.org/wiki/Continuation-passing_style)\n",
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Parser"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"from joy.parser import text_to_expression"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A simple sequence."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(1, (2, (3, (4, (5, ())))))"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"text_to_expression('1 2 3 4 5')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Three items, the first is a list with three items"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((1, (2, (3, ()))), (4, (5, ())))"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"text_to_expression('[1 2 3] 4 5')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A mixed bag."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(1, (23, ((\"four\", ((-5.0, ()), (cons, ()))), (8888, ()))))"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"text_to_expression('1 23 [\"four\" [-5.0] cons] 8888')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Five empty lists."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((), ((), ((), ((), ((), ())))))"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"text_to_expression('[][][][][]')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Five nested lists."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((((((), ()), ()), ()), ()), ())"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"text_to_expression('[[[[[]]]]]')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Library\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"!= % & * *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 gcd2 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 truthy tuck unary uncons unique unit unquoted unstack unswons void warranty while words x xor zip •\n"
]
}
],
"source": [
"import joy.library\n",
"\n",
"print(' '.join(sorted(joy.library.initialize())))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Many of the functions are defined in Python, like `dip`:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"import inspect"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The dip combinator expects a quoted program on the stack and below it\n",
"some item, it hoists the item into the expression and runs the program\n",
"on the rest of the stack.\n",
"::\n",
"\n",
" ... x [Q] dip\n",
" -------------------\n",
" ... Q x\n"
]
}
],
"source": [
"print(inspect.getdoc(joy.library.dip))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The code (I was using ``inspect.getsource()`` here to automatically print the souce but it was not as nice-looking that way due to lack of syntax highlighting and the docstring being too long for the width of the element and wrapping in an ungainly way. SO now, instead, I'm just including it as a Python cell in the notebook):"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"def dip(stack, expression, dictionary):\n",
" try:\n",
" (quote, (x, stack)) = stack\n",
" except ValueError:\n",
" raise StackUnderflowError('Not enough values on stack.')\n",
" return stack, concat(quote, (x, expression)), dictionary"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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.\n",
"\n",
"(Note that the embedded ``joy.library.definitions`` is going away in favor of a ``def.txt`` file that would be read in at start-time. See\n",
"[Ticket: thun-der#7](https://todo.sr.ht/~sforman/thun-der/7))"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"? == dup truthy\n",
"*fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons\n",
"*fraction0 == concat [[swap] dip * [*] dip] infra\n",
"anamorphism == [pop []] swap [dip swons] genrec\n",
"average == [sum 1.0 *] [size] cleave /\n",
"binary == nullary [popop] dip\n",
"cleave == fork [popd] dip\n",
"codireco == cons dip rest cons\n",
"dinfrirst == dip infra first\n",
"unstack == ? [uncons ?] loop pop\n",
"down_to_zero == [0 >] [dup --] while\n",
"dupdipd == dup dipd\n",
"enstacken == stack [clear] dip\n",
"flatten == [] swap [concat] step\n",
"fork == [i] app2\n",
"gcd == 1 [tuck modulus dup 0 >] loop pop\n",
"ifte == [nullary not] dipd branch\n",
"ii == [dip] dupdip i\n",
"least_fraction == dup [gcd] infra [div] concat map\n",
"make_generator == [codireco] ccons\n",
"nullary == [stack] dinfrirst\n",
"of == swap at\n",
"pam == [i] map\n",
"tailrec == [i] genrec\n",
"product == 1 swap [*] step\n",
"quoted == [unit] dip\n",
"range == [0 <=] [1 - dup] anamorphism\n",
"range_to_zero == unit [down_to_zero] infra\n",
"run == [] swap infra\n",
"size == 0 swap [pop ++] step\n",
"sqr == dup mul\n",
"step_zero == 0 roll> step\n",
"swoncat == swap concat\n",
"tailrec == [i] genrec\n",
"ternary == unary [popop] dip\n",
"unary == nullary popd\n",
"unquoted == [i] dip\n",
"while == swap [nullary] cons dup dipd concat loop\n",
"\n"
]
}
],
"source": [
"print(joy.library.definitions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Currently, there's no function to add new definitions to the dictionary from \"within\" Joy code itself. (Actually there is, it's called ``inscribe``, but don't use it, eh? :) Adding new definitions remains a meta-interpreter action. You have to do it yourself, in Python, and wash your hands afterward.\n",
"\n",
"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.)\n",
"\n",
"#### \"There should be only one.\"\n",
"\n",
"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.\n",
"\n",
"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.\n",
"\n",
"#### Literary Code Library\n",
"\n",
"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.\n",
"\n",
"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.\n",
"\n",
"There seems to be an elegance and power to the notation."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,443 @@
# 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
```
@@ -0,0 +1,567 @@
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
@@ -0,0 +1,240 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Preamble\n",
"\n",
"First, import what we need."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from joy.joy import run\n",
"from joy.library import initialize\n",
"from joy.utils.stack import stack_to_string\n",
"from joy.utils.pretty_print import TracePrinter"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define a dictionary, an initial stack, and two helper functions to run Joy code and print results for us."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"D = initialize()\n",
"S = ()\n",
"\n",
"\n",
"def J(text):\n",
" print(stack_to_string(run(text, S, D)[0]))\n",
"\n",
"\n",
"def V(text):\n",
" tp = TracePrinter()\n",
" run(text, S, D, tp.viewer)\n",
" tp.print_()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Run some simple programs"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"41\n"
]
}
],
"source": [
"J('23 18 +')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"15\n"
]
}
],
"source": [
"J('45 30 gcd')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### With Viewer\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" • 23 18 +\n",
" 23 • 18 +\n",
"23 18 • +\n",
" 41 • \n"
]
}
],
"source": [
"V('23 18 +')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" • 45 30 gcd\n",
" 45 • 30 gcd\n",
" 45 30 • gcd\n",
" 45 30 • 1 [tuck modulus dup 0 >] loop pop\n",
" 45 30 1 • [tuck modulus dup 0 >] loop pop\n",
" 45 30 1 [tuck modulus dup 0 >] • loop pop\n",
" 45 30 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 30 45 30 • modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 30 15 • dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 30 15 15 • 0 > [tuck modulus dup 0 >] loop pop\n",
" 30 15 15 0 • > [tuck modulus dup 0 >] loop pop\n",
" 30 15 True • [tuck modulus dup 0 >] loop pop\n",
"30 15 True [tuck modulus dup 0 >] • loop pop\n",
" 30 15 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 15 30 15 • modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 15 0 • dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 15 0 0 • 0 > [tuck modulus dup 0 >] loop pop\n",
" 15 0 0 0 • > [tuck modulus dup 0 >] loop pop\n",
" 15 0 False • [tuck modulus dup 0 >] loop pop\n",
"15 0 False [tuck modulus dup 0 >] • loop pop\n",
" 15 0 • pop\n",
" 15 • \n"
]
}
],
"source": [
"V('45 30 gcd')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here's a longer trace."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" • 96 27 gcd\n",
" 96 • 27 gcd\n",
" 96 27 • gcd\n",
" 96 27 • 1 [tuck modulus dup 0 >] loop pop\n",
" 96 27 1 • [tuck modulus dup 0 >] loop pop\n",
" 96 27 1 [tuck modulus dup 0 >] • loop pop\n",
" 96 27 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 27 96 27 • modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 27 15 • dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 27 15 15 • 0 > [tuck modulus dup 0 >] loop pop\n",
" 27 15 15 0 • > [tuck modulus dup 0 >] loop pop\n",
" 27 15 True • [tuck modulus dup 0 >] loop pop\n",
"27 15 True [tuck modulus dup 0 >] • loop pop\n",
" 27 15 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 15 27 15 • modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 15 12 • dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 15 12 12 • 0 > [tuck modulus dup 0 >] loop pop\n",
" 15 12 12 0 • > [tuck modulus dup 0 >] loop pop\n",
" 15 12 True • [tuck modulus dup 0 >] loop pop\n",
"15 12 True [tuck modulus dup 0 >] • loop pop\n",
" 15 12 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 12 15 12 • modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 12 3 • dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 12 3 3 • 0 > [tuck modulus dup 0 >] loop pop\n",
" 12 3 3 0 • > [tuck modulus dup 0 >] loop pop\n",
" 12 3 True • [tuck modulus dup 0 >] loop pop\n",
" 12 3 True [tuck modulus dup 0 >] • loop pop\n",
" 12 3 • tuck modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 3 12 3 • modulus dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 3 0 • dup 0 > [tuck modulus dup 0 >] loop pop\n",
" 3 0 0 • 0 > [tuck modulus dup 0 >] loop pop\n",
" 3 0 0 0 • > [tuck modulus dup 0 >] loop pop\n",
" 3 0 False • [tuck modulus dup 0 >] loop pop\n",
" 3 0 False [tuck modulus dup 0 >] • loop pop\n",
" 3 0 • pop\n",
" 3 • \n"
]
}
],
"source": [
"V('96 27 gcd')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,136 @@
### 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 •
@@ -0,0 +1,153 @@
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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+686
View File
@@ -0,0 +1,686 @@
# [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.
+791
View File
@@ -0,0 +1,791 @@
`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
@@ -0,0 +1,455 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Advent of Code 2017\n",
"\n",
"## December 1st\n",
"\n",
"\\[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.\n",
"\n",
"For example:\n",
"\n",
"* 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.\n",
"* 1111 produces 4 because each digit (all 1) matches the next.\n",
"* 1234 produces 0 because no digit matches the next.\n",
"* 91212129 produces 9 because the only digit that matches the next one is the last digit, 9."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from notebook_preamble import J, V, define"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I'll assume the input is a Joy sequence of integers (as opposed to a string or something else.)\n",
"\n",
"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.\n",
"\n",
" AoC2017.1 == pair_up total_matches\n",
"\n",
"Let's derive `pair_up`:\n",
"\n",
" [a b c] pair_up\n",
" -------------------------\n",
" [[a b] [b c] [c a]]\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Straightforward (although the order of each pair is reversed, due to the way `zip` works, but it doesn't matter for this program):\n",
"\n",
" [a b c] dup\n",
" [a b c] [a b c] uncons swap\n",
" [a b c] [b c] a unit concat\n",
" [a b c] [b c a] zip\n",
" [[b a] [c b] [a c]]"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"define('pair_up dup uncons swap unit concat zip')"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[2 1] [3 2] [1 3]]\n"
]
}
],
"source": [
"J('[1 2 3] pair_up')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[2 1] [2 2] [3 2] [1 3]]\n"
]
}
],
"source": [
"J('[1 2 2 3] pair_up')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we need to derive `total_matches`. It will be a `step` function:\n",
"\n",
" total_matches == 0 swap [F] step\n",
"\n",
"Where `F` will have the pair to work with, and it will basically be a `branch` or `ifte`.\n",
"\n",
" total [n m] F\n",
"\n",
"It will probably be easier to write if we dequote the pair:\n",
"\n",
" total [n m] i F\n",
" ----------------------\n",
" total n m F\n",
"\n",
"Now `F` becomes just:\n",
"\n",
" total n m [=] [pop +] [popop] ifte\n",
"\n",
"So:\n",
"\n",
" F == i [=] [pop +] [popop] ifte\n",
"\n",
"And thus:\n",
"\n",
" total_matches == 0 swap [i [=] [pop +] [popop] ifte] step"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"define('total_matches 0 swap [i [=] [pop +] [popop] ifte] step')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n"
]
}
],
"source": [
"J('[1 2 3] pair_up total_matches')"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2\n"
]
}
],
"source": [
"J('[1 2 2 3] pair_up total_matches')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can define our main program and evaluate it on the examples."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"define('AoC2017.1 pair_up total_matches')"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3\n"
]
}
],
"source": [
"J('[1 1 2 2] AoC2017.1')"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4\n"
]
}
],
"source": [
"J('[1 1 1 1] AoC2017.1')"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n"
]
}
],
"source": [
"J('[1 2 3 4] AoC2017.1')"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9\n"
]
}
],
"source": [
"J('[9 1 2 1 2 1 2 9] AoC2017.1')"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9\n"
]
}
],
"source": [
"J('[9 1 2 1 2 1 2 9] AoC2017.1')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" pair_up == dup uncons swap unit concat zip\n",
" total_matches == 0 swap [i [=] [pop +] [popop] ifte] step\n",
"\n",
" AoC2017.1 == pair_up total_matches"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now the paired digit is \"halfway\" round.\n",
"\n",
" [a b c d] dup size 2 / [drop] [take reverse] cleave concat zip"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[3 1] [4 2] [1 3] [2 4]]\n"
]
}
],
"source": [
"J('[1 2 3 4] dup size 2 / [drop] [take reverse] cleave concat zip')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I realized that each pair is repeated..."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1 2 3 4] [[1 3] [2 4]]\n"
]
}
],
"source": [
"J('[1 2 3 4] dup size 2 / [drop] [take reverse] cleave zip')"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"define('AoC2017.1.extra dup size 2 / [drop] [take reverse] cleave zip swap pop total_matches 2 *')"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6\n"
]
}
],
"source": [
"J('[1 2 1 2] AoC2017.1.extra')"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n"
]
}
],
"source": [
"J('[1 2 2 1] AoC2017.1.extra')"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4\n"
]
}
],
"source": [
"J('[1 2 3 4 2 5] AoC2017.1.extra')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Refactor FTW\n",
"\n",
"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...\n",
"\n",
" rotate_seq == uncons swap unit concat\n",
" pair_up == dup rotate_seq zip\n",
" add_if_match == [=] [pop +] [popop] ifte\n",
" total_matches == [i add_if_match] step_zero\n",
"\n",
" AoC2017.1 == pair_up total_matches\n",
"\n",
" half_of_size == dup size 2 /\n",
" split_at == [drop] [take reverse] cleave\n",
" pair_up.extra == half_of_size split_at zip swap pop\n",
"\n",
" AoC2017.1.extra == pair_up.extra total_matches 2 *\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,232 @@
# 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 *
@@ -0,0 +1,288 @@
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
@@ -0,0 +1,831 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Advent of Code 2017\n",
"\n",
"## December 2nd\n",
"\n",
"For each row, determine the difference between the largest value and the smallest value; the checksum is the sum of all of these differences.\n",
"\n",
"For example, given the following spreadsheet:\n",
"\n",
" 5 1 9 5\n",
" 7 5 3\n",
" 2 4 6 8\n",
"\n",
"* The first row's largest and smallest values are 9 and 1, and their difference is 8.\n",
"* The second row's largest and smallest values are 7 and 3, and their difference is 4.\n",
"* The third row's difference is 6.\n",
"\n",
"In this example, the spreadsheet's checksum would be 8 + 4 + 6 = 18.\n",
"\n",
"I'll assume the input is a Joy sequence of sequences of integers.\n",
"\n",
" [[5 1 9 5]\n",
" [7 5 3]\n",
" [2 4 6 8]]\n",
"\n",
"So, obviously, the initial form will be a `step` function:\n",
"\n",
" AoC2017.2 == 0 swap [F +] step\n",
"\n",
"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:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
}
],
"source": [
"[maxmin [max] [min] cleave] inscribe"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3 1"
]
}
],
"source": [
"[1 2 3] maxmin"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then `F` just does that then subtracts the min from the max:\n",
"\n",
" F == maxmin -\n",
"\n",
"So:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3 1"
]
}
],
"source": [
"[AoC2017.2 [maxmin - +] step_zero] inscribe"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"18"
]
}
],
"source": [
"clear\n",
"\n",
"[[5 1 9 5]\n",
" [7 5 3]\n",
" [2 4 6 8]] AoC2017.2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"...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.\n",
"\n",
"For example, given the following spreadsheet:\n",
"\n",
" 5 9 2 8\n",
" 9 4 7 3\n",
" 3 8 6 5\n",
"\n",
"* In the first row, the only two numbers that evenly divide are 8 and 2; the result of this division is 4.\n",
"* In the second row, the two numbers are 9 and 3; the result is 3.\n",
"* In the third row, the result is 2.\n",
"\n",
"In this example, the sum of the results would be 4 + 3 + 2 = 9.\n",
"\n",
"What is the sum of each row's result in your puzzle input?"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
}
],
"source": [
"clear"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[2 5 8 9]"
]
}
],
"source": [
"[5 9 2 8] sort"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[5 8 9] [2 mod not]"
]
}
],
"source": [
"uncons swap [mod not] cons"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[false true false]"
]
}
],
"source": [
"map"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"false true false"
]
}
],
"source": [
"step"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"false true false"
]
}
],
"source": [
"[P 2 mod not] inscribe"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[4 5 6 7]"
]
}
],
"source": [
"clear\n",
"[4 5 6 7]"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[true false true false]"
]
}
],
"source": [
"[P] map"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[] [4 5 6 7]"
]
}
],
"source": [
"[] swap"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[] 4"
]
}
],
"source": [
"first"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[4]"
]
}
],
"source": [
"[P][swons][pop]ifte"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[4] 5"
]
}
],
"source": [
"5"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[4]"
]
}
],
"source": [
"[P][swons][pop]ifte"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[4 5 6 7]"
]
}
],
"source": [
"clear\n",
"[4 5 6 7]"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[6 4]"
]
}
],
"source": [
"[] swap [[P][swons][pop]ifte] step"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" [...] [P] filter\n",
" -----------------------------------------\n",
" [] [...] [[P][swons][pop]ifte] step\n",
"\n",
"But that `[]` could get in the way of `P`, no?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[8 5 2] [9 divmod] [8 5 2]"
]
}
],
"source": [
"uncons [swap [divmod] cons] dupdip"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
" [9 8 5 2] uncons [swap [divmod] cons F] dupdip G\n",
" [8 5 2] [9 divmod] F [8 5 2] G\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" • [8 5 2] [9 divmod] [uncons swap] dip dup [i not] dip\n",
" [8 5 2] • [9 divmod] [uncons swap] dip dup [i not] dip\n",
" [8 5 2] [9 divmod] • [uncons swap] dip dup [i not] dip\n",
" [8 5 2] [9 divmod] [uncons swap] • dip dup [i not] dip\n",
" [8 5 2] • uncons swap [9 divmod] dup [i not] dip\n",
" 8 [5 2] • swap [9 divmod] dup [i not] dip\n",
" [5 2] 8 • [9 divmod] dup [i not] dip\n",
" [5 2] 8 [9 divmod] • dup [i not] dip\n",
" [5 2] 8 [9 divmod] [9 divmod] • [i not] dip\n",
"[5 2] 8 [9 divmod] [9 divmod] [i not] • dip\n",
" [5 2] 8 [9 divmod] • i not [9 divmod]\n",
" [5 2] 8 • 9 divmod not [9 divmod]\n",
" [5 2] 8 9 • divmod not [9 divmod]\n",
" [5 2] 1 1 • not [9 divmod]\n",
" [5 2] 1 False • [9 divmod]\n",
" [5 2] 1 False [9 divmod] • \n"
]
}
],
"source": [
"V('[8 5 2] [9 divmod] [uncons swap] dip dup [i not] dip')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tricky\n",
"\n",
"Let's think.\n",
"\n",
"Given a *sorted* sequence (from highest to lowest) we want to \n",
"* for head, tail in sequence\n",
" * for term in tail:\n",
" * check if the head % term == 0\n",
" * if so compute head / term and terminate loop\n",
" * else continue"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### So we want a `loop` I think\n",
"\n",
" [a b c d] True [Q] loop\n",
" [a b c d] Q [Q] loop\n",
"\n",
"`Q` should either leave the result and False, or the `rest` and True.\n",
"\n",
" [a b c d] Q\n",
" -----------------\n",
" result 0\n",
"\n",
" [a b c d] Q\n",
" -----------------\n",
" [b c d] 1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This suggests that `Q` should start with:\n",
"\n",
" [a b c d] uncons dup roll<\n",
" [b c d] [b c d] a\n",
"\n",
"Now we just have to `pop` it if we don't need it.\n",
"\n",
" [b c d] [b c d] a [P] [T] [cons] app2 popdd [E] primrec\n",
" [b c d] [b c d] [a P] [a T] [E] primrec\n",
"\n",
"-------------------\n",
"\n",
" w/ Q == [% not] [T] [F] primrec\n",
"\n",
" [a b c d] uncons\n",
" a [b c d] tuck\n",
" [b c d] a [b c d] uncons\n",
" [b c d] a b [c d] roll>\n",
" [b c d] [c d] a b Q\n",
" [b c d] [c d] a b [% not] [T] [F] primrec\n",
"\n",
" [b c d] [c d] a b T\n",
" [b c d] [c d] a b / roll> popop 0\n",
"\n",
" [b c d] [c d] a b F Q\n",
" [b c d] [c d] a b pop swap uncons ... Q\n",
" [b c d] [c d] a swap uncons ... Q\n",
" [b c d] a [c d] uncons ... Q\n",
" [b c d] a c [d] roll> Q\n",
" [b c d] [d] a c Q\n",
"\n",
" Q == [% not] [/ roll> popop 0] [pop swap uncons roll>] primrec\n",
" \n",
" uncons tuck uncons roll> Q"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[8 5 3 2] [9 swap] [9 % not]\n"
]
}
],
"source": [
"J('[8 5 3 2] 9 [swap] [% not] [cons] app2 popdd')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"-------------------\n",
"\n",
" [a b c d] uncons\n",
" a [b c d] tuck\n",
" [b c d] a [b c d] [not] [popop 1] [Q] ifte\n",
"\n",
" [b c d] a [] popop 1\n",
" [b c d] 1\n",
"\n",
" [b c d] a [b c d] Q \n",
"\n",
"\n",
" a [...] Q\n",
" ---------------\n",
" result 0\n",
"\n",
" a [...] Q\n",
" ---------------\n",
" 1\n",
"\n",
"\n",
" w/ Q == [first % not] [first / 0] [rest [not] [popop 1]] [ifte]\n",
"\n",
"\n",
"\n",
" a [b c d] [first % not] [first / 0] [rest [not] [popop 1]] [ifte]\n",
" a [b c d] first % not\n",
" a b % not\n",
" a%b not\n",
" bool(a%b)\n",
"\n",
" a [b c d] [first % not] [first / 0] [rest [not] [popop 1]] [ifte]\n",
" a [b c d] first / 0\n",
" a b / 0\n",
" a/b 0\n",
"\n",
" a [b c d] [first % not] [first / 0] [rest [not] [popop 1]] [ifte]\n",
" a [b c d] rest [not] [popop 1] [Q] ifte\n",
" a [c d] [not] [popop 1] [Q] ifte\n",
" a [c d] [not] [popop 1] [Q] ifte\n",
"\n",
" a [c d] [not] [popop 1] [Q] ifte\n",
" a [c d] not\n",
"\n",
" a [] popop 1\n",
" 1\n",
"\n",
" a [c d] Q\n",
"\n",
"\n",
" uncons tuck [first % not] [first / 0] [rest [not] [popop 1]] [ifte]\n",
" \n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### I finally sat down with a piece of paper and blocked it out.\n",
"\n",
"First, I made a function `G` that expects a number and a sequence of candidates and return the result or zero:\n",
"\n",
" n [...] G\n",
" ---------------\n",
" result\n",
"\n",
" n [...] G\n",
" ---------------\n",
" 0\n",
"\n",
"It's a recursive function that conditionally executes the recursive part of its recursive branch\n",
"\n",
" [Pg] [E] [R1 [Pi] [T]] [ifte] genrec\n",
"\n",
"The recursive branch is the else-part of the inner `ifte`:\n",
"\n",
" G == [Pg] [E] [R1 [Pi] [T]] [ifte] genrec\n",
" == [Pg] [E] [R1 [Pi] [T] [G] ifte] ifte\n",
"\n",
"But this is in hindsight. Going forward I derived:\n",
"\n",
" G == [first % not]\n",
" [first /]\n",
" [rest [not] [popop 0]]\n",
" [ifte] genrec\n",
"\n",
"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",
"\n",
" n [m ...] rest [not] [popop 0] [G] ifte\n",
" n [...] [not] [popop 0] [G] ifte\n",
"\n",
"This `ifte` guards against empty sequences and returns zero in that case, otherwise it executes `G`."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"define('G [first % not] [first /] [rest [not] [popop 0]] [ifte] genrec')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"\n",
" n [...] p find-result\n",
" ---------------------------\n",
" result\n",
"\n",
"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`.)\n",
"\n",
" find-result == [0 >] [roll> popop] [roll< popop uncons [G] nullary] tailrec\n",
"\n",
" n [...] p [0 >] [roll> popop] [roll< popop uncons [G] nullary] tailrec\n",
"\n",
"The base-case is trivial, return the (non-zero) result. The recursive branch...\n",
"\n",
" n [...] p roll< popop uncons [G] nullary find-result\n",
" [...] p n popop uncons [G] nullary find-result\n",
" [...] uncons [G] nullary find-result\n",
" m [..] [G] nullary find-result\n",
" m [..] p find-result\n",
"\n",
"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`."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"define('find-result [0 >] [roll> popop] [roll< popop uncons [G] nullary] tailrec')"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3.0\n"
]
}
],
"source": [
"J('[11 9 8 7 3 2] 0 tuck find-result')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"define('prep-row sort reverse 0 tuck')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can define our program."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"define('AoC20017.2.extra [prep-row find-result +] step_zero')"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.0\n"
]
}
],
"source": [
"J('''\n",
"\n",
"[[5 9 2 8]\n",
" [9 4 7 3]\n",
" [3 8 6 5]] AoC20017.2.extra\n",
"\n",
"''')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Joypy",
"language": "",
"name": "thun"
},
"language_info": {
"file_extension": ".joy",
"mimetype": "text/plain",
"name": "Joy"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,360 @@
# 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
@@ -0,0 +1,431 @@
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,846 @@
# 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
@@ -0,0 +1,976 @@
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
Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

File diff suppressed because it is too large Load Diff
@@ -0,0 +1,145 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Advent of Code 2017\n",
"\n",
"## December 4th\n",
"To ensure security, a valid passphrase must contain no duplicate words.\n",
"\n",
"For example:\n",
"\n",
"* aa bb cc dd ee is valid.\n",
"* aa bb cc dd aa is not valid - the word aa appears more than once.\n",
"* aa bb cc dd aaa is valid - aa and aaa count as different words.\n",
"\n",
"The system's full passphrase list is available as your puzzle input. How many passphrases are valid?"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from notebook_preamble import J, V, define"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I'll assume the input is a Joy sequence of sequences of integers.\n",
"\n",
" [[5 1 9 5]\n",
" [7 5 4 3]\n",
" [2 4 6 8]]\n",
"\n",
"So, obviously, the initial form will be a `step` function:\n",
"\n",
" AoC2017.4 == 0 swap [F +] step"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
" F == [size] [unique size] cleave =\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `step_zero` combinator includes the `0 swap` that would normally open one of these definitions:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"==== Help on step_zero ====\n",
"\n",
"0 roll> step\n",
"\n",
"---- end (step_zero)\n",
"\n",
"\n"
]
}
],
"source": [
"J('[step_zero] help')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" AoC2017.4 == [F +] step_zero"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"define('AoC2017.4 [[size] [unique size] cleave = +] step_zero')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2\n"
]
}
],
"source": [
"J('''\n",
"\n",
"[[5 1 9 5]\n",
" [7 5 4 3]\n",
" [2 4 6 8]] AoC2017.4\n",
"\n",
"''')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,69 @@
# 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
@@ -0,0 +1,82 @@
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
@@ -0,0 +1,401 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Advent of Code 2017\n",
"\n",
"## December 5th\n",
"...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.\n",
"\n",
"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.\n",
"\n",
"For example, consider the following list of jump offsets:\n",
"\n",
" 0\n",
" 3\n",
" 0\n",
" 1\n",
" -3\n",
"\n",
"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:\n",
"\n",
"* (0) 3 0 1 -3 - before we have taken any steps.\n",
"* (1) 3 0 1 -3 - jump with offset 0 (that is, don't jump at all). Fortunately, the instruction is then incremented to 1.\n",
"* 2 (3) 0 1 -3 - step forward because of the instruction we just modified. The first instruction is incremented again, now to 2.\n",
"* 2 4 0 1 (-3) - jump all the way to the end; leave a 4 behind.\n",
"* 2 (4) 0 1 -2 - go back to where we just were; increment -3 to -2.\n",
"* 2 5 0 1 -2 - jump 4 steps forward, escaping the maze.\n",
"\n",
"In this example, the exit is reached in 5 steps.\n",
"\n",
"How many steps does it take to reach the exit?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Breakdown\n",
"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.\n",
"\n",
" size index step-count [...] F\n",
" -----------------------------------\n",
" step-count\n",
"\n",
" F == [P] [T] [R1] [R2] genrec\n",
"\n",
"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!\n",
"\n",
" index step-count [...] F\n",
" ------------------------------\n",
" step-count"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So, let's start by nailing down the predicate:\n",
"\n",
" F == [P] [T] [R1] [R2] genrec\n",
" == [P] [T] [R1 [F] R2] ifte\n",
"\n",
" 0 0 [0 3 0 1 -3] popop 5 >=\n",
"\n",
" P == popop 5 >="
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we need the else-part:\n",
"\n",
" index step-count [0 3 0 1 -3] roll< popop\n",
"\n",
" E == roll< popop"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Last but not least, the recursive branch\n",
"\n",
" 0 0 [0 3 0 1 -3] R1 [F] R2\n",
"\n",
"The `R1` function has a big job:\n",
"\n",
" R1 == get the value at index\n",
" increment the value at the index\n",
" add the value gotten to the index\n",
" increment the step count\n",
"\n",
"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\".)\n",
"\n",
"In the meantime, I'm going to write a primitive function that just does what we need."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from notebook_preamble import D, J, V, define\n",
"from joy.library import SimpleFunctionWrapper\n",
"from joy.utils.stack import list_to_stack\n",
"\n",
"\n",
"@SimpleFunctionWrapper\n",
"def incr_at(stack):\n",
" '''Given a index and a sequence of integers, increment the integer at the index.\n",
"\n",
" E.g.:\n",
"\n",
" 3 [0 1 2 3 4 5] incr_at\n",
" -----------------------------\n",
" [0 1 2 4 4 5]\n",
" \n",
" '''\n",
" sequence, (i, stack) = stack\n",
" mem = []\n",
" while i >= 0:\n",
" term, sequence = sequence\n",
" mem.append(term)\n",
" i -= 1\n",
" mem[-1] += 1\n",
" return list_to_stack(mem, sequence), stack\n",
"\n",
"\n",
"D['incr_at'] = incr_at"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 1 2 4 4 5]\n"
]
}
],
"source": [
"J('3 [0 1 2 3 4 5] incr_at')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### get the value at index\n",
"\n",
" 3 0 [0 1 2 3 4] [roll< at] nullary\n",
" 3 0 [0 1 2 n 4] n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### increment the value at the index\n",
"\n",
" 3 0 [0 1 2 n 4] n [Q] dip\n",
" 3 0 [0 1 2 n 4] Q n\n",
" 3 0 [0 1 2 n 4] [popd incr_at] unary n\n",
" 3 0 [0 1 2 n+1 4] n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### add the value gotten to the index\n",
"\n",
" 3 0 [0 1 2 n+1 4] n [+] cons dipd\n",
" 3 0 [0 1 2 n+1 4] [n +] dipd\n",
" 3 n + 0 [0 1 2 n+1 4]\n",
" 3+n 0 [0 1 2 n+1 4]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### increment the step count\n",
"\n",
" 3+n 0 [0 1 2 n+1 4] [++] dip\n",
" 3+n 1 [0 1 2 n+1 4]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### All together now...\n",
"\n",
" get_value == [roll< at] nullary\n",
" incr_value == [[popd incr_at] unary] dip\n",
" add_value == [+] cons dipd\n",
" incr_step_count == [++] dip\n",
"\n",
" R1 == get_value incr_value add_value incr_step_count\n",
"\n",
" F == [P] [T] [R1] primrec\n",
" \n",
" F == [popop !size! >=] [roll< pop] [get_value incr_value add_value incr_step_count] tailrec"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from joy.library import DefinitionWrapper\n",
"\n",
"\n",
"DefinitionWrapper.add_definitions('''\n",
"\n",
" get_value [roll< at] nullary\n",
" incr_value [[popd incr_at] unary] dip\n",
" add_value [+] cons dipd\n",
"incr_step_count [++] dip\n",
"\n",
" AoC2017.5.0 get_value incr_value add_value incr_step_count\n",
"\n",
"''', D)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"from joy.library import DefinitionWrapper\n",
"\n",
"\n",
"DefinitionWrapper.add_definitions('''\n",
"\n",
" get_value [roll< at] nullary\n",
" incr_value [[popd incr_at] unary] dip\n",
" add_value [+] cons dipd\n",
"incr_step_count [++] dip\n",
"\n",
" AoC2017.5.0 get_value incr_value add_value incr_step_count\n",
"\n",
"''', D)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"define('F [popop 5 >=] [roll< popop] [AoC2017.5.0] tailrec')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5\n"
]
}
],
"source": [
"J('0 0 [0 3 0 1 -3] F')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Preamble for setting up predicate, `index`, and `step-count`\n",
"\n",
"We want to go from this to this:\n",
"\n",
" [...] AoC2017.5.preamble\n",
" ------------------------------\n",
" 0 0 [...] [popop n >=]\n",
"\n",
"Where `n` is the size of the sequence.\n",
"\n",
"The first part is obviously `0 0 roll<`, then `dup size`:\n",
"\n",
" [...] 0 0 roll< dup size\n",
" 0 0 [...] n\n",
"\n",
"Then:\n",
"\n",
" 0 0 [...] n [>=] cons [popop] swoncat\n",
"\n",
"So:\n",
"\n",
" init-index-and-step-count == 0 0 roll<\n",
" prepare-predicate == dup size [>=] cons [popop] swoncat\n",
"\n",
" AoC2017.5.preamble == init-index-and-step-count prepare-predicate"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"DefinitionWrapper.add_definitions('''\n",
"\n",
"init-index-and-step-count 0 0 roll<\n",
"prepare-predicate dup size [>=] cons [popop] swoncat\n",
"\n",
"AoC2017.5.preamble init-index-and-step-count prepare-predicate\n",
"\n",
"AoC2017.5 AoC2017.5.preamble [roll< popop] [AoC2017.5.0] tailrec\n",
"\n",
"''', D)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5\n"
]
}
],
"source": [
"J('[0 3 0 1 -3] AoC2017.5')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
" AoC2017.5 == AoC2017.5.preamble [roll< popop] [AoC2017.5.0] primrec\n",
"\n",
" AoC2017.5.0 == get_value incr_value add_value incr_step_count\n",
" AoC2017.5.preamble == init-index-and-step-count prepare-predicate\n",
"\n",
" get_value == [roll< at] nullary\n",
" incr_value == [[popd incr_at] unary] dip\n",
" add_value == [+] cons dipd\n",
" incr_step_count == [++] dip\n",
"\n",
" init-index-and-step-count == 0 0 roll<\n",
" prepare-predicate == dup size [>=] cons [popop] swoncat\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,260 @@
# 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.
@@ -0,0 +1,339 @@
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
@@ -0,0 +1,457 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Advent of Code 2017\n",
"\n",
"## December 6th\n",
"\n",
"\n",
" [0 2 7 0] dup max\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from notebook_preamble import D, J, V, define"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 2 7 0] 7\n"
]
}
],
"source": [
"J('[0 2 7 0] dup max')"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from joy.library import SimpleFunctionWrapper\n",
"from joy.utils.stack import list_to_stack\n",
"\n",
"\n",
"@SimpleFunctionWrapper\n",
"def index_of(stack):\n",
" '''Given a sequence and a item, return the index of the item, or -1 if not found.\n",
"\n",
" E.g.:\n",
"\n",
" [a b c] a index_of\n",
" ------------------------\n",
" 0\n",
"\n",
" [a b c] d index_of\n",
" ------------------------\n",
" -1\n",
"\n",
" '''\n",
" item, (sequence, stack) = stack\n",
" i = 0\n",
" while sequence:\n",
" term, sequence = sequence\n",
" if term == item:\n",
" break\n",
" i += 1\n",
" else:\n",
" i = -1\n",
" return i, stack\n",
"\n",
"\n",
"D['index_of'] = index_of"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2\n"
]
}
],
"source": [
"J('[0 2 7 0] 7 index_of')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"-1\n"
]
}
],
"source": [
"J('[0 2 7 0] 23 index_of')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Starting at `index` distribute `count` \"blocks\" to the \"banks\" in the sequence.\n",
"\n",
" [...] count index distribute\n",
" ----------------------------\n",
" [...]\n",
"\n",
"This seems like it would be a PITA to implement in Joypy..."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"from joy.utils.stack import iter_stack, list_to_stack\n",
"\n",
"\n",
"@SimpleFunctionWrapper\n",
"def distribute(stack):\n",
" '''Starting at index+1 distribute count \"blocks\" to the \"banks\" in the sequence.\n",
"\n",
" [...] count index distribute\n",
" ----------------------------\n",
" [...]\n",
"\n",
" '''\n",
" index, (count, (sequence, stack)) = stack\n",
" assert count >= 0\n",
" cheat = list(iter_stack(sequence))\n",
" n = len(cheat)\n",
" assert index < n\n",
" cheat[index] = 0\n",
" while count:\n",
" index += 1\n",
" index %= n\n",
" cheat[index] += 1\n",
" count -= 1\n",
" return list_to_stack(cheat), stack\n",
"\n",
"\n",
"D['distribute'] = distribute"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[2 4 1 2]\n"
]
}
],
"source": [
"J('[0 2 7 0] dup max [index_of] nullary distribute')"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[3 1 2 3]\n"
]
}
],
"source": [
"J('[2 4 1 2] dup max [index_of] nullary distribute')"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 2 3 4]\n"
]
}
],
"source": [
"J('[3 1 2 3] dup max [index_of] nullary distribute')"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1 3 4 1]\n"
]
}
],
"source": [
"J('[0 2 3 4] dup max [index_of] nullary distribute')"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[2 4 1 2]\n"
]
}
],
"source": [
"J('[1 3 4 1] dup max [index_of] nullary distribute')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Recalling \"Generator Programs\"\n",
"\n",
" [a F] x\n",
" [a F] a F \n",
" \n",
" [a F] a swap [C] dip rest cons\n",
" a [a F] [C] dip rest cons\n",
" a C [a F] rest cons\n",
" a C [F] cons\n",
"\n",
" w/ C == dup G\n",
"\n",
" a dup G [F] cons\n",
" a a G [F] cons\n",
"\n",
" w/ G == dup max [index_of] nullary distribute"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"define('direco dip rest cons')"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"define('G [direco] cons [swap] swoncat cons')"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"define('make_distributor [dup dup max [index_of] nullary distribute] G')"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 2 7 0] [2 4 1 2] [3 1 2 3] [0 2 3 4] [1 3 4 1] [2 4 1 2]\n"
]
}
],
"source": [
"J('[0 2 7 0] make_distributor 6 [x] times pop')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### A function to drive a generator and count how many states before a repeat.\n",
"First draft:\n",
"\n",
" [] [GEN] x [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec\n",
"\n",
"(?)\n",
"\n",
" [] [GEN] x [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec\n",
" [] [...] [GEN] [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec\n",
" [] [...] [GEN] pop index_of 0 >=\n",
" [] [...] index_of 0 >=\n",
" -1 0 >=\n",
" False\n",
"\n",
"Base case\n",
"\n",
" [] [...] [GEN] [pop index_of 0 >=] [pop size --] [[swons] dip x] tailrec\n",
" [] [...] [GEN] pop size --\n",
" [] [...] size --\n",
" [] [...] size --\n",
"\n",
"A mistake, `popop` and no need for `--`\n",
"\n",
" [] [...] [GEN] popop size\n",
" [] size\n",
" n\n",
"\n",
"Recursive case\n",
"\n",
" [] [...] [GEN] [pop index_of 0 >=] [popop size] [[swons] dip x] tailrec\n",
" [] [...] [GEN] [swons] dip x F\n",
" [] [...] swons [GEN] x F\n",
" [[...]] [GEN] x F\n",
" [[...]] [...] [GEN] F\n",
"\n",
" [[...]] [...] [GEN] F\n",
"\n",
"What have we learned?\n",
"\n",
" F == [pop index_of 0 >=] [popop size] [[swons] dip x] tailrec"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"define('count_states [] swap x [pop index_of 0 >=] [popop size] [[swons] dip x] tailrec')"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"define('AoC2017.6 make_distributor count_states')"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5\n"
]
}
],
"source": [
"J('[0 2 7 0] AoC2017.6')"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4\n"
]
}
],
"source": [
"J('[1 1 1] AoC2017.6')"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"15\n"
]
}
],
"source": [
"J('[8 0 0 0 0 0] AoC2017.6')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,266 @@
# 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
@@ -0,0 +1,304 @@
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
File diff suppressed because it is too large Load Diff
+629
View File
@@ -0,0 +1,629 @@
```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
```
+592
View File
@@ -0,0 +1,592 @@
.. 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 it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,817 @@
# ∂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."
![omg.svg](attachment: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.
```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)*
@@ -0,0 +1,946 @@
∂RE
===
Brzozowskis 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
~~~~~~~~~~~~~~~~~~~
Im 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 Im 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()``
~~~~~~~~~~~
Lets 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
Lets 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 youll 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 1s and not ending in 01 nor composed of all 1s.”
.. 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/>`__.) Youll 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.
Theres a happy path to ``g`` along 111:
::
a→c→e→g
After you reach ``g`` youre stuck there eating 1s until you see a 0,
which takes you to the ``i→j→i|i→j→h→i`` “trap”. You cant 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 1s or as many 0 in a
row and still be either still at ``h`` (for 1s) or move to ``i`` (for
0s). If you find yourself at ``i`` you can see as many 0s, 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 arrows 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`` isnt.
.. 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
File diff suppressed because it is too large Load Diff
+506
View File
@@ -0,0 +1,506 @@
# 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
+635
View File
@@ -0,0 +1,635 @@
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]
Lets 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')
Lets 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 youll 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
thats a little beyond the scope of this article. This solution does
extra work, but not much, and were 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. Its 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]
Lets 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 its 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
Heres 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
+309
View File
@@ -0,0 +1,309 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "61944c2e",
"metadata": {},
"source": [
"Using the Joypy (Thun) Jupyter kernal."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "f7bb85e5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"41"
]
}
],
"source": [
"23 18 +"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "cbc09c4a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"41 123"
]
}
],
"source": [
"123"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f310ec86",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5043"
]
}
],
"source": [
"*"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "8d86c75f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
}
],
"source": [
"clear"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ff9b5754",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"15"
]
}
],
"source": [
"45 30 gcd"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "e1027ca3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
}
],
"source": [
"clear"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "aef6f509",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"!= % & * *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 gcd2 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 trace truthy tuck unary uncons unique unit unquoted unstack unswons void warranty while words x xor zip •\n",
"\n"
]
}
],
"source": [
"words"
]
},
{
"cell_type": "markdown",
"id": "904ce05e",
"metadata": {},
"source": [
"``trace`` is only loaded in the ``pretty_printer.py`` module, so it's not automatically included in the dictionary in the kernel.\n",
"\n",
"Stdout is also not captured and returned to the notebook. (So ``words`` doesn't work, for example, and neither would ``trace`` if it was available, I imagine.)\n",
"\n",
"Also, exceptions (like ``trace`` not being found in the dictionary) lead to the kernal \"hanging\" in the sense that you just see the \"pending computation\" asterix in the notebook cell.\n",
"\n",
"This would seem to indicate that I should polish the Joy kernel, eh?"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "f491e33f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1 2 +]"
]
}
],
"source": [
"[1 2 +]"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "31d6ec54",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" • 1 2 +\n",
" 1 • 2 +\n",
"1 2 • +\n",
" 3 • \n",
"\n",
"3"
]
}
],
"source": [
"trace"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "f85a149a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
}
],
"source": [
"clear"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "2e13763d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[dup cons]"
]
}
],
"source": [
"[dup cons]"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "e4509e6a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" [dup cons] • x\n",
" [dup cons] • dup cons\n",
"[dup cons] [dup cons] • cons\n",
"[[dup cons] dup cons] • \n",
"\n",
"[[dup cons] dup cons]"
]
}
],
"source": [
"[x] trace"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "8170053c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[dup cons] dup cons] • i\n",
" • [dup cons] dup cons\n",
" [dup cons] • dup cons\n",
"[dup cons] [dup cons] • cons\n",
"[[dup cons] dup cons] • \n",
"\n",
"[[dup cons] dup cons]"
]
}
],
"source": [
"[i] trace"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "50c24687",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
}
],
"source": [
"clear"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21c86a84",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Joypy",
"language": "",
"name": "thun"
},
"language_info": {
"file_extension": ".joy",
"mimetype": "text/plain",
"name": "Joy"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
docs = $(wildcard *.ipynb)
docs_html = $(patsubst %.ipynb,%.html,$(docs))
docs_md = $(patsubst %.ipynb,%.md,$(docs))
docs_rst = $(patsubst %.ipynb,%.rst,$(docs))
.PHONY: clean sdist test docs
all: $(docs_html) $(docs_md) $(docs_rst)
clean:
$(RM) -v $(docs_html) $(docs_md) $(docs_rst)
$(docs_html): %.html : %.ipynb
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)
cp -v $? ./sphinx_docs/notebooks/
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+752
View File
@@ -0,0 +1,752 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Newton's method](https://en.wikipedia.org/wiki/Newton%27s_method)\n",
"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.\n",
"\n",
"Cf. [\"Why Functional Programming Matters\" by John Hughes](https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A Generator for Approximations\n",
"\n",
"To make a generator that generates successive approximations lets start by assuming an initial approximation and then derive the function that computes the next approximation:\n",
"\n",
" a F\n",
" ---------\n",
" a'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### A Function to Compute the Next Approximation\n",
"\n",
"This is the equation for computing the next approximate value of the square root:\n",
"\n",
"$a_{i+1} = \\frac{(a_i+\\frac{n}{a_i})}{2}$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Starting with $\\frac{(a_i+\\frac{n}{a_i})}{2}$ we can derive the Joy expression to compute it using abstract dummy variables to stand in for actual values. First undivide by two:\n",
"\n",
"$(a_i+\\frac{n}{a_i})$ `2 /`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then unadd terms:\n",
"\n",
"$a_i$ $\\frac{n}{a_i}$ `+ 2 /`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Undivide again:\n",
"\n",
"$a_i$ $n$ $a_i$ `/ + 2 /`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally deduplicate the $a_i$ term:\n",
"\n",
"$a_i$ $n$ `over / + 2 /`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's try out this function `over / + 2 /` on an example:\n",
"\n",
" F == over / + 2 /"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
}
],
"source": [
"[F over / + 2 /] inscribe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In order to use this function `F` we have to provide an initial estimate for the value of the square root, and we want to keep the input value `n` handy for iterations (we don't want the user to have to keep reentering it.)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 5 36 • F\n",
" 5 36 • over / + 2 /\n",
"5 36 5 • / + 2 /\n",
" 5 7 • + 2 /\n",
" 12 • 2 /\n",
" 12 2 • /\n",
" 6 • \n",
"\n",
"6"
]
}
],
"source": [
"clear\n",
"\n",
"5 36 [F] trace"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The initial estimate can be 2, and we can `cons` the input value onto a quote with `F`:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6"
]
}
],
"source": [
"[F1 2 swap [F] cons] inscribe"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 36 • F1\n",
" 36 • 2 swap [F] cons\n",
" 36 2 • swap [F] cons\n",
" 2 36 • [F] cons\n",
"2 36 [F] • cons\n",
"2 [36 F] • \n",
"\n",
"2 [36 F]"
]
}
],
"source": [
"clear\n",
"\n",
"36 [F1] trace"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 2 • 36 F\n",
" 2 36 • F\n",
" 2 36 • over / + 2 /\n",
"2 36 2 • / + 2 /\n",
" 2 18 • + 2 /\n",
" 20 • 2 /\n",
" 20 2 • /\n",
" 10 • \n",
"\n",
"10"
]
}
],
"source": [
"trace"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6"
]
}
],
"source": [
"36 F"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
}
],
"source": [
"clear"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[2 [36 F] codireco]"
]
}
],
"source": [
"36 F1 make_generator"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6"
]
}
],
"source": [
"x x x first"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6 12"
]
}
],
"source": [
"144 F1 make_generator x x x x first"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2 [36 F]"
]
}
],
"source": [
"clear\n",
"\n",
"2 [36 F]"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2 [36 F] false"
]
}
],
"source": [
"[first] [pop sqr] fork - abs 3 <"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"10"
]
}
],
"source": [
"pop i"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"10 [36 F] false"
]
}
],
"source": [
"[36 F] [first] [pop sqr] fork - abs 3 <"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6"
]
}
],
"source": [
"pop i"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6 [36 F] true"
]
}
],
"source": [
"[36 F] [first] [pop sqr] fork - abs 3 <"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2"
]
}
],
"source": [
"clear\n",
"\n",
"2"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6"
]
}
],
"source": [
"[] true [i [36 F] [first] [pop sqr] fork - abs 3 >] loop pop"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"12"
]
}
],
"source": [
"clear\n",
"\n",
"7 [] true [i [144 F] [first] [pop sqr] fork - abs 3 >] loop pop"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"120"
]
}
],
"source": [
"clear\n",
"\n",
"7 [] true [i [14400 F] [first] [pop sqr] fork - abs 3 >] loop pop"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"broken due to no float div\n",
"\n",
" clear\n",
"\n",
" 7 [] true [i [1000 F] [first] [pop sqr] fork - abs 10 >] loop pop"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Make it into a Generator\n",
"\n",
"Our generator would be created by:\n",
"\n",
" a [dup F] make_generator\n",
"\n",
"With n as part of the function F, but n is the input to the sqrt function were writing. If we let 1 be the initial approximation:\n",
"\n",
" 1 n 1 / + 2 /\n",
" 1 n/1 + 2 /\n",
" 1 n + 2 /\n",
" n+1 2 /\n",
" (n+1)/2\n",
"\n",
"The generator can be written as:\n",
"\n",
" 23 1 swap [over / + 2 /] cons [dup] swoncat make_generator\n",
" 1 23 [over / + 2 /] cons [dup] swoncat make_generator\n",
" 1 [23 over / + 2 /] [dup] swoncat make_generator\n",
" 1 [dup 23 over / + 2 /] make_generator"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"define('gsra 1 swap [over / + 2 /] cons [dup] swoncat make_generator')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"J('23 gsra')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's drive the generator a few time (with the `x` combinator) and square the approximation to see how well it works..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"J('23 gsra 6 [x popd] times first sqr')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Finding Consecutive Approximations within a Tolerance\n",
"\n",
"From [\"Why Functional Programming Matters\" by John Hughes](https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf):\n",
"\n",
"\n",
"> 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.\n",
"\n",
"(And note that by “list” he means a lazily-evaluated list.)\n",
"\n",
"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...\n",
"\n",
" a [b G] ε within\n",
" ---------------------- a b - abs ε <=\n",
" b\n",
"\n",
"\n",
" a [b G] ε within\n",
" ---------------------- a b - abs ε >\n",
" b [c G] ε within\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Predicate\n",
"\n",
" a [b G] ε [first - abs] dip <=\n",
" a [b G] first - abs ε <=\n",
" a b - abs ε <=\n",
" a-b abs ε <=\n",
" abs(a-b) ε <=\n",
" (abs(a-b)<=ε)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"define('_within_P [first - abs] dip <=')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Base-Case\n",
"\n",
" a [b G] ε roll< popop first\n",
" [b G] ε a popop first\n",
" [b G] first\n",
" b"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"define('_within_B roll< popop first')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Recur\n",
"\n",
" a [b G] ε R0 [within] R1\n",
"\n",
"1. Discard a.\n",
"2. Use `x` combinator to generate next term from `G`.\n",
"3. Run `within` with `i` (it is a \"tail-recursive\" function.)\n",
"\n",
"Pretty straightforward:\n",
"\n",
" a [b G] ε R0 [within] R1\n",
" a [b G] ε [popd x] dip [within] i\n",
" a [b G] popd x ε [within] i\n",
" [b G] x ε [within] i\n",
" b [c G] ε [within] i\n",
" b [c G] ε within\n",
"\n",
" b [c G] ε within"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"define('_within_R [popd x] dip')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Setting up\n",
"\n",
"The recursive function we have defined so far needs a slight preamble: `x` to prime the generator and the epsilon value to use:\n",
"\n",
" [a G] x ε ...\n",
" a [b G] ε ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"define('within x 0.000000001 [_within_P] [_within_B] [_within_R] tailrec')\n",
"define('sqrt gsra within')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Try it out..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"J('36 sqrt')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"J('23 sqrt')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Check it."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"4.795831523312719**2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from math import sqrt\n",
"\n",
"sqrt(23)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Joypy",
"language": "",
"name": "thun"
},
"language_info": {
"file_extension": ".joy",
"mimetype": "text/plain",
"name": "Joy"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+208
View File
@@ -0,0 +1,208 @@
# [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 lets 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 were 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
+257
View File
@@ -0,0 +1,257 @@
`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 lets 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 were 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
File diff suppressed because it is too large Load Diff
+241
View File
@@ -0,0 +1,241 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from notebook_preamble import J, V, define"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Quadratic formula](https://en.wikipedia.org/wiki/Quadratic_formula)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Cf. [jp-quadratic.html](http://www.kevinalbrecht.com/code/joy-mirror/jp-quadratic.html)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" -b ± sqrt(b^2 - 4 * a * c)\n",
" --------------------------------\n",
" 2 * a"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$\\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Write a straightforward program with variable names.\n",
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `-b`\n",
" b neg"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `sqrt(b^2 - 4 * a * c)`\n",
" b sqr 4 a c * * - sqrt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `/2a`\n",
" a 2 * /"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `±`\n",
"There is a function `pm` that accepts two values on the stack and replaces them with their sum and difference.\n",
"\n",
" pm == [+] [-] cleave popdd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Putting Them Together\n",
"\n",
" b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2\n",
"\n",
"We use `app2` to compute both roots by using a quoted program `[2a /]` built with `cons`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Derive a definition.\n",
"Working backwards we use `dip` and `dipd` to extract the code from the variables:\n",
"\n",
" b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2\n",
" b [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2\n",
" b a c [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2\n",
" b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2\n",
" b a c over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2\n",
"\n",
"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:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"define('quadratic == over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's try it out:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"-0.3819660112501051 -2.618033988749895\n"
]
}
],
"source": [
"J('3 1 1 quadratic')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" . -5 1 4 quadratic\n",
" -5 . 1 4 quadratic\n",
" -5 1 . 4 quadratic\n",
" -5 1 4 . quadratic\n",
" -5 1 4 . over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2\n",
" -5 1 4 1 . [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2\n",
"-5 1 4 1 [[[neg] dupdip sqr 4] dipd * * - sqrt pm] . dip 2 * [/] cons app2\n",
" -5 1 4 . [[neg] dupdip sqr 4] dipd * * - sqrt pm 1 2 * [/] cons app2\n",
" -5 1 4 [[neg] dupdip sqr 4] . dipd * * - sqrt pm 1 2 * [/] cons app2\n",
" -5 . [neg] dupdip sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" -5 [neg] . dupdip sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" -5 . neg -5 sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 . -5 sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 -5 . sqr 4 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 -5 . dup mul 4 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 -5 -5 . mul 4 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 25 . 4 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 25 4 . 1 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 25 4 1 . 4 * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 25 4 1 4 . * * - sqrt pm 1 2 * [/] cons app2\n",
" 5 25 4 4 . * - sqrt pm 1 2 * [/] cons app2\n",
" 5 25 16 . - sqrt pm 1 2 * [/] cons app2\n",
" 5 9 . sqrt pm 1 2 * [/] cons app2\n",
" 5 3.0 . pm 1 2 * [/] cons app2\n",
" 8.0 2.0 . 1 2 * [/] cons app2\n",
" 8.0 2.0 1 . 2 * [/] cons app2\n",
" 8.0 2.0 1 2 . * [/] cons app2\n",
" 8.0 2.0 2 . [/] cons app2\n",
" 8.0 2.0 2 [/] . cons app2\n",
" 8.0 2.0 [2 /] . app2\n",
" [8.0] [2 /] . infra first [2.0] [2 /] infra first\n",
" 8.0 . 2 / [] swaack first [2.0] [2 /] infra first\n",
" 8.0 2 . / [] swaack first [2.0] [2 /] infra first\n",
" 4.0 . [] swaack first [2.0] [2 /] infra first\n",
" 4.0 [] . swaack first [2.0] [2 /] infra first\n",
" [4.0] . first [2.0] [2 /] infra first\n",
" 4.0 . [2.0] [2 /] infra first\n",
" 4.0 [2.0] . [2 /] infra first\n",
" 4.0 [2.0] [2 /] . infra first\n",
" 2.0 . 2 / [4.0] swaack first\n",
" 2.0 2 . / [4.0] swaack first\n",
" 1.0 . [4.0] swaack first\n",
" 1.0 [4.0] . swaack first\n",
" 4.0 [1.0] . first\n",
" 4.0 1.0 . \n"
]
}
],
"source": [
"V('-5 1 4 quadratic')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

Some files were not shown because too many files have changed in this diff Show More