From 7d8e2ae6115c575dda28569daa0fcfab158742f7 Mon Sep 17 00:00:00 2001 From: sforman Date: Mon, 14 Aug 2023 09:18:04 -0700 Subject: [PATCH] Updating notebooks to not use Jupyter because I can't figure out how to use the Joy kernel. --- docs/html/notebooks/Developing_a_Program.html | 398 + docs/html/notebooks/Generator_Programs.html | 14681 +--------------- docs/source/notebooks/Developing_a_Program.md | 508 + docs/source/notebooks/Generator_Programs.md | 455 + 4 files changed, 1616 insertions(+), 14426 deletions(-) create mode 100644 docs/html/notebooks/Developing_a_Program.html create mode 100644 docs/source/notebooks/Developing_a_Program.md create mode 100644 docs/source/notebooks/Generator_Programs.md diff --git a/docs/html/notebooks/Developing_a_Program.html b/docs/html/notebooks/Developing_a_Program.html new file mode 100644 index 0000000..a6d62d5 --- /dev/null +++ b/docs/html/notebooks/Developing_a_Program.html @@ -0,0 +1,398 @@ + + + + +Developing a Joy Program + + + + +

Developing a Joy Program

+

As a first attempt at writing software in Joy let's tackle +Project Euler, first problem: "Multiples of 3 and 5":

+
+

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.

+
+

Filter

+

Let's create a predicate that returns true if a number is a multiple of 3 or 5 and false otherwise.

+

multiple-of

+

First we write multiple-of which take two numbers and return true if the first is a multiple of the second. We use the mod operator, convert the remainder to a Boolean value, then invert it to get our answer:

+
[multiple-of mod bool not] inscribe
+
+

multiple-of-3-or-5

+

Next we can use that with app2 to get both Boolean values (for 3 and 5) and then use the logical OR function \/ on them. (We use popd to get rid of the original number):

+
[multiple-of-3-or-5 3 5 [multiple-of] app2 \/ popd] inscribe
+
+

Here it is in action:

+
joy? [6 7 8 9 10] [multiple-of-3-or-5] map
+
+[true false false true true]
+
+

Given the predicate function multiple-of-3-or-5 a suitable program would be:

+
1000 range [multiple-of-3-or-5] filter sum
+
+

This function generates a list of the integers from 0 to 999, filters +that list by multiple-of-3-or-5, and then sums the result. (I should +mention that the filter function has not yet been implemented.)

+

Logically this is fine, but pragmatically we are doing more work than we +should. 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.

+

Looking for Pattern

+

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.

+

Increment a Counter

+

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:

+
+ [+] dupdip
+
+

We start with a sum, the counter, and a term to add:

+
joy? 0 0 3
+0 0 3
+
+

Here is our function, quoted to let it be run with the trace combinator +(which is like i but it also prints a trace of evaluation to stdout.)

+
joy? [+ [+] dupdip]
+0 0 3 [+ [+] dupdip]
+
+

And here we go:

+
joy? trace
+
+  0 0 3 • + [+] dupdip
+    0 3 • [+] dupdip
+0 3 [+] • dupdip
+    0 3 • + 3
+      3 • 3
+    3 3 •
+
+3 3
+
+

PE1.1

+

We can inscribe it for use in later definitions:

+
[PE1.1 + [+] dupdip] inscribe
+
+

Let's try it out on our palindromic sequence:

+
joy? 0 0 [3 2 1 3 1 2 3] [PE1.1] step
+60 15
+
+

So one step through all seven terms brings the counter to 15 and the total to 60.

+

How Many Times?

+

We want all the terms less than 1000, and each pass through the palindromic sequence +will count off 15 so how many is that?

+
joy? 1000 15 /
+66
+
+

So 66 × 15 bring us to...

+
66
+joy? 15 *
+990
+
+

That means we want to run the full list of numbers sixty-six times to get to 990 and then, obviously, the first four terms of the palindromic sequence, 3 2 1 3, to get to 999.

+

Start with the sum and counter:

+
joy? 0 0
+0 0
+
+

We will run a program sixty-six times:

+
joy? 66
+0 0 66
+
+

This is the program we will run sixty-six times, it steps through the +palindromic sequence and sums up the terms:

+
joy? [[3 2 1 3 1 2 3] [PE1.1] step]
+0 0 66 [[3 2 1 3 1 2 3] [PE1.1] step]
+
+

Runing that brings us to the sum of the numbers less than 991:

+
joy? times
+229185 990
+
+

We need to count 9 more to reach the sum of the numbers less than 1000:

+
joy? [3 2 1 3] [PE1.1] step
+233168 999
+
+

All that remains is the counter, which we can discard:

+
joy? pop
+233168
+
+

And so we have our answer: 233168

+

This form uses no extra storage and produces no unused summands. It's +good but there's one more trick we can apply.

+

A Slight Increase of Efficiency

+

The list of seven terms +takes up at least seven bytes for themselves and a few bytes for their list. +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.

+

Let's encode the term in 7 × 2 = 14 bits:

+
Decimal:    3  2  1  3  1  2  3
+Binary:    11 10 01 11 01 10 11
+
+

The number 11100111011011 in binary is 14811 in decimal notation.

+

Recovering the Terms

+

We can recover the terms from this number by 4 divmod:

