Updating notebooks
to not use Jupyter because I can't figure out how to use the Joy kernel.
This commit is contained in:
parent
70aa2ef5e9
commit
7d8e2ae611
|
|
@ -0,0 +1,398 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Developing a Joy Program</title>
|
||||||
|
<link rel="stylesheet" href="/css/fonts.css">
|
||||||
|
<link rel="stylesheet" href="/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Developing a Joy Program</h1>
|
||||||
|
<p>As a first attempt at writing software in Joy let's tackle
|
||||||
|
<a href="https://projecteuler.net/problem=1">Project Euler, first problem: "Multiples of 3 and 5"</a>:</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>Find the sum of all the multiples of 3 or 5 below 1000.</p>
|
||||||
|
</blockquote>
|
||||||
|
<h2>Filter</h2>
|
||||||
|
<p>Let's create a predicate that returns <code>true</code> if a number is a multiple of 3 or 5 and <code>false</code> otherwise.</p>
|
||||||
|
<h3><code>multiple-of</code></h3>
|
||||||
|
<p>First we write <code>multiple-of</code> which take two numbers and return true if the first is a multiple of the second. We use the <code>mod</code> operator, convert the remainder to a Boolean value, then invert it to get our answer:</p>
|
||||||
|
<pre><code>[multiple-of mod bool not] inscribe
|
||||||
|
</code></pre>
|
||||||
|
<h3><code>multiple-of-3-or-5</code></h3>
|
||||||
|
<p>Next we can use that with <code>app2</code> to get both Boolean values (for 3 and 5) and then use the logical OR function <code>\/</code> on them. (We use <code>popd</code> to get rid of the original number):</p>
|
||||||
|
<pre><code>[multiple-of-3-or-5 3 5 [multiple-of] app2 \/ popd] inscribe
|
||||||
|
</code></pre>
|
||||||
|
<p>Here it is in action:</p>
|
||||||
|
<pre><code>joy? [6 7 8 9 10] [multiple-of-3-or-5] map
|
||||||
|
|
||||||
|
[true false false true true]
|
||||||
|
</code></pre>
|
||||||
|
<p>Given the predicate function <code>multiple-of-3-or-5</code> a suitable program would be:</p>
|
||||||
|
<pre><code>1000 range [multiple-of-3-or-5] filter sum
|
||||||
|
</code></pre>
|
||||||
|
<p>This function generates a list of the integers from 0 to 999, filters
|
||||||
|
that list by <code>multiple-of-3-or-5</code>, and then sums the result. (I should
|
||||||
|
mention that the <code>filter</code> function has not yet been implemented.)</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>This seemed a little clunky, so I tried a different approach.</p>
|
||||||
|
<h2>Looking for Pattern</h2>
|
||||||
|
<p>Consider the first few terms in the series:</p>
|
||||||
|
<pre><code>3 5 6 9 10 12 15 18 20 21 ...
|
||||||
|
</code></pre>
|
||||||
|
<p>Subtract each number from the one after it (subtracting 0 from 3):</p>
|
||||||
|
<pre><code>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 ...
|
||||||
|
</code></pre>
|
||||||
|
<p>You get this lovely repeating palindromic sequence:</p>
|
||||||
|
<pre><code>3 2 1 3 1 2 3
|
||||||
|
</code></pre>
|
||||||
|
<p>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.</p>
|
||||||
|
<h3>Increment a Counter</h3>
|
||||||
|
<p>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:</p>
|
||||||
|
<pre><code>+ [+] dupdip
|
||||||
|
</code></pre>
|
||||||
|
<p>We start with a sum, the counter, and a term to add:</p>
|
||||||
|
<pre><code>joy? 0 0 3
|
||||||
|
0 0 3
|
||||||
|
</code></pre>
|
||||||
|
<p>Here is our function, quoted to let it be run with the <code>trace</code> combinator
|
||||||
|
(which is like <code>i</code> but it also prints a trace of evaluation to <code>stdout</code>.)</p>
|
||||||
|
<pre><code>joy? [+ [+] dupdip]
|
||||||
|
0 0 3 [+ [+] dupdip]
|
||||||
|
</code></pre>
|
||||||
|
<p>And here we go:</p>
|
||||||
|
<pre><code>joy? trace
|
||||||
|
|
||||||
|
0 0 3 • + [+] dupdip
|
||||||
|
0 3 • [+] dupdip
|
||||||
|
0 3 [+] • dupdip
|
||||||
|
0 3 • + 3
|
||||||
|
3 • 3
|
||||||
|
3 3 •
|
||||||
|
|
||||||
|
3 3
|
||||||
|
</code></pre>
|
||||||
|
<h3><code>PE1.1</code></h3>
|
||||||
|
<p>We can <code>inscribe</code> it for use in later definitions:</p>
|
||||||
|
<pre><code>[PE1.1 + [+] dupdip] inscribe
|
||||||
|
</code></pre>
|
||||||
|
<p>Let's try it out on our palindromic sequence:</p>
|
||||||
|
<pre><code>joy? 0 0 [3 2 1 3 1 2 3] [PE1.1] step
|
||||||
|
60 15
|
||||||
|
</code></pre>
|
||||||
|
<p>So one <code>step</code> through all seven terms brings the counter to 15 and the total to 60.</p>
|
||||||
|
<h3>How Many Times?</h3>
|
||||||
|
<p>We want all the terms less than 1000, and each pass through the palindromic sequence
|
||||||
|
will count off 15 so how many is that?</p>
|
||||||
|
<pre><code>joy? 1000 15 /
|
||||||
|
66
|
||||||
|
</code></pre>
|
||||||
|
<p>So 66 × 15 bring us to...</p>
|
||||||
|
<pre><code>66
|
||||||
|
joy? 15 *
|
||||||
|
990
|
||||||
|
</code></pre>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>Start with the sum and counter:</p>
|
||||||
|
<pre><code>joy? 0 0
|
||||||
|
0 0
|
||||||
|
</code></pre>
|
||||||
|
<p>We will run a program sixty-six times:</p>
|
||||||
|
<pre><code>joy? 66
|
||||||
|
0 0 66
|
||||||
|
</code></pre>
|
||||||
|
<p>This is the program we will run sixty-six times, it steps through the
|
||||||
|
palindromic sequence and sums up the terms:</p>
|
||||||
|
<pre><code>joy? [[3 2 1 3 1 2 3] [PE1.1] step]
|
||||||
|
0 0 66 [[3 2 1 3 1 2 3] [PE1.1] step]
|
||||||
|
</code></pre>
|
||||||
|
<p>Runing that brings us to the sum of the numbers less than 991:</p>
|
||||||
|
<pre><code>joy? times
|
||||||
|
229185 990
|
||||||
|
</code></pre>
|
||||||
|
<p>We need to count 9 more to reach the sum of the numbers less than 1000:</p>
|
||||||
|
<pre><code>joy? [3 2 1 3] [PE1.1] step
|
||||||
|
233168 999
|
||||||
|
</code></pre>
|
||||||
|
<p>All that remains is the counter, which we can discard:</p>
|
||||||
|
<pre><code>joy? pop
|
||||||
|
233168
|
||||||
|
</code></pre>
|
||||||
|
<p>And so we have our answer: <strong>233168</strong></p>
|
||||||
|
<p>This form uses no extra storage and produces no unused summands. It's
|
||||||
|
good but there's one more trick we can apply.</p>
|
||||||
|
<h2>A Slight Increase of Efficiency</h2>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>Let's encode the term in 7 × 2 = 14 bits:</p>
|
||||||
|
<pre><code>Decimal: 3 2 1 3 1 2 3
|
||||||
|
Binary: 11 10 01 11 01 10 11
|
||||||
|
</code></pre>
|
||||||
|
<p>The number 11100111011011 in binary is 14811 in decimal notation.</p>
|
||||||
|
<h3>Recovering the Terms</h3>
|
||||||
|
<p>We can recover the terms from this number by <code>4 divmod</code>:</p>
|
||||||
|
<pre><code>joy? 14811
|
||||||
|
14811
|
||||||
|
|
||||||
|
joy? 4 divmod
|
||||||
|
3702 3
|
||||||
|
</code></pre>
|
||||||
|
<p>We want the term below the rest of the terms:</p>
|
||||||
|
<pre><code>joy? swap
|
||||||
|
3 3702
|
||||||
|
</code></pre>
|
||||||
|
<h3><code>PE1.2</code></h3>
|
||||||
|
<p>Giving us <code>4 divmod swap</code>:</p>
|
||||||
|
<pre><code>[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
|
||||||
|
</code></pre>
|
||||||
|
<p>So we want:</p>
|
||||||
|
<pre><code>joy? 0 0 14811 PE1.2
|
||||||
|
0 0 3 3702
|
||||||
|
</code></pre>
|
||||||
|
<p>And then:</p>
|
||||||
|
<pre><code>joy? [PE1.1] dip
|
||||||
|
3 3 3702
|
||||||
|
</code></pre>
|
||||||
|
<p>Continuing:</p>
|
||||||
|
<pre><code>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
|
||||||
|
</code></pre>
|
||||||
|
<h3><code>PE1.3</code></h3>
|
||||||
|
<p>Let's define:</p>
|
||||||
|
<pre><code>[PE1.3 PE1.2 [PE1.1] dip] inscribe
|
||||||
|
</code></pre>
|
||||||
|
<p>Now:</p>
|
||||||
|
<pre><code>14811 7 [PE1.3] times pop
|
||||||
|
</code></pre>
|
||||||
|
<p>Will add up one set of the palindromic sequence of terms:</p>
|
||||||
|
<pre><code>joy? 0 0
|
||||||
|
0 0
|
||||||
|
|
||||||
|
joy? 14811 7 [PE1.3] times pop
|
||||||
|
60 15
|
||||||
|
</code></pre>
|
||||||
|
<p>And we want to do that sixty-six times:</p>
|
||||||
|
<pre><code>joy? 0 0
|
||||||
|
0 0
|
||||||
|
|
||||||
|
joy? 66 [14811 7 [PE1.3] times pop] times
|
||||||
|
229185 990
|
||||||
|
</code></pre>
|
||||||
|
<p>And then four more:</p>
|
||||||
|
<pre><code>joy? 14811 4 [PE1.3] times pop
|
||||||
|
233168 999
|
||||||
|
</code></pre>
|
||||||
|
<p>And discard the counter:</p>
|
||||||
|
<pre><code>joy? pop
|
||||||
|
233168
|
||||||
|
</code></pre>
|
||||||
|
<p><em>Violà!</em></p>
|
||||||
|
<h3>Let's refactor.</h3>
|
||||||
|
<p>From these two:</p>
|
||||||
|
<pre><code>14811 7 [PE1.3] times pop
|
||||||
|
14811 4 [PE1.3] times pop
|
||||||
|
</code></pre>
|
||||||
|
<p>We can generalize the loop counter:</p>
|
||||||
|
<pre><code>14811 n [PE1.3] times pop
|
||||||
|
</code></pre>
|
||||||
|
<p>And use <code>swap</code> to put it to the left...</p>
|
||||||
|
<pre><code>n 14811 swap [PE1.3] times pop
|
||||||
|
</code></pre>
|
||||||
|
<h3><code>PE1.4</code></h3>
|
||||||
|
<p>...and so we have a new definition:</p>
|
||||||
|
<pre><code>[PE1.4 14811 swap [PE1.3] times pop] inscribe
|
||||||
|
</code></pre>
|
||||||
|
<p>Now we can simplify the program from:</p>
|
||||||
|
<pre><code>0 0 66 [14811 7 [PE1.3] times pop] times 14811 4 [PE1.3] times pop pop
|
||||||
|
</code></pre>
|
||||||
|
<p>To:</p>
|
||||||
|
<pre><code>0 0 66 [7 PE1.4] times 4 PE1.4 pop
|
||||||
|
</code></pre>
|
||||||
|
<p>Let's run it and see:</p>
|
||||||
|
<pre><code>joy? 0 0 66 [7 PE1.4] times 4 PE1.4 pop
|
||||||
|
233168
|
||||||
|
</code></pre>
|
||||||
|
<h3><code>PE1</code></h3>
|
||||||
|
<pre><code>[PE1 0 0 66 [7 PE1.4] times 4 PE1.4 pop] inscribe
|
||||||
|
|
||||||
|
joy? PE1
|
||||||
|
233168
|
||||||
|
</code></pre>
|
||||||
|
<p>Here's our joy program all in one place, as it might appear in <code>def.txt</code>:</p>
|
||||||
|
<pre><code>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
|
||||||
|
</code></pre>
|
||||||
|
<h2>Generator Version</h2>
|
||||||
|
<p>It's a little clunky iterating sixty-six times though the seven numbers then four more. In the <em>Generator Programs</em> notebook we derive a generator that can be repeatedly driven by the <code>x</code> combinator to produce a stream of the seven numbers repeating over and over again.</p>
|
||||||
|
<p>Here it is again:</p>
|
||||||
|
<pre><code>[0 swap [? [pop 14811] [] branch PE1.2] dip rest cons]
|
||||||
|
</code></pre>
|
||||||
|
<p>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 <code>x</code> combinator to
|
||||||
|
produce successive terms of our palindromic sequence. The <code>branch</code> sub-expression
|
||||||
|
resets the integer that encodes the terms when it reaches 0.</p>
|
||||||
|
<p>Let's <code>inscribe</code> this generator quote to keep it handy.</p>
|
||||||
|
<h3><code>PE1.terms</code></h3>
|
||||||
|
<pre><code>[PE1.terms [0 swap [? [pop 14811] [] branch PE1.2] dip rest cons]] inscribe
|
||||||
|
</code></pre>
|
||||||
|
<p>Let's try it out:</p>
|
||||||
|
<pre><code>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
|
||||||
|
</code></pre>
|
||||||
|
<p>Pretty neat, eh?</p>
|
||||||
|
<h3>How Many Terms?</h3>
|
||||||
|
<p>We know from above that we need sixty-six times seven then four more terms to reach up to but not over one thousand.</p>
|
||||||
|
<pre><code>joy? 7 66 * 4 +
|
||||||
|
466
|
||||||
|
</code></pre>
|
||||||
|
<h3>Here they are...</h3>
|
||||||
|
<pre><code>joy? PE1.terms 466 [x] times pop
|
||||||
|
</code></pre>
|
||||||
|
<blockquote>
|
||||||
|
<p>3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3</p>
|
||||||
|
</blockquote>
|
||||||
|
<h3>...and they do sum to 999.</h3>
|
||||||
|
<pre><code>joy? [PE1.terms 466 [x] times pop] run sum
|
||||||
|
999
|
||||||
|
</code></pre>
|
||||||
|
<p>Now we can use <code>PE1.1</code> to accumulate the terms as we go, and then <code>pop</code> the
|
||||||
|
generator and the counter from the stack when we're done, leaving just the sum.</p>
|
||||||
|
<pre><code>joy? 0 0 PE1.terms 466 [x [PE1.1] dip] times popop
|
||||||
|
233168
|
||||||
|
</code></pre>
|
||||||
|
<h2>A Little Further Analysis...</h2>
|
||||||
|
<p>A little further analysis renders iteration unnecessary.
|
||||||
|
Consider finding the sum of the positive integers less than or equal to ten.</p>
|
||||||
|
<pre><code>joy? [10 9 8 7 6 5 4 3 2 1] sum
|
||||||
|
55
|
||||||
|
</code></pre>
|
||||||
|
<p>Gauss famously showed that you can find the sum directly with a simple equation.</p>
|
||||||
|
<p><a href="https://en.wikipedia.org/wiki/File:Animated_proof_for_the_formula_giving_the_sum_of_the_first_integers_1%2B2%2B...%2Bn.gif">Observe</a>:</p>
|
||||||
|
<pre><code> 10 9 8 7 6
|
||||||
|
+ 1 2 3 4 5
|
||||||
|
---- -- -- -- --
|
||||||
|
11 11 11 11 11
|
||||||
|
|
||||||
|
11 × 5 = 55
|
||||||
|
</code></pre>
|
||||||
|
<p>The sum of the first N positive integers is:</p>
|
||||||
|
<pre><code>(𝑛 + 1) × 𝑛 / 2
|
||||||
|
</code></pre>
|
||||||
|
<p>In Joy this equation could be expressed as:</p>
|
||||||
|
<pre><code>dup ++ * 2 /
|
||||||
|
</code></pre>
|
||||||
|
<p>(Note that <code>(𝑛 + 1) × 𝑛</code> will always be an even number.)</p>
|
||||||
|
<h3>Generalizing to Blocks of Terms</h3>
|
||||||
|
<p>We can apply the same reasoning to the <code>PE1</code> problem.</p>
|
||||||
|
<p>Recall that between 1 and 990 inclusive there are sixty-six "blocks" of seven terms each, starting with:</p>
|
||||||
|
<pre><code>3 5 6 9 10 12 15
|
||||||
|
</code></pre>
|
||||||
|
<p>And ending with:</p>
|
||||||
|
<pre><code>978 980 981 984 985 987 990
|
||||||
|
</code></pre>
|
||||||
|
<p>If we reverse one of these two blocks and sum pairs...</p>
|
||||||
|
<pre><code>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]
|
||||||
|
</code></pre>
|
||||||
|
<p>(Interesting that the sequence of seven numbers appears again in the rightmost digit of each term.)</p>
|
||||||
|
<p>...and then sum the sums...</p>
|
||||||
|
<pre><code>joy? sum
|
||||||
|
6945
|
||||||
|
</code></pre>
|
||||||
|
<p>We arrive at 6945.</p>
|
||||||
|
<h3>Pair Up the Blocks</h3>
|
||||||
|
<p>Since there are sixty-six blocks and we are pairing them up, there must be thirty-three pairs, each of which sums to 6945.</p>
|
||||||
|
<pre><code>6945
|
||||||
|
joy? 33 *
|
||||||
|
229185
|
||||||
|
</code></pre>
|
||||||
|
<p>We also have those four additional terms between 990 and 1000, they are unpaired:</p>
|
||||||
|
<pre><code>993 995 996 999
|
||||||
|
</code></pre>
|
||||||
|
<h3>A Simple Solution</h3>
|
||||||
|
<p>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:</p>
|
||||||
|
<pre><code>joy? 6945 33 * [993 995 996 999] cons sum
|
||||||
|
233168
|
||||||
|
</code></pre>
|
||||||
|
<h3>Generalizing</h3>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>The sequence of differences will always be a palindrome. Consider an interval spanning the least common multiple of 𝑛 and 𝑚:</p>
|
||||||
|
<pre><code>| | | | | | | |
|
||||||
|
| | | | |
|
||||||
|
</code></pre>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>Geometrically, the actual values of 𝑛 and 𝑚 and their <em>least common multiple</em> 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.</p>
|
||||||
|
<h1>The Simplest Program</h1>
|
||||||
|
<p>Of course, having done all that, the simplest joy program for the first Project Euler problem is just:</p>
|
||||||
|
<pre><code>[PE1 233168] inscribe
|
||||||
|
</code></pre>
|
||||||
|
<p>Fin.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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.
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
Loading…
Reference in New Issue