712 lines
16 KiB
Plaintext
712 lines
16 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# [Project Euler, first problem: \"Multiples of 3 and 5\"](https://projecteuler.net/problem=1)\n",
|
|
"\n",
|
|
" 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.\n",
|
|
"\n",
|
|
" Find the sum of all the multiples of 3 or 5 below 1000."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Let's create a predicate that returns `True` if a number is a multiple of 3 or 5 and `False` otherwise.\n",
|
|
"\n",
|
|
"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:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": []
|
|
}
|
|
],
|
|
"source": [
|
|
"[multiple-of mod bool not] inscribe"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Next we can use that with `app2` to get both Boolean values (for 3 and 5) and the `or` them. (We use `popd` to get rid of the original number):"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": []
|
|
}
|
|
],
|
|
"source": [
|
|
"[P 3 5 [multiple-of] app2 or popd] inscribe"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"[true false false true true]"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"[6 7 8 9 10] [P] map"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": []
|
|
}
|
|
],
|
|
"source": [
|
|
"clear"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Given the predicate function `P` a suitable program is:\n",
|
|
"\n",
|
|
" PE1 == 1000 range [P] filter sum\n",
|
|
"\n",
|
|
"This function generates a list of the integers from 0 to 999, filters\n",
|
|
"that list by `P`, and then sums the result.\n",
|
|
"\n",
|
|
"Logically this is fine, but pragmatically we are doing more work than we\n",
|
|
"should be; we generate one thousand integers but actually use less than\n",
|
|
"half of them. A better solution would be to generate just the multiples\n",
|
|
"we want to sum, and to add them as we go rather than storing them and\n",
|
|
"adding summing them at the end.\n",
|
|
"\n",
|
|
"At first I had the idea to use two counters and increase them by three\n",
|
|
"and five, respectively. This way we only generate the terms that we\n",
|
|
"actually want to sum. We have to proceed by incrementing the counter\n",
|
|
"that is lower, or if they are equal, the three counter, and we have to\n",
|
|
"take care not to double add numbers like 15 that are multiples of both\n",
|
|
"three and five.\n",
|
|
"\n",
|
|
"This seemed a little clunky, so I tried a different approach.\n",
|
|
"\n",
|
|
"Consider the first few terms in the series:\n",
|
|
"\n",
|
|
" 3 5 6 9 10 12 15 18 20 21 ...\n",
|
|
"\n",
|
|
"Subtract each number from the one after it (subtracting 0 from 3):\n",
|
|
"\n",
|
|
" 3 5 6 9 10 12 15 18 20 21 24 25 27 30 ...\n",
|
|
" 0 3 5 6 9 10 12 15 18 20 21 24 25 27 ...\n",
|
|
" -------------------------------------------\n",
|
|
" 3 2 1 3 1 2 3 3 2 1 3 1 2 3 ...\n",
|
|
"\n",
|
|
"You get this lovely repeating palindromic sequence:\n",
|
|
"\n",
|
|
" 3 2 1 3 1 2 3\n",
|
|
"\n",
|
|
"To make a counter that increments by factors of 3 and 5 you just add\n",
|
|
"these differences to the counter one-by-one in a loop.\n",
|
|
"\n",
|
|
"\n",
|
|
"To make use of this sequence to increment a counter and sum terms as we\n",
|
|
"go we need a function that will accept the sum, the counter, and the next\n",
|
|
"term to add, and that adds the term to the counter and a copy of the\n",
|
|
"counter to the running sum. This function will do that:\n",
|
|
"\n",
|
|
" PE1.1 == + [+] dupdip"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": []
|
|
}
|
|
],
|
|
"source": [
|
|
"[PE1.1 + [+] dupdip] inscribe"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3 3"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"0 0 3 PE1.1"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {
|
|
"scrolled": false
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"60 15"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"clear\n",
|
|
"\n",
|
|
"0 0 [3 2 1 3 1 2 3] [PE1.1] step"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"So one `step` through all seven terms brings the counter to 15 and the total to 60."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"66"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"clear\n",
|
|
"\n",
|
|
"1000 15 div"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"990"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"15 *"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"That means we want to run the full list of numbers sixty-six times to get to 990 and then, obviously, the first four numbers 3 2 1 3 to get to 999."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"233168"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"clear\n",
|
|
"\n",
|
|
"[PE1 0 0 66 [[3 2 1 3 1 2 3] [PE1.1] step] times [3 2 1 3] [PE1.1] step pop] inscribe\n",
|
|
"\n",
|
|
"PE1"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"This form uses no extra storage and produces no unused summands. It's\n",
|
|
"good but there's one more trick we can apply. The list of seven terms\n",
|
|
"takes up at least seven bytes. But notice that all of the terms are less\n",
|
|
"than four, and so each can fit in just two bits. We could store all\n",
|
|
"seven terms in just fourteen bits and use masking and shifts to pick out\n",
|
|
"each term as we go. This will use less space and save time loading whole\n",
|
|
"integer terms from the list.\n",
|
|
"\n",
|
|
" 3 2 1 3 1 2 3\n",
|
|
" 0b 11 10 01 11 01 10 11 == 14811"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": []
|
|
}
|
|
],
|
|
"source": [
|
|
"clear"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": []
|
|
}
|
|
],
|
|
"source": [
|
|
"[PE1.2 [3 & PE1.1] dupdip 2 >>] inscribe"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"0 0 14811 PE1.2"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"V('3 3 3702 PE1.2')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"V('0 0 14811 7 [PE1.2] times pop')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"And so we have at last:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"define('PE1 0 0 66 [14811 7 [PE1.2] times pop] times 14811 4 [PE1.2] times popop')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('PE1')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Let's refactor.\n",
|
|
"\n",
|
|
" 14811 7 [PE1.2] times pop\n",
|
|
" 14811 4 [PE1.2] times pop\n",
|
|
" 14811 n [PE1.2] times pop\n",
|
|
" n 14811 swap [PE1.2] times pop"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"define('PE1.3 14811 swap [PE1.2] times pop')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Now we can simplify the definition above:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"define('PE1 0 0 66 [7 PE1.3] times 4 PE1.3 pop')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('PE1')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Here's our joy program all in one place. It doesn't make so much sense, but if you have read through the above description of how it was derived I hope it's clear.\n",
|
|
"\n",
|
|
" PE1.1 == + [+] dupdip\n",
|
|
" PE1.2 == [3 & PE1.1] dupdip 2 >>\n",
|
|
" PE1.3 == 14811 swap [PE1.2] times pop\n",
|
|
" PE1 == 0 0 66 [7 PE1.3] times 4 PE1.3 pop"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Generator Version\n",
|
|
"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."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"define('PE1.terms [0 swap [dup [pop 14811] [] branch [3 &] dupdip 2 >>] dip rest cons]')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('PE1.terms 21 [x] times')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"We know from above that we need sixty-six times seven then four more terms to reach up to but not over one thousand."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('7 66 * 4 +')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Here they are..."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('PE1.terms 466 [x] times pop')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### ...and they do sum to 999."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('[PE1.terms 466 [x] times pop] run sum')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"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."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('0 0 PE1.terms 466 [x [PE1.1] dip] times popop')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# A little further analysis renders iteration unnecessary.\n",
|
|
"Consider finding the sum of the positive integers less than or equal to ten."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('[10 9 8 7 6 5 4 3 2 1] sum')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Instead of summing them, [observe](https://en.wikipedia.org/wiki/File:Animated_proof_for_the_formula_giving_the_sum_of_the_first_integers_1%2B2%2B...%2Bn.gif):\n",
|
|
"\n",
|
|
" 10 9 8 7 6\n",
|
|
" + 1 2 3 4 5\n",
|
|
" ---- -- -- -- --\n",
|
|
" 11 11 11 11 11\n",
|
|
" \n",
|
|
" 11 * 5 = 55\n",
|
|
"\n",
|
|
"From the above example we can deduce that the sum of the first N positive integers is:\n",
|
|
"\n",
|
|
" (N + 1) * N / 2 \n",
|
|
"\n",
|
|
"(The formula also works for odd values of N, I'll leave that to you if you want to work it out or you can take my word for it.)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"define('F dup ++ * 2 floordiv')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"V('10 F')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Generalizing to Blocks of Terms\n",
|
|
"We can apply the same reasoning to the PE1 problem.\n",
|
|
"\n",
|
|
"Between 0 and 990 inclusive there are sixty-six \"blocks\" of seven terms each, starting with:\n",
|
|
"\n",
|
|
" [3 5 6 9 10 12 15]\n",
|
|
" \n",
|
|
"And ending with:\n",
|
|
"\n",
|
|
" [978 980 981 984 985 987 990]\n",
|
|
" \n",
|
|
"If we reverse one of these two blocks and sum pairs..."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('[3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('[3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip [sum] map')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"(Interesting that the sequence of seven numbers appears again in the rightmost digit of each term.)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('[ 3 5 6 9 10 12 15] reverse [978 980 981 984 985 987 990] zip [sum] map sum')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Since there are sixty-six blocks and we are pairing them up, there must be thirty-three pairs, each of which sums to 6945. We also have these additional unpaired terms between 990 and 1000:\n",
|
|
"\n",
|
|
" 993 995 996 999\n",
|
|
" \n",
|
|
"So we can give the \"sum of all the multiples of 3 or 5 below 1000\" like so:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"J('6945 33 * [993 995 996 999] cons sum')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"It's worth noting, I think, that this same reasoning holds for any two numbers $n$ and $m$ the multiples of which we hope to sum. The multiples would have a cycle of differences of length $k$ and so we could compute the sum of $Nk$ multiples as above.\n",
|
|
"\n",
|
|
"The sequence of differences will always be a palidrome. Consider an interval spanning the least common multiple of $n$ and $m$:\n",
|
|
"\n",
|
|
" | | | | | | | |\n",
|
|
" | | | | |\n",
|
|
" \n",
|
|
"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.\n",
|
|
" \n",
|
|
"Geometrically, the actual values of $n$ and $m$ and their *lcm* don't matter, the pattern they make will always be symmetrical around its midpoint. The same reasoning holds for multiples of more than two numbers."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# The Simplest Program"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Of course, the simplest joy program for the first Project Euler problem is just:\n",
|
|
"\n",
|
|
" PE1 == 233168\n",
|
|
"\n",
|
|
"Fin."
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Joypy",
|
|
"language": "",
|
|
"name": "thun"
|
|
},
|
|
"language_info": {
|
|
"file_extension": ".joy",
|
|
"mimetype": "text/plain",
|
|
"name": "Joy"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|