+
joy? 14811
+14811
+
+joy? 4 divmod
+3702 3
+
+

We want the term below the rest of the terms:

+
joy? swap
+3 3702
+
+

PE1.2

+

Giving us 4 divmod swap:

+
[PE1.2 4 divmod swap] inscribe
+
+joy? 14811
+14811
+joy? PE1.2
+3 3702
+joy? PE1.2
+3 2 925
+joy? PE1.2
+3 2 1 231
+joy? PE1.2
+3 2 1 3 57
+joy? PE1.2
+3 2 1 3 1 14
+joy? PE1.2
+3 2 1 3 1 2 3
+joy? PE1.2
+3 2 1 3 1 2 3 0
+
+

So we want:

+
joy? 0 0 14811 PE1.2
+0 0 3 3702
+
+

And then:

+
joy? [PE1.1] dip
+3 3 3702
+
+

Continuing:

+
joy? PE1.2 [PE1.1] dip
+8 5 925
+
+joy? PE1.2 [PE1.1] dip
+14 6 231
+
+joy? PE1.2 [PE1.1] dip
+23 9 57
+
+joy? PE1.2 [PE1.1] dip
+33 10 14
+
+joy? PE1.2 [PE1.1] dip
+45 12 3
+
+joy? PE1.2 [PE1.1] dip
+60 15 0
+
+

PE1.3

+

Let's define:

+
[PE1.3 PE1.2 [PE1.1] dip] inscribe
+
+

Now:

+
14811 7 [PE1.3] times pop
+
+

Will add up one set of the palindromic sequence of terms:

+
joy? 0 0 
+0 0
+
+joy? 14811 7 [PE1.3] times pop
+60 15
+
+

And we want to do that sixty-six times:

+
joy? 0 0 
+0 0
+
+joy? 66 [14811 7 [PE1.3] times pop] times
+229185 990
+
+

And then four more:

+
joy? 14811 4 [PE1.3] times pop
+233168 999
+
+

And discard the counter:

+
joy? pop
+233168
+
+

Violà!

+

Let's refactor.

+

From these two:

+
14811 7 [PE1.3] times pop
+14811 4 [PE1.3] times pop
+
+

We can generalize the loop counter:

+
14811 n [PE1.3] times pop
+
+

And use swap to put it to the left...

+
n 14811 swap [PE1.3] times pop
+
+

PE1.4

+

...and so we have a new definition:

+
[PE1.4 14811 swap [PE1.3] times pop] inscribe
+
+

Now we can simplify the program from:

+
0 0 66 [14811 7 [PE1.3] times pop] times 14811 4 [PE1.3] times pop pop
+
+

To:

+
0 0 66 [7 PE1.4] times 4 PE1.4 pop
+
+

Let's run it and see:

+
joy? 0 0 66 [7 PE1.4] times 4 PE1.4 pop
+233168
+
+

PE1

+
[PE1 0 0 66 [7 PE1.4] times 4 PE1.4 pop] inscribe
+
+joy? PE1
+233168
+
+

Here's our joy program all in one place, as it might appear in def.txt:

+
PE1.1 + [+] dupdip
+PE1.2 4 divmod swap
+PE1.3 PE1.2 [PE1.1] dip
+PE1.4 14811 swap [PE1.3] times pop
+PE1   0 0 66 [7 PE1.4] times 4 PE1.4 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.

+

Here it is again:

+
[0 swap [? [pop 14811] [] branch PE1.2] dip rest cons]
+
+

This self-referential quote contains a bit of state (the initial 0) and a "step" +function (the rest of the quote) and can be "forced" by the x combinator to +produce successive terms of our palindromic sequence. The branch sub-expression +resets the integer that encodes the terms when it reaches 0.

+

Let's inscribe this generator quote to keep it handy.

+

PE1.terms

+
[PE1.terms [0 swap [? [pop 14811] [] branch PE1.2] dip rest cons]] inscribe
+
+

Let's try it out:

+
joy? PE1.terms 21 [x] times pop
+3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3
+
+

Pretty neat, eh?

+

How Many Terms?

+

We know from above that we need sixty-six times seven then four more terms to reach up to but not over one thousand.

+
joy? 7 66 * 4 +
+466
+
+

Here they are...

+
joy? 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.

+
joy? [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.

+
joy? 0 0 PE1.terms 466 [x [PE1.1] dip] times popop
+233168
+
+

A Little Further Analysis...

+

A little further analysis renders iteration unnecessary. +Consider finding the sum of the positive integers less than or equal to ten.

+
joy? [10 9 8 7 6 5 4 3 2 1] sum
+55
+
+

Gauss famously showed that you can find the sum directly with a simple equation.

+

Observe:

+
  10  9  8  7  6
++  1  2  3  4  5
+---- -- -- -- --
+  11 11 11 11 11
+
+  11 × 5 = 55
+
+

The sum of the first N positive integers is:

+
(𝑛 + 1) × 𝑛 / 2
+
+

In Joy this equation could be expressed as:

+
dup ++ * 2 /
+
+

(Note that (𝑛 + 1) × 𝑛 will always be an even number.)

+

Generalizing to Blocks of Terms

+

We can apply the same reasoning to the PE1 problem.

+

Recall that between 1 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...

+
joy? [3 5 6 9 10 12 15] reverse
+[15 12 10 9 6 5 3]
+
+joy? [978 980 981 984 985 987 990] 
+[15 12 10 9 6 5 3] [978 980 981 984 985 987 990]
+
+joy? zip
+[[978 15] [980 12] [981 10] [984 9] [985 6] [987 5] [990 3]]
+
+joy? [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.)

+

...and then sum the sums...

+
joy? sum
+6945
+
+

We arrive at 6945.

+

Pair Up the Blocks

+

Since there are sixty-six blocks and we are pairing them up, there must be thirty-three pairs, each of which sums to 6945.

+
6945
+joy? 33 *
+229185
+
+

We also have those four additional terms between 990 and 1000, they are unpaired:

+
993 995 996 999
+
+

A Simple Solution

+

I think we can just use those as is, so we can give the "sum of all the multiples of 3 or 5 below 1000" like so:

+
joy? 6945 33 * [993 995 996 999] cons sum
+233168
+
+

Generalizing

+

It's worth noting, I think, that this same reasoning holds for any two numbers 𝑛 and 𝑚 the multiples of which we hope to sum. The multiples would have a cycle of differences of length 𝑘 and so we could compute the sum of 𝑁𝑘 multiples as above.

+

The sequence of differences will always be a palindrome. Consider an interval spanning the least common multiple of 𝑛 and 𝑚:

+
|   |   |   |   |   |   |   |
+|      |      |      |      |
+
+

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 𝑛 and 𝑚 and their least common multiple 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, having done all that, the simplest joy program for the first Project Euler problem is just:

+
[PE1 233168] inscribe
+
+

Fin.

+ + diff --git a/docs/html/notebooks/Generator_Programs.html b/docs/html/notebooks/Generator_Programs.html index ed448b7..e343638 100644 --- a/docs/html/notebooks/Generator_Programs.html +++ b/docs/html/notebooks/Generator_Programs.html @@ -1,14207 +1,279 @@ - + - - - -Generator_Programs - - - - - - - - - - - - + + +Generator Programs + + + -
-
- -
-
-
-

Using x to Generate Values

Cf. jp-reprod.html

- -
-
-
-
-
-
In [1]:
-
-
-
from notebook_preamble import J, V, define
-
- -
-
-
- -
-
-
-
+

Generator Programs

+

Using x to Generate Values

+

Cf. Self-reproducing and reproducing programs by Manfred von Thun

Consider the x combinator:

- -
x == dup i
- -
-
-
-
-
-
+
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
- -
-
-
-
-
-
+[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]
- -
-
-
-
-
-
+b [a B] +

Now discard the quoted a with rest then cons b:

-
b [a B] rest cons
 b [B]        cons
-[b B]
- -
-
-
-
-
-
+[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]:

- +
B == swap [C] dip rest cons
+
+

An Example

+

We can make a generator for the Natural numbers (0, 1, 2, ...) by using +0 for the initial state a and [dup ++] for [C]. +We need the dup to leave the old state value behind on the stack. +Putting it together:

[0 swap [dup ++] dip rest cons]
-
 

Let's try it:

+
joy? [0 swap [dup ++] dip rest cons]
+[0 swap [dup ++] dip rest cons]
 
-
-
-
-
-
-
In [2]:
-
-
-
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.

- -
-
-
-
-
-
In [3]:
-
-
-
J('[0 swap [dup ++] dip rest cons] x x x x x pop')
-
- -
-
-
- -
-
- - -
- -
- - -
-
0 1 2 3 4
-
-
-
- -
-
- -
-
-
-
-

direco

-
-
-
-
-
-
In [4]:
-
-
-
define('direco == dip rest cons')
-
- -
-
-
- -
-
-
-
In [5]:
-
-
-
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:

+joy? [x] +[0 swap [dup ++] dip rest cons] [x] +joy? trace + [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 0 • 1 + [0 swap [dup ++] dip rest cons] rest cons + 0 0 1 • + [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.

+
0 [1 swap [dup ++] dip rest cons]
+
+

We can use x as many times as we like to get as many terms as we like:

+
joy? x x x x x pop
+0 1 2 3 4 5
+
+

direco

+

Let's define a helper function:

+
[direco dip rest cons] inscribe
+
+

That makes our generator quote into:

+
[0 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]
- -
-
-
-
-
-
+ [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
- -
-
-
-
-
-
In [6]:
-
-
-
define('G == [direco] cons [swap] swoncat cons')
-
- -
-
-
- -
-
-
-
+
[direco] cons [swap] swap concat cons
+
+

Or:

+
[direco] cons [swap] swoncat cons
+
+

make-generator

+
[make-generator [direco] cons [swap] swoncat cons] inscribe
+

Let's try it out:

- -
-
-
-
-
-
In [7]:
-
-
-
J('0 [dup ++] G')
-
- -
-
-
- -
-
- - -
- -
- - -
-
[0 swap [dup ++] direco]
-
-
-
- -
-
- -
-
-
-
In [8]:
-
-
-
J('0 [dup ++] G x x x pop')
-
- -
-
-
- -
-
- - -
- -
- - -
-
0 1 2
-
-
-
- -
-
- -
-
-
-
-

Powers of 2

-
-
-
-
-
-
In [9]:
-
-
-
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.

- -
-
-
-
-
-
In [10]:
-
-
-
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:

- +
joy? 0 [dup ++] make-generator
+[0 swap [dup ++] direco]
+
+

And generate some values:

+
joy? x x x pop
+0 1 2
+
+

Powers of Two

+

Let's generate powers of two:

+
joy? 1 [dup 1 <<] make-generator
+[1 swap [dup 1 <<] direco]
+
+

We can drive it using times with the x combinator.

+
joy? 10 [x] times pop
+1 2 4 8 16 32 64 128 256 512
+
+

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
-
+

To do this we want to encode the numbers as pairs of bits in a single integer:

+
Decimal:    3  2  1  3  1  2  3
+Binary:    11 10 01 11 01 10 11
 
-

And pick them off by masking with 3 (binary 11) and then shifting the int right two bits.

- -
-
-
-
-
-
In [11]:
-
-
-
define('PE1.1 == dup [3 &] dip 2 >>')
-
- -
-
-
- -
-
-
-
In [12]:
-
-
-
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...

- -
-
-
-
-
-
In [13]:
-
-
-
J('14811 [PE1.1] G')
-
- -
-
-
- -
-
- - -
- -
- - -
-
[14811 swap [PE1.1] direco]
-
-
-
- -
-
- -
-
-
-
-

...we get a generator that works for seven cycles before it reaches zero:

- -
-
-
-
-
-
In [14]:
-
-
-
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.

- -
-
-
-
-
-
In [15]:
-
-
-
define('PE1.1.check == dup [pop 14811] [] branch')
-
- -
-
-
- -
-
-
-
In [16]:
-
-
-
J('14811 [PE1.1.check PE1.1] G')
-
- -
-
-
- -
-
- - -
- -
- - -
-
[14811 swap [PE1.1.check PE1.1] direco]
-
-
-
- -
-
- -
-
-
-
In [17]:
-
-
-
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.

- -
-
-
-
-
-
In [18]:
-
-
-
J('7 66 * 4 +')
-
- -
-
-
- -
-
- - -
- -
- - -
-
466
-
-
-
- -
-
- -
-
-
-
-

If we drive our generator 466 times and sum the stack we get 999.

- -
-
-
-
-
-
In [19]:
-
-
-
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]
-
-
-
- -
-
- -
-
-
-
In [20]:
-
-
-
J('[14811 swap [PE1.1.check PE1.1] direco] 466 [x] times pop enstacken sum')
-
- -
-
-
- -
-
- - -
- -
- - -
-
999
-
-
-
- -
-
- -
-
-
-
-

Project Euler Problem One

-
-
-
-
-
-
In [21]:
-
-
-
define('PE1.2 == + dup [+] dip')
-
- -
-
-
- -
-
-
-
-

Now we can add PE1.2 to the quoted program given to G.

- -
-
-
-
-
-
In [22]:
-
-
-
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:

- +

The number 11100111011011 in binary is 14811 in decimal notation. +We can recover the terms from this number by using 4 divmod.

+
joy? 14811 [4 divmod swap] make-generator
+[14811 swap [4 divmod swap] direco]
+
+joy? x
+3 [3702 swap [4 divmod swap] direco]
+
+joy? x
+3 2 [925 swap [4 divmod swap] direco]
+
+joy? x
+3 2 1 [231 swap [4 divmod swap] direco]
+
+joy? x
+3 2 1 3 [57 swap [4 divmod swap] direco]
+
+joy? x
+3 2 1 3 1 [14 swap [4 divmod swap] direco]
+
+joy? x
+3 2 1 3 1 2 [3 swap [4 divmod swap] direco]
+
+joy? x
+3 2 1 3 1 2 3 [0 swap [4 divmod swap] direco]
+
+joy? x
+3 2 1 3 1 2 3 0 [0 swap [4 divmod swap] direco]
+
+joy? x
+3 2 1 3 1 2 3 0 0 [0 swap [4 divmod swap] direco]
+
+joy? x
+3 2 1 3 1 2 3 0 0 0 [0 swap [4 divmod swap] direco]
+
+

...we get a generator that works for seven cycles before it reaches zero.

+

Reset at Zero

+

We need a function that checks if the int has reached zero and resets it if so. +That's easy enough to write:

+
? [pop 14811] [] branch
+
+

I don't like that we're checking every time even though we know we +only need to reset the integer every seventh time, but this way we +can include this function in the generator (rather than wrapping the +generator in something to do it only every seventh iteration.) So +the "forcing" function is just x.

+

PE1.1.check

+
[PE1.1.check ? [pop 14811] [] branch] inscribe
+
+

PE1.1

+
[PE1.1 4 divmod swap] inscribe
+
+

Now we can make-generator:

+
joy? 14811 [PE1.1.check PE1.1] make-generator
+[14811 swap [PE1.1.check PE1.1] direco]
+
+

We can then "force" the generator with x to get as many terms as we like:

+
joy? 21 [x] times pop
+3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3
+
+

Run 466 times

+

In the PE1 problem 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 our cycle of seven numbers +sixty-six times and then four more.

+
joy? 7 66 * 4 +
+466
+
+

If we drive our generator 466 times and sum the stack we get 999:

+
joy? 14811 [PE1.1.check PE1.1] make-generator
+[14811 swap [PE1.1.check PE1.1] direco]
+
+joy? 466 [x] times pop enstacken sum
+999
+
+

If you want to see how this is used read the +Developing a Program notebook.

+

A generator for the Fibonacci Sequence.

+

Consider:

[b a F] x
-[b a F] b a F
- -
-
-
-
-
-
+[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
- -
-
-
-
-
-
+[b a F] b+a +

From here we want to arrive at:

- -
b [b+a b F]
- -
-
-
-
-
-
+
b [b+a b F]
+

Let's start with swons:

-
[b a F] b+a swons
-[b+a b a F]
- -
-
-
-
-
-
+[b+a b a F] +

Considering this quote as a stack:

- -
F a b b+a
- -
-
-
-
-
-
+
F a b b+a
+

We want to get it to:

- -
F b b+a b
- -
-
-
-
-
-
+
F b b+a b
+

So:

-
F a b b+a popdd over
-F b b+a b
- -
-
-
-
-
-
+F b b+a b +

And therefore:

-
[b+a b a F] [popdd over] infra
-[b b+a b F]
- -
-
-
-
-
-
+[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]
- -
-
-
-
-
-
+[b b+a b F] +

Lastly:

-
[b b+a b F] uncons
-b [b+a b F]
- -
-
-
-
-
-
+b [b+a b F] +

Putting it all together:

-
F == + [popdd over] cons infra uncons
-fib_gen == [1 1 F]
- -
-
-
-
-
-
In [23]:
-
-
-
define('fib == + [popdd over] cons infra uncons')
-
- -
-
-
- -
-
-
-
In [24]:
-
-
-
define('fib_gen == [1 1 fib]')
-
- -
-
-
- -
-
-
-
In [25]:
-
-
-
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.

+fib_gen == [1 1 F] + +

Let's call F fib_gen:

+
[fib_gen + [popdd over] cons infra uncons] inscribe
+
+

We can just write the initial quote and then "force" it with x:

+
joy? [1 1 fib_gen] 10 [x] times
+1 2 3 5 8 13 21 34 55 89 [144 89 fib_gen]
+
+

It skips the first term (1) but if that bothers you you can just prepend it to the program:

+
1 [1 1 fib_gen] 10 [x] times
+
+

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 pops it otherwise.

- -
-
-
-
-
-
In [26]:
-
-
-
define('PE2.1 == dup 2 % [+] [pop] branch')
-
- -
-
-
- -
-
-
-
+

python +define('PE2.1 == dup 2 % [+] [pop] branch')

And a predicate function that detects when the terms in the series "exceed four million".

- -
-
-
-
-
-
In [27]:
-
-
-
define('>4M == 4000000 >')
-
- -
-
-
- -
-
-
-
+

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.

- -
-
-
-
-
-
In [28]:
-
-
-
define('PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec')
-
- -
-
-
- -
-
-
-
In [29]:
-
-
-
J('PE2')
-
- -
-
-
- -
-
- - -
- -
- - -
-
4613732
-
-
-
- -
-
- -
-
-
-
+

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]
 
@@ -14209,236 +281,47 @@ 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:

- +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.

- -
-
-
-
-
-
In [30]:
-
-
-
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]
-
-
-
- -
-
- -
-
-
-
+

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.

- -
-
-
-
-
-
In [31]:
-
-
-
J('[1 0 fib] x x x [popop] dipd')
-
- -
-
-
- -
-
- - -
- -
- - -
-
2 [3 2 fib]
-
-
-
- -
-
- -
-
-
-
In [32]:
-
-
-
define('PE2.2 == x x x [popop] dipd')
-
- -
-
-
- -
-
-
-
In [33]:
-
-
-
J('[1 0 fib] 10 [PE2.2] times')
-
- -
-
-
- -
-
- - -
- -
- - -
-
2 8 34 144 610 2584 10946 46368 196418 832040 [1346269 832040 fib]
-
-
-
- -
-
- -
-
-
-
+

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.

- -
-
-
-
-
-
In [34]:
-
-
-
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

-
-
-
-
-
-
In [35]:
-
-
-
define('codireco == cons dip rest cons')
-
- -
-
-
- -
-
-
-
In [36]:
-
-
-
V('[0 [dup ++] codireco] x')
-
- -
-
-
- -
-
- - -
- -
- - -
-
                                 . [0 [dup ++] codireco] x
+

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
@@ -14451,67 +334,13 @@ o + e = o
                              0 1 . [0 [dup ++] codireco] rest cons
        0 1 [0 [dup ++] codireco] . rest cons
          0 1 [[dup ++] codireco] . cons
-         0 [1 [dup ++] codireco] . 
-
-
-
- -
-
- -
-
-
-
In [37]:
-
-
-
define('G == [codireco] cons cons')
-
- -
-
-
- -
-
-
-
In [38]:
-
-
-
J('230 [dup ++] G 5 [x] times pop')
-
- -
-
-
- -
-
- - -
- -
- - -
-
230 231 232 233 234
-
-
-
- -
-
- -
-
-
+ 0 [1 [dup ++] codireco] . + +

python +define('G == [codireco] cons cons')

+

python +J('230 [dup ++] G 5 [x] times pop')

+
230 231 232 233 234
+
- - - - - - - diff --git a/docs/source/notebooks/Developing_a_Program.md b/docs/source/notebooks/Developing_a_Program.md new file mode 100644 index 0000000..e3a6d90 --- /dev/null +++ b/docs/source/notebooks/Developing_a_Program.md @@ -0,0 +1,508 @@ +# Developing a Joy Program + +As a first attempt at writing software in Joy let's tackle +[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. + +## Filter + +Let's create a predicate that returns `true` if a number is a multiple of 3 or 5 and `false` otherwise. + +### `multiple-of` + +First we write `multiple-of` which take two numbers and return true if the first is a multiple of the second. We use the `mod` operator, convert the remainder to a Boolean value, then invert it to get our answer: + + [multiple-of mod bool not] inscribe + +### `multiple-of-3-or-5` + +Next we can use that with `app2` to get both Boolean values (for 3 and 5) and then use the logical OR function `\/` on them. (We use `popd` to get rid of the original number): + + [multiple-of-3-or-5 3 5 [multiple-of] app2 \/ popd] inscribe + +Here it is in action: + + joy? [6 7 8 9 10] [multiple-of-3-or-5] map + + [true false false true true] + +Given the predicate function `multiple-of-3-or-5` a suitable program would be: + + 1000 range [multiple-of-3-or-5] filter sum + +This function generates a list of the integers from 0 to 999, filters +that list by `multiple-of-3-or-5`, and then sums the result. (I should +mention that the `filter` function has not yet been implemented.) + +Logically this is fine, but pragmatically we are doing more work than we +should. 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. + + +## Looking for Pattern + +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. + +### Increment a Counter + +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: + + + [+] dupdip + +We start with a sum, the counter, and a term to add: + + joy? 0 0 3 + 0 0 3 + +Here is our function, quoted to let it be run with the `trace` combinator +(which is like `i` but it also prints a trace of evaluation to `stdout`.) + + joy? [+ [+] dupdip] + 0 0 3 [+ [+] dupdip] + +And here we go: + + joy? trace + + 0 0 3 • + [+] dupdip + 0 3 • [+] dupdip + 0 3 [+] • dupdip + 0 3 • + 3 + 3 • 3 + 3 3 • + + 3 3 + + +### `PE1.1` + +We can `inscribe` it for use in later definitions: + + [PE1.1 + [+] dupdip] inscribe + +Let's try it out on our palindromic sequence: + + joy? 0 0 [3 2 1 3 1 2 3] [PE1.1] step + 60 15 + +So one `step` through all seven terms brings the counter to 15 and the total to 60. + + +### How Many Times? + +We want all the terms less than 1000, and each pass through the palindromic sequence +will count off 15 so how many is that? + + joy? 1000 15 / + 66 + +So 66 × 15 bring us to... + + 66 + joy? 15 * + 990 + +That means we want to run the full list of numbers sixty-six times to get to 990 and then, obviously, the first four terms of the palindromic sequence, 3 2 1 3, to get to 999. + +Start with the sum and counter: + + joy? 0 0 + 0 0 + +We will run a program sixty-six times: + + joy? 66 + 0 0 66 + +This is the program we will run sixty-six times, it steps through the +palindromic sequence and sums up the terms: + + joy? [[3 2 1 3 1 2 3] [PE1.1] step] + 0 0 66 [[3 2 1 3 1 2 3] [PE1.1] step] + +Runing that brings us to the sum of the numbers less than 991: + + joy? times + 229185 990 + +We need to count 9 more to reach the sum of the numbers less than 1000: + + joy? [3 2 1 3] [PE1.1] step + 233168 999 + +All that remains is the counter, which we can discard: + + joy? pop + 233168 + +And so we have our answer: **233168** + +This form uses no extra storage and produces no unused summands. It's +good but there's one more trick we can apply. + + +## A Slight Increase of Efficiency + +The list of seven terms +takes up at least seven bytes for themselves and a few bytes for their list. +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. + +Let's encode the term in 7 × 2 = 14 bits: + + Decimal: 3 2 1 3 1 2 3 + Binary: 11 10 01 11 01 10 11 + +The number 11100111011011 in binary is 14811 in decimal notation. + +### Recovering the Terms + +We can recover the terms from this number by `4 divmod`: + + joy? 14811 + 14811 + + joy? 4 divmod + 3702 3 + +We want the term below the rest of the terms: + + joy? swap + 3 3702 + + +### `PE1.2` + +Giving us `4 divmod swap`: + + [PE1.2 4 divmod swap] inscribe + + joy? 14811 + 14811 + joy? PE1.2 + 3 3702 + joy? PE1.2 + 3 2 925 + joy? PE1.2 + 3 2 1 231 + joy? PE1.2 + 3 2 1 3 57 + joy? PE1.2 + 3 2 1 3 1 14 + joy? PE1.2 + 3 2 1 3 1 2 3 + joy? PE1.2 + 3 2 1 3 1 2 3 0 + +So we want: + + joy? 0 0 14811 PE1.2 + 0 0 3 3702 + +And then: + + joy? [PE1.1] dip + 3 3 3702 + +Continuing: + + joy? PE1.2 [PE1.1] dip + 8 5 925 + + joy? PE1.2 [PE1.1] dip + 14 6 231 + + joy? PE1.2 [PE1.1] dip + 23 9 57 + + joy? PE1.2 [PE1.1] dip + 33 10 14 + + joy? PE1.2 [PE1.1] dip + 45 12 3 + + joy? PE1.2 [PE1.1] dip + 60 15 0 + + +### `PE1.3` + +Let's define: + + [PE1.3 PE1.2 [PE1.1] dip] inscribe + +Now: + + 14811 7 [PE1.3] times pop + +Will add up one set of the palindromic sequence of terms: + + joy? 0 0 + 0 0 + + joy? 14811 7 [PE1.3] times pop + 60 15 + +And we want to do that sixty-six times: + + joy? 0 0 + 0 0 + + joy? 66 [14811 7 [PE1.3] times pop] times + 229185 990 + +And then four more: + + joy? 14811 4 [PE1.3] times pop + 233168 999 + +And discard the counter: + + joy? pop + 233168 + +*Violà!* + + +### Let's refactor. + +From these two: + + 14811 7 [PE1.3] times pop + 14811 4 [PE1.3] times pop + +We can generalize the loop counter: + + 14811 n [PE1.3] times pop + +And use `swap` to put it to the left... + + n 14811 swap [PE1.3] times pop + +### `PE1.4` + +...and so we have a new definition: + + [PE1.4 14811 swap [PE1.3] times pop] inscribe + +Now we can simplify the program from: + + 0 0 66 [14811 7 [PE1.3] times pop] times 14811 4 [PE1.3] times pop pop + +To: + + 0 0 66 [7 PE1.4] times 4 PE1.4 pop + +Let's run it and see: + + joy? 0 0 66 [7 PE1.4] times 4 PE1.4 pop + 233168 + +### `PE1` + + [PE1 0 0 66 [7 PE1.4] times 4 PE1.4 pop] inscribe + + joy? PE1 + 233168 + +Here's our joy program all in one place, as it might appear in `def.txt`: + + PE1.1 + [+] dupdip + PE1.2 4 divmod swap + PE1.3 PE1.2 [PE1.1] dip + PE1.4 14811 swap [PE1.3] times pop + PE1 0 0 66 [7 PE1.4] times 4 PE1.4 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. + +Here it is again: + + [0 swap [? [pop 14811] [] branch PE1.2] dip rest cons] + +This self-referential quote contains a bit of state (the initial 0) and a "step" +function (the rest of the quote) and can be "forced" by the `x` combinator to +produce successive terms of our palindromic sequence. The `branch` sub-expression +resets the integer that encodes the terms when it reaches 0. + +Let's `inscribe` this generator quote to keep it handy. + +### `PE1.terms` + + [PE1.terms [0 swap [? [pop 14811] [] branch PE1.2] dip rest cons]] inscribe + +Let's try it out: + + joy? PE1.terms 21 [x] times pop + 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 + +Pretty neat, eh? + +### How Many Terms? + +We know from above that we need sixty-six times seven then four more terms to reach up to but not over one thousand. + + joy? 7 66 * 4 + + 466 + + +### Here they are... + + joy? 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. + + joy? [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. + + joy? 0 0 PE1.terms 466 [x [PE1.1] dip] times popop + 233168 + + +## A Little Further Analysis... + +A little further analysis renders iteration unnecessary. +Consider finding the sum of the positive integers less than or equal to ten. + + joy? [10 9 8 7 6 5 4 3 2 1] sum + 55 + +Gauss famously showed that you can find the sum directly with a simple equation. + +[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 + +The sum of the first N positive integers is: + + (𝑛 + 1) × 𝑛 / 2 + +In Joy this equation could be expressed as: + + dup ++ * 2 / + +(Note that `(𝑛 + 1) × 𝑛` will always be an even number.) + +### Generalizing to Blocks of Terms + +We can apply the same reasoning to the `PE1` problem. + +Recall that between 1 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... + + joy? [3 5 6 9 10 12 15] reverse + [15 12 10 9 6 5 3] + + joy? [978 980 981 984 985 987 990] + [15 12 10 9 6 5 3] [978 980 981 984 985 987 990] + + joy? zip + [[978 15] [980 12] [981 10] [984 9] [985 6] [987 5] [990 3]] + + joy? [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.) + +...and then sum the sums... + + joy? sum + 6945 + +We arrive at 6945. + +### Pair Up the Blocks + +Since there are sixty-six blocks and we are pairing them up, there must be thirty-three pairs, each of which sums to 6945. + + 6945 + joy? 33 * + 229185 + +We also have those four additional terms between 990 and 1000, they are unpaired: + + 993 995 996 999 + +### A Simple Solution + +I think we can just use those as is, so we can give the "sum of all the multiples of 3 or 5 below 1000" like so: + + joy? 6945 33 * [993 995 996 999] cons sum + 233168 + + +### Generalizing + +It's worth noting, I think, that this same reasoning holds for any two numbers 𝑛 and 𝑚 the multiples of which we hope to sum. The multiples would have a cycle of differences of length 𝑘 and so we could compute the sum of 𝑁𝑘 multiples as above. + +The sequence of differences will always be a palindrome. Consider an interval spanning the least common multiple of 𝑛 and 𝑚: + + | | | | | | | | + | | | | | + +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 𝑛 and 𝑚 and their *least common multiple* 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, having done all that, the simplest joy program for the first Project Euler problem is just: + + [PE1 233168] inscribe + +Fin. diff --git a/docs/source/notebooks/Generator_Programs.md b/docs/source/notebooks/Generator_Programs.md new file mode 100644 index 0000000..a3c500c --- /dev/null +++ b/docs/source/notebooks/Generator_Programs.md @@ -0,0 +1,455 @@ +# Generator Programs + +## Using `x` to Generate Values + +Cf. [Self-reproducing and reproducing programs](https://www.kevinalbrecht.com/code/joy-mirror/jp-reprod.html) by Manfred von Thun + +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 + + +## An Example + +We can make a generator for the Natural numbers (0, 1, 2, ...) by using +`0` for the initial state `a` and `[dup ++]` for `[C]`. +We need the `dup` to leave the old state value behind on the stack. +Putting it together: + + [0 swap [dup ++] dip rest cons] + +Let's try it: + + joy? [0 swap [dup ++] dip rest cons] + [0 swap [dup ++] dip rest cons] + + joy? [x] + [0 swap [dup ++] dip rest cons] [x] + + joy? trace + [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 0 • 1 + [0 swap [dup ++] dip rest cons] rest cons + 0 0 1 • + [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. + + 0 [1 swap [dup ++] dip rest cons] + +We can use `x` as many times as we like to get as many terms as we like: + + joy? x x x x x pop + 0 1 2 3 4 5 + +### `direco` + +Let's define a helper function: + + [direco dip rest cons] inscribe + +That makes our generator quote into: + + [0 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: + + [direco] cons [swap] swap concat cons + +Or: + + [direco] cons [swap] swoncat cons + +### make-generator + + [make-generator [direco] cons [swap] swoncat cons] inscribe + +Let's try it out: + + joy? 0 [dup ++] make-generator + [0 swap [dup ++] direco] + +And generate some values: + + joy? x x x pop + 0 1 2 + +### Powers of Two + +Let's generate powers of two: + + joy? 1 [dup 1 <<] make-generator + [1 swap [dup 1 <<] direco] + +We can drive it using `times` with the `x` combinator. + + joy? 10 [x] times pop + 1 2 4 8 16 32 64 128 256 512 + +## Generating Multiples of Three and Five + +Look at the treatment of the Project Euler Problem One in the +[Developing a Program](/notebooks/Developing_a_Program.html) +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 integer: + + Decimal: 3 2 1 3 1 2 3 + Binary: 11 10 01 11 01 10 11 + +The number 11100111011011 in binary is 14811 in decimal notation. +We can recover the terms from this number by using `4 divmod`. + + joy? 14811 [4 divmod swap] make-generator + [14811 swap [4 divmod swap] direco] + + joy? x + 3 [3702 swap [4 divmod swap] direco] + + joy? x + 3 2 [925 swap [4 divmod swap] direco] + + joy? x + 3 2 1 [231 swap [4 divmod swap] direco] + + joy? x + 3 2 1 3 [57 swap [4 divmod swap] direco] + + joy? x + 3 2 1 3 1 [14 swap [4 divmod swap] direco] + + joy? x + 3 2 1 3 1 2 [3 swap [4 divmod swap] direco] + + joy? x + 3 2 1 3 1 2 3 [0 swap [4 divmod swap] direco] + + joy? x + 3 2 1 3 1 2 3 0 [0 swap [4 divmod swap] direco] + + joy? x + 3 2 1 3 1 2 3 0 0 [0 swap [4 divmod swap] direco] + + joy? x + 3 2 1 3 1 2 3 0 0 0 [0 swap [4 divmod swap] direco] + + +...we get a generator that works for seven cycles before it reaches zero. + +### Reset at Zero + +We need a function that checks if the int has reached zero and resets it if so. +That's easy enough to write: + + ? [pop 14811] [] branch + +I don't like that we're checking every time even though we know we +only need to reset the integer every seventh time, but this way we +can include this function in the generator (rather than wrapping the +generator in something to do it only every seventh iteration.) So +the "forcing" function is just `x`. + +### `PE1.1.check` + + [PE1.1.check ? [pop 14811] [] branch] inscribe + +### `PE1.1` + + [PE1.1 4 divmod swap] inscribe + +Now we can `make-generator`: + + joy? 14811 [PE1.1.check PE1.1] make-generator + [14811 swap [PE1.1.check PE1.1] direco] + +We can then "force" the generator with `x` to get as many terms as we like: + + joy? 21 [x] times pop + 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 + +### Run 466 times + +In the PE1 problem [PE1 problem](/notebooks/Developing_a_Program.html) +we are asked to sum all the multiples of three and five +less than 1000. It's worked out that we need to use our cycle of seven numbers +sixty-six times and then four more. + + joy? 7 66 * 4 + + 466 + +If we drive our generator 466 times and sum the stack we get 999: + + joy? 14811 [PE1.1.check PE1.1] make-generator + [14811 swap [PE1.1.check PE1.1] direco] + + joy? 466 [x] times pop enstacken sum + 999 + +If you want to see how this is used read the +[Developing a Program](/notebooks/Developing_a_Program.html) notebook. + + +## 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] + +Let's call `F` `fib_gen`: + + [fib_gen + [popdd over] cons infra uncons] inscribe + +We can just write the initial quote and then "force" it with `x`: + + joy? [1 1 fib_gen] 10 [x] times + 1 2 3 5 8 13 21 34 55 89 [144 89 fib_gen] + +It skips the first term (1) but if that bothers you you can just prepend it to the program: + + 1 [1 1 fib_gen] 10 [x] times + +## 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 +