{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Peter Norvig
5 January 2016
revised 18 May 2018
" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "# Four 4s, Five 5s, and Countdown to 2016\n", "\n", "On January 1, 2016 Alex Bellos [posed](http://www.theguardian.com/science/2016/jan/04/can-you-solve-it-complete-the-equation-10-9-8-7-6-5-4-3-2-1-2016) this New Year's puzzle:\n", "\n", "\n", "> Fill in the blanks so that this equation makes arithmetical sense:\n", ">\n", "> `10 ␣ 9 ␣ 8 ␣ 7 ␣ 6 ␣ 5 ␣ 4 ␣ 3 ␣ 2 ␣ 1 = 2016`\n", ">\n", "> You are allowed to use *only* the four basic arithmetical operations: +, -, ×, ÷. But brackets (parentheses) can be used wherever needed. So, for example, the solution could begin\n", ">\n", "> `(10 + 9) * (8` ... or `10 + (9 * 8)` ...\n", "\n", "Let's see if we can solve this puzzle, and some of the related ones from Alex's [first](http://www.theguardian.com/science/2016/jan/04/can-you-solve-it-complete-the-equation-10-9-8-7-6-5-4-3-2-1-2016) and [second](http://www.theguardian.com/science/2016/jan/04/did-you-solve-it-complete-the-equation-10-9-8-7-6-5-4-3-2-1-2016) post. " ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "# Four Operations, No Brackets\n", "\n", "We'll start with a simpler version of the puzzle: a countdown with no brackets. \n", "\n", "There are nine blanks, each of which can be filled by one of four operators, so there are 94 = 262,144 possibilities, few enough that we can enumerate them all, using `itertools.product` to get sequences of operators, and then `str.format` to plug them into blanks, and then `eval` to evaluate the string:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from itertools import product\n", "from functools import lru_cache # Used later\n", "from collections import Counter, defaultdict # Used later" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "('+', '+', '+', '+', '+', '+', '+', '+', '+')" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ops = next(product(('+', '-', '*', '/'), repeat=9))\n", "ops" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'10+9+8+7+6+5+4+3+2+1'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "'10{}9{}8{}7{}6{}5{}4{}3{}2{}1'.format(*ops)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "55" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "eval(_)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We need to catch errors such as dividing by zero, so I'll define a wrapper function, `evaluate`, to do that, and I'll define `simple_countdown` to put the pieces together:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [], "source": [ "def evaluate(exp):\n", " \"eval(exp), or None if there is an arithmetic error.\"\n", " try:\n", " return eval(exp)\n", " except ArithmeticError:\n", " return None\n", "\n", "def simple_countdown(target, operators=('+', '-', '*', '/')):\n", " \"All solutions to the countdown puzzle (with no brackets).\"\n", " exps = ('10{}9{}8{}7{}6{}5{}4{}3{}2{}1'.format(*ops)\n", " for ops in product(operators, repeat=9))\n", " return [exp for exp in exps if evaluate(exp) == target]" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "simple_countdown(2016)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "Too bad; we did all that work and didn't find a solution. What years *can* we find solutions for? I'll modify `simple_countdown` to take a collection of target years rather than a single one, and return a dict of the form `{year: 'expression'}` for each expression that evaluates to one of the target years. I'll also generalize it to allow any format string, not just the countdown string." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "{1981: '10*9*8+7*6*5*4*3/2+1',\n", " 1979: '10*9*8+7*6*5*4*3/2-1',\n", " 1980: '10*9*8+7*6*5*4*3/2/1',\n", " 2019: '10*9*8*7/6/5*4*3+2+1',\n", " 2017: '10*9*8*7/6/5*4*3+2-1',\n", " 2018: '10*9*8*7/6/5*4*3+2/1',\n", " 2015: '10*9*8*7/6/5*4*3-2+1',\n", " 2013: '10*9*8*7/6/5*4*3-2-1',\n", " 2014: '10*9*8*7/6/5*4*3-2/1'}" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def simple_countdown(targets, operators=('+', '-', '*', '/'), \n", " fmt='10{}9{}8{}7{}6{}5{}4{}3{}2{}1'):\n", " \"All solutions to the countdown puzzle (with no brackets).\"\n", " exps = (fmt.format(*ops)\n", " for ops in product(operators, repeat=fmt.count('{}')))\n", " return {int(evaluate(exp)): exp for exp in exps if evaluate(exp) in targets}\n", "\n", "simple_countdown(range(1900, 2100))" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "Interesting: in the 20th and 21st centuries, there are only two \"golden eras\" where the bracketless countdown equation works: the three year period centered on 1980, and the seven year period that is centered on 2016, but omits 2016. " ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "# Four Operations, With Brackets\n", "\n", "Now we return to the original problem. Can I make use of what I have so far? Well, my second version of `simple_countdown` accepts different format strings, so I could give it all possible bracketed format strings and let it fill in all possible operator combinations. How long would that take? I happen to remember that the number of bracketings of *n* operands is the nth [Catalan number](https://oeis.org/A000108), which I looked up and found to be 4862. A single call to `simple_countdown` took about 2 seconds, so we could do 4862 calls in about 160 minutes. That's not terrible, but (a) it would still take work to generate all the bracketings, and (b) I think I can find another way that is much faster.\n", "\n", "I'll restate the problem as: given the sequence of numbers (10, 9, ... 1), create an expression whose value is 2016.\n", "But to get there I'll solve a more general problem: given any sequence of numbers, like `(10, 9, 8)`, what `{value: expression}` pairs can I make with them?\n", "I'll define `expressions(numbers)` to return a dict of `{value: expression}` \n", "for all expressions (strings) whose numeric value is `value`, and can be made from `numbers`, for example:\n", "\n", " expressions((10,)) ⇒ {10: '10'}\n", " expressions((9, 8)) ⇒ {1: '(9-8)', 1.125: '(9/8)', 17: '(9+8)', 72: '(9*8)'}\n", "\n", "I'll use the idea of [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming): break the problem down into simpler subparts, compute an answer for each subpart, and remember intermediate results so we don't need to re-compute them later. How do we break the problem into parts? `expressions((10, 9, 8))` should consist of all the ways of splitting `(10, 9, 8)` into two parts, finding all the expressions that can be made with each part, and combining pairs of expressions with any of the four operators:\n", "\n", " expressions((10, 9, 8)) ⇒ {11: '(10+(9-8))', 27: '(10+(9+8))', 720: '(10*(9*8))', ...}\n", "\n", "First the function `splits`:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [], "source": [ "c10 = (10, 9, 8, 7, 6, 5, 4, 3, 2, 1)\n", "\n", "def splits(sequence):\n", " \"Split sequence into two non-empty parts, in all ways.\"\n", " return [(sequence[:i], sequence[i:]) \n", " for i in range(1, len(sequence))]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "[((10,), (9, 8, 7, 6, 5, 4, 3, 2, 1)),\n", " ((10, 9), (8, 7, 6, 5, 4, 3, 2, 1)),\n", " ((10, 9, 8), (7, 6, 5, 4, 3, 2, 1)),\n", " ((10, 9, 8, 7), (6, 5, 4, 3, 2, 1)),\n", " ((10, 9, 8, 7, 6), (5, 4, 3, 2, 1)),\n", " ((10, 9, 8, 7, 6, 5), (4, 3, 2, 1)),\n", " ((10, 9, 8, 7, 6, 5, 4), (3, 2, 1)),\n", " ((10, 9, 8, 7, 6, 5, 4, 3), (2, 1)),\n", " ((10, 9, 8, 7, 6, 5, 4, 3, 2), (1,))]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "splits(c10)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "Now the function `expressions`:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "@lru_cache(None)\n", "def expressions(numbers: tuple) -> dict:\n", " \"Return {value: expr} for all expressions that can be made from numbers.\"\n", " if len(numbers) == 1: \n", " return {numbers[0]: str(numbers[0])}\n", " else: \n", " result = {}\n", " for (Lnums, Rnums) in splits(numbers):\n", " for (L, R) in product(expressions(Lnums), expressions(Rnums)):\n", " Lexp = '(' + expressions(Lnums)[L]\n", " Rexp = expressions(Rnums)[R] + ')'\n", " result[L * R] = Lexp + '*' + Rexp\n", " result[L - R] = Lexp + '-' + Rexp\n", " result[L + R] = Lexp + '+' + Rexp\n", " if R != 0: \n", " result[L / R] = Lexp + '/' + Rexp\n", " return result" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "For example, at one point in a call to `expressions((10, 9, 8))` we would have:\n", "\n", " Lnums, Rnums = (10), (9, 8)\n", " L, R = 10, 1\n", " Lexp, Rexp = '(10', '(9-8))'\n", "\n", "This would lead to us adding the following four entries to `result`:\n", "\n", " result[10] = '(10*(9-8))'\n", " result[9] = '(10-(9-8))'\n", " result[11] = '(10+(9-8))'\n", " result[10] = '(10/(9-8))'\n", " \n", "The decorator `@lru_cache` takes care of storing the intermediate results. Rather than catching errors, we just avoid division by 0.\n", "\n", "Let's give it a try:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{10: '10'}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expressions((10,))" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "{72: '(9*8)', 1: '(9-8)', 17: '(9+8)', 1.125: '(9/8)'}" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expressions((9, 8))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "{720: '((10*9)*8)',\n", " -62: '(10-(9*8))',\n", " 82: '((10*9)-8)',\n", " 0.1388888888888889: '((10/9)/8)',\n", " 10: '(10/(9-8))',\n", " 9: '((10-9)+8)',\n", " 11: '((10+9)-8)',\n", " 170: '(10*(9+8))',\n", " -7: '((10-9)-8)',\n", " 27: '((10+9)+8)',\n", " 0.5882352941176471: '(10/(9+8))',\n", " 11.25: '((10*9)/8)',\n", " 8.875: '(10-(9/8))',\n", " 11.125: '(10+(9/8))',\n", " 8.88888888888889: '((10/9)*8)',\n", " 98: '((10*9)+8)',\n", " 8: '((10-9)*8)',\n", " 0.125: '((10-9)/8)',\n", " 152: '((10+9)*8)',\n", " 2.375: '((10+9)/8)',\n", " -6.888888888888889: '((10/9)-8)',\n", " 9.11111111111111: '((10/9)+8)'}" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expressions((10, 9, 8))" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "That looks reasonable. Let's solve the whole puzzle.\n", "\n", "# Countdown to 2016: A Solution" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 29.9 s, sys: 833 ms, total: 30.7 s\n", "Wall time: 30.8 s\n" ] }, { "data": { "text/plain": [ "'(((((((((10*9)+8)*7)-6)-5)-4)*3)+2)+1)'" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%time expressions(c10)[2016]" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "We have an answer! And in seconds, not hours, thanks to dynamic programming! Here are solutions for nearby years:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{2010: '((((((10+(((9*8)-7)*6))*5)+4)+3)+2)+1)',\n", " 2011: '((((((10+9)*8)+7)*(6+(5*(4/3))))-2)-1)',\n", " 2012: '((((((10+9)*8)+7)*(6+(5*(4/3))))-2)/1)',\n", " 2013: '((((((10+9)*8)+7)*(6+(5*(4/3))))-2)+1)',\n", " 2014: '(((((((10+((9*8)*7))-6)-5)*4)+3)-2)+1)',\n", " 2015: '(((((((((10*9)+8)*7)-6)-5)-4)*3)+2)/1)',\n", " 2016: '(((((((((10*9)+8)*7)-6)-5)-4)*3)+2)+1)',\n", " 2017: '(((10*(((9*8)-((7*6)/(5+4)))*3))-2)-1)',\n", " 2018: '(((10*(((9*8)-((7*6)/(5+4)))*3))-2)/1)',\n", " 2019: '(((10*(((9*8)-((7*6)/(5+4)))*3))-2)+1)',\n", " 2020: '(((((10+((9+((8+7)*6))*5))*4)+3)-2)-1)',\n", " 2021: '((((((((10-9)+(8*7))*6)-5)*4)*3)/2)-1)',\n", " 2022: '((((((((10-9)+(8*7))*6)-5)*4)*3)/2)/1)',\n", " 2023: '(((((((10*9)*(8+7))/6)*(5+4))-3)+2)-1)',\n", " 2024: '(((((((10*9)*(8+7))/6)*(5+4))-3)+2)/1)',\n", " 2025: '(((((((10*9)*(8+7))/6)*(5+4))-3)+2)+1)'}" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{y: expressions(c10)[y] for y in range(2010, 2026)}" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "# Counting Solutions\n", "\n", "Alex Bellos had another challenge: \n", "\n", "> I was half hoping a computer scientist would let me know exactly how many solutions [to the Countdown problem] there are with only the four basic operations. Maybe someone will. \n", "\n", "As it stands, my program can't answer that question, because I only keep one expression for each value. \n", "\n", "Also, I'm not sure what it means to be a distinct solution. For example, are `((10+9)+8)` and `(10+(9+8))` different, or are they same, because they both are equivalent to `(10+9+8)`? Similarly, are `((3-2)-1)` and `(3-(2+1)` different, or the same because they both are equivalent to `(3 + -2 + -1)`? I think the notion of \"distinct solution\" is just inherently ambiguous. My choice is to count each of these as distinct: every expression has exactly ten numbers, nine operators, and nine pairs of brackets, and if an expression differs in any character, it is different. But I won't argue with anyone who prefers a different definition of \"distinct solution.\"\n", "\n", "So how can I count expressions? One approach would be to go back to enumerating every equation (all 4862 × 49 = 1.2 bilion of them) and checking which ones equal 2016. That would take about 40 hours with my Python program. A better approach is to mimic `expressions`, but to make a table of counts, rather than expression strings. I want:\n", "\n", " counts[(10, 9, 8)][27] == 2\n", " \n", "because there are 2 ways to make 27 with the numbers `(10, 9, 8)`, namely, `((10+9)+8)` and `(10+(9+8))`. And in general, if there are `Lcount` ways to make `L` using `Lnums` and `Rcount` ways to make `R` using `Rnums` then there are `Lcount * Rcount` ways of making `L * R` using `Lnums` followed by `Rnums`. So we have:\n" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [], "source": [ "@lru_cache(None)\n", "def counts(numbers: tuple) -> Counter:\n", " \"Return a Counter of {value: count} for every value that can be made from numbers.\"\n", " if len(numbers) == 1: # Only one way to make an expression out of a single number\n", " return Counter(numbers)\n", " else: \n", " result = Counter()\n", " for (Lnums, Rnums) in splits(numbers):\n", " for L in counts(Lnums):\n", " for R in counts(Rnums):\n", " count = counts(Lnums)[L] * counts(Rnums)[R]\n", " result[L + R] += count\n", " result[L - R] += count\n", " result[L * R] += count\n", " if R != 0:\n", " result[L / R] += count\n", " return result" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Counter({4: 2, 0: 1, 1.0: 1})" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "counts((2, 2)) # corresponds to {0: '2-2', 1.0: '2/2', 4: '2+2' or '2*2'}" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "counts((10, 9, 8))[27] # (10+9)+8 or 10+(9+8)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "Looks good to me. Now let's see if we can answer the question." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "30066" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "counts(c10)[2016]" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "This says there are 30,066 distinct expressions for 2016. \n", "\n", "**But we're forgetting about round-off error.**\n", "\n", "Let's find all the values that are very near to `2016`:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "{2016.0000000000002: 5792,\n", " 2016.0: 30066,\n", " 2015.9999999999995: 1930,\n", " 2015.999999999997: 15,\n", " 2016.0000000000005: 510,\n", " 2016.0000000000018: 264,\n", " 2016.0000000000023: 12,\n", " 2015.9999999999998: 5868,\n", " 2015.9999999999993: 14,\n", " 2016.000000000002: 18,\n", " 2015.999999999999: 10}" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{y: counts(c10)[y] \n", " for y in counts(c10)\n", " if abs(y - 2016) < 1e-10}" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "I suspect that all of these actually should be exactly 2016. \n", "To be absolutely sure, I could re-do the calculations using exact rational arithmetic, as provided by the `fractions.Fraction` data type. From experience I know that would be an order of magnitude slower, so instead I'll just add up the counts:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "44499" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum(_.values())" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "I have more confidence in this answer, 44,499, than in 30,066, but I wouldn't accept it as definitive until it was independently verified and passed an extensive test suite. And of course, if you have a different definition of \"distinct solution,\" you will get a different answer." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Four 4s\n", "\n", "Alex Bellos continues with a related puzzle:\n", " \n", "> The most famous “fill in the gaps in the equation” puzzle is known as the [four fours](https://en.wikipedia.org/wiki/Four_fours), because every equation is of the form\n", ">\n", "> `4 ␣ 4 ␣ 4 ␣ 4 = X.`\n", ">\n", ">In the classic form of the puzzle you must find a solution for X = 0 to 9 using just addition, subtraction, multiplication and division.\n", "\n", "This puzzle goes back to a [1914 publication](https://archive.org/details/mathematicalrecr00ball) by the \"most famous\" mathematician/magician W. W. Rouse Ball. The solution is easy:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{0: '(((4-4)-4)+4)',\n", " 1: '(((4/4)-4)+4)',\n", " 2: '((4/(4+4))*4)',\n", " 3: '(((4+4)+4)/4)',\n", " 4: '(((4-4)/4)+4)',\n", " 5: '(((4*4)+4)/4)',\n", " 6: '(((4+4)/4)+4)',\n", " 7: '((4-(4/4))+4)',\n", " 8: '(((4+4)/4)*4)',\n", " 9: '(((4/4)+4)+4)'}" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{i: expressions((4, 4, 4, 4))[i] for i in range(10)}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that I didn't do anything special to take advantage of the fact that in `(4, 4, 4, 4)` the digits are all the same. Happily, `lru_cache` does that for me automatically! If I split that into `(4, 4)` and `(4, 4)`, when it comes time to do the second half of the split, the result will already be in the cache, and so won't be recomputed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# New Mathematical Operations\n", "\n", "Bellos then writes:\n", " \n", "> If you want to show off, you can introduce **new mathematical operations** such as powers, square roots, concatenation and decimals, ... or use the factorial symbol, `!`.\n", "\n", "Bellos is suggesting the following operations:\n", "\n", "- **Powers**: `(4 ^ 4)` is 4 to the fourth power, or 256.\n", "- **Square roots:** `√4` is the square root of 4, or 2.\n", "- **Concatenation:** `44` is forty-four.\n", "- **Decimals:** `4.4` is four and four tenths.\n", "- **Factorials** `4!` is 4 × 3 × 2 × 1, or 24.\n", "\n", "There are some complications to deal with:\n", "\n", "- **Irrationals**: `√2` is an irrational number; so we can't do exact rational arithmetic.\n", "- **Imaginaries**: `√-1` is an imaginary number; do we want to deal with that?\n", "- **Overflow**: `(10. ^ (9. ^ 8.))`, as a `float`, gives an `OverflowError`.\n", "- **Out of memory**: [`(10 ^ (9 ^ (8 * 7)))`](http://www.wolframalpha.com/input/?i=10+%5E+9+%5E+56), as an `int`, gives an `OutOfMemoryError` (even if your memory uses every atom on Earth).\n", "- **Infinite expressions**: We could add an infinite number of square root and/or factorial signs to any expression: `√√√√√√(4!!!!!!!!)`...\n", "- **Round-off error**: `(49*(1/49))` evaluates to `0.9999999999999999`, not `1`.\n", "\n", "We could try to manage this with *symbolic algebra*, perhaps using [SymPy](http://www.sympy.org/en/index.html), but that seems complicated, so instead I will make some arbitrary decisions:\n", "\n", "- Use floating point approximations for everything, rational or irrational.\n", "- Disallow irrationals.\n", "- Disallow overflow errors (a type of arithmetic error).\n", "- Put limits on what is allow for exponentiation.\n", "- Allow only two nested unary operations to avoid infinite expressions.\n", "\n", "I'll define the function `do` to do an arithmetic computation, catch any errors, and try to correct round-off errors. The idea is that since my expressions start with integers, any result that is very close to an integer is probably actually that integer. So I'll correct `(49*(1/49))` to be `1.0`. Of course, an expression like `(1+(10^-99))` is also very close to `1.0`, but it should not be rounded off. Instead, I'll try to avoid such expressions by silently dropping any value that is outside the range of 10-10 to 1010 (except of course I will accept an exact 0)." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "from operator import add, sub, mul, truediv\n", "from math import sqrt, factorial\n", "\n", "def do(op, *args): \n", " \"Return op(*args), trying to correct for roundoff error, or `None` on Error or too big/small.\"\n", " try:\n", " val = op(*args)\n", " return (val if val == 0 else\n", " None if isinstance(val, complex) else\n", " None if not (1e-10 < abs(val) < 1e10) else\n", " round(val) if abs(val - round(val)) < 1e-12 else \n", " val)\n", " except (ArithmeticError, ValueError):\n", " return None" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "assert do(truediv, 12, 4) == 3 and do(truediv, 12, 0) == None\n", "assert do(mul, 49, do(truediv, 1, 49)) == 1\n", "assert do(pow, 10, -99) == None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I'll take this opportunity to refactor `expressions` to have these subfunctions:\n", "- `digit_expressions(result, numbers)`: returns a dict like {0.44: '.44', 4.4: '4.4', 44.0: '44'}\n", "- `add_unary_expressions(result)`: add expressions like `√44` and `4!` to `result` and return `result`. \n", "- `add_binary_expressions(result, numbers)`: add expressions like `4+√44` and `4^4` to `result` and return `result`." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [], "source": [ "@lru_cache(None)\n", "def expressions(numbers: tuple) -> dict:\n", " \"Return {value: expr} for all expressions that can be made from numbers.\"\n", " return add_unary_expressions(add_binary_expressions(digit_expressions(numbers), numbers))\n", "\n", "def digit_expressions(digits: tuple) -> dict:\n", " \"All ways of making numbers from these digits (in order), and a decimal point.\"\n", " D = ''.join(map(str, digits))\n", " exps = [(D[:i] + '.' + D[i:]).rstrip('.')\n", " for i in range(len(D) + 1)\n", " if not (D.startswith('0') and i > 1)]\n", " return {float(exp): exp for exp in exps}\n", " \n", "def add_binary_expressions(result: dict, numbers: tuple) -> dict:\n", " \"Add binary expressions by splitting numbers and combining with an op.\"\n", " for (Lnums, Rnums) in splits(numbers):\n", " for (L, R) in product(expressions(Lnums), expressions(Rnums)):\n", " Lexp = '(' + expressions(Lnums)[L]\n", " Rexp = expressions(Rnums)[R] + ')'\n", " assign(result, do(truediv, L, R), Lexp + '/' + Rexp)\n", " assign(result, do(mul, L, R), Lexp + '*' + Rexp)\n", " assign(result, do(add, L, R), Lexp + '+' + Rexp)\n", " assign(result, do(sub, L, R), Lexp + '-' + Rexp)\n", " if -10 <= R <= 10 and (L > 0 or int(R) == R):\n", " assign(result, do(pow, L, R), Lexp + '^' + Rexp)\n", " return result\n", " \n", "def add_unary_expressions(result: dict, nesting_level=2) -> dict:\n", " \"Add unary expressions: -v, √v and v! to result\"\n", " for _ in range(nesting_level):\n", " for v in tuple(result):\n", " exp = result[v]\n", " if -v not in result:\n", " assign(result, -v, '-' + exp)\n", " if 0 < v <= 100 and 120 * v == round(120 * v): \n", " assign(result, sqrt(v), '√' + exp)\n", " if 3 <= v <= 6 and v == int(v):\n", " assign(result, factorial(v), exp + '!')\n", " return result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The function `assign` will silently drop expressions whose value is `None`, and if two expressions have the same value, `assign` keeps the one with the lower \"weight\"—a measure of the characters in the expression. This shows simpler expressions in favor of complex ones: for four 4s, the entry for `0` will be `44-44`, not something like `-√((-√4*--4)*(-√4--√4))`." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "def assign(result: dict, value: float, exp: str): \n", " \"Assign result[value] = exp, unless we already have a lighter exp or value is None.\"\n", " if (value is not None \n", " and (value not in result or weight(exp) < weight(result[value]))): \n", " result[value] = exp\n", " \n", "PENALTIES = defaultdict(int, {'√':7, '!':5, '.':1, '^':3, '/':1, '-':1})\n", " \n", "def weight(exp: str) -> int: return len(exp) + sum(PENALTIES[c] for c in exp)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "I'll define a function to print a table of consecutive integers (starting at 0) that can be made by a sequence of numbers:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [], "source": [ "def show(numbers: tuple, upto=10000, clear=True):\n", " \"\"\"Print expressions for integers from 0 up to limit or the first unmakeable integer.\"\"\"\n", " if clear: expressions.cache_clear() # To free up memory\n", " print('{:,} entries for {}\\n'.format(len(expressions(numbers)), numbers))\n", " for i in range(upto + 1): \n", " if i not in expressions(numbers):\n", " return i\n", " print('{:4} = {}'.format(i, unbracket(expressions(numbers)[i])))\n", " \n", "def unbracket(exp: str) -> str:\n", " \"Strip outer parens from exp, if they are there\"\n", " return (exp[1:-1] if exp.startswith('(') and exp.endswith(')') else exp)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "705,959 entries for (4, 4, 4, 4)\n", "\n", " 0 = 44-44\n", " 1 = 44/44\n", " 2 = 4*(4/(4+4))\n", " 3 = (4+(4+4))/4\n", " 4 = 4+(4*(4-4))\n", " 5 = (4+(4*4))/4\n", " 6 = 4+((4+4)/4)\n", " 7 = (44/4)-4\n", " 8 = 4+(4*(4/4))\n", " 9 = 4+(4+(4/4))\n", " 10 = 44/4.4\n", " 11 = (4/4)+(4/.4)\n", " 12 = (4+44)/4\n", " 13 = 4!-(44/4)\n", " 14 = (4+(.4*4))/.4\n", " 15 = 4+(44/4)\n", " 16 = .4*(44-4)\n", " 17 = (4/4)+(4*4)\n", " 18 = .4+(.4*44)\n", " 19 = (4+(4-.4))/.4\n", " 20 = 4*(4+(4/4))\n", " 21 = (4+4.4)/.4\n", " 22 = √4*(44/4)\n", " 23 = ((4*4!)-4)/4\n", " 24 = 4+(4+(4*4))\n", " 25 = (4+(4*4!))/4\n", " 26 = (4/.4)+(4*4)\n", " 27 = 4+(4!-(4/4))\n", " 28 = 44-(4*4)\n", " 29 = 4+(4/(.4*.4))\n", " 30 = (4+(4+4))/.4\n", " 31 = 4!+((4+4!)/4)\n", " 32 = (4*4)+(4*4)\n", " 33 = 4!+((4-.4)/.4)\n", " 34 = 44-(4/.4)\n", " 35 = 4!+(44/4)\n", " 36 = 44-(4+4)\n", " 37 = ((.4+4!)/.4)-4!\n", " 38 = 44-(4!/4)\n", " 39 = ((4*4)-.4)/.4\n", " 40 = 44-√(4*4)\n", " 41 = (.4+(4*4))/.4\n", " 42 = √4+(44-4)\n", " 43 = 44-(4/4)\n", " 44 = 4+(44-4)\n", " 45 = 44+(4/4)\n", " 46 = 4+(44-√4)\n", " 47 = 4!+(4!-(4/4))\n", " 48 = 4*(4+(4+4))\n", " 49 = 44+(√4/.4)\n", " 50 = (4+(4*4))/.4\n", " 51 = (.4+(4!-4))/.4\n", " 52 = 4+(4+44)\n", " 53 = 4!+(4!+(√4/.4))\n", " 54 = 44+(4/.4)\n", " 55 = 44/(.4+.4)\n", " 56 = 4*(4+(4/.4))\n", " 57 = ((.4+4!)/.4)-4\n", " 58 = ((4^4)-4!)/4\n", " 59 = (4!/.4)-(4/4)\n", " 60 = 44+(4*4)\n", " 61 = (4/4)+(4!/.4)\n", " 62 = (4*(4*4))-√4\n", " 63 = ((4^4)-4)/4\n", " 64 = (4+4)*(4+4)\n", " 65 = (4+(4^4))/4\n", " 66 = √4+(4*(4*4))\n", " 67 = √4+((√4+4!)/.4)\n", " 68 = 4+(4*(4*4))\n", " 69 = (4+(4!-.4))/.4\n", " 70 = (4!+(4^4))/4\n", " 71 = (4!+4.4)/.4\n", " 72 = 4+(4!+44)\n", "CPU times: user 9.72 s, sys: 88.3 ms, total: 9.81 s\n", "Wall time: 9.84 s\n" ] }, { "data": { "text/plain": [ "73" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%time show((4, 4, 4, 4))" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "We can also solve the \"2016 with four fours\" puzzle:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "data": { "text/plain": [ "'(4!*(4!+(4!/.4)))'" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expressions((4, 4, 4, 4))[2016]" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "In a [separate video](https://www.youtube.com/embed/Noo4lN-vSvw), Alex Bellos shows how to form **every** integer from 0 to infinity using four 4s, if the square root and `log` functions are allowed. The solution comes from Paul Dirac (although [Dirac originally developed it](https://nebusresearch.wordpress.com/2014/04/18/how-dirac-made-every-number/) for the \"four 2s\" problem).\n", "\n", "Donald Knuth has [conjectured](https://www.tandfonline.com/doi/abs/10.1080/0025570X.1964.11975546) that with floor, square root and factorial, you can make any positive integer with just **one** 4.\n", "\n", "Below are some popular variants:\n", "\n", "# Four 2s" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "109,747 entries for (2, 2, 2, 2)\n", "\n", " 0 = 22-22\n", " 1 = 22/22\n", " 2 = 2+(2*(2-2))\n", " 3 = (2+(2*2))/2\n", " 4 = .2*(22-2)\n", " 5 = 2+(2+(2/2))\n", " 6 = 2*(2+(2/2))\n", " 7 = 2+(2/(.2*2))\n", " 8 = 2+(2+(2*2))\n", " 9 = (22/2)-2\n", " 10 = 22/2.2\n", " 11 = (2/2)+(2/.2)\n", " 12 = (2+22)/2\n", " 13 = 2+(22/2)\n", " 14 = 2+(2+(2/.2))\n", " 15 = (2+(2/2))/.2\n", " 16 = 2*(2*(2*2))\n", " 17 = 22-(√.2^-2)\n", " 18 = 22-(2*2)\n", " 19 = (2+(2-.2))/.2\n", " 20 = 22-√(2*2)\n", " 21 = 22-(2/2)\n", " 22 = 2+(22-2)\n", " 23 = 22+(2/2)\n", " 24 = 2*(2+(2/.2))\n", " 25 = 2/(.2*(.2*2))\n", " 26 = 2+(2+22)\n", " 27 = 22+(√.2^-2)\n", " 28 = 2+(2+(2*2)!)\n", " 29 = 2+(2+(.2^-2))\n", " 30 = (2+(2*2))/.2\n" ] }, { "data": { "text/plain": [ "31" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "show((2, 2, 2, 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Four 9s" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "539,849 entries for (9, 9, 9, 9)\n", "\n", " 0 = 99-99\n", " 1 = 99/99\n", " 2 = (99/9)-9\n", " 3 = (9+(9+9))/9\n", " 4 = 9-(9/(.9+.9))\n", " 5 = √9+((9+9)/9)\n", " 6 = ((9+(9+9))/9)!\n", " 7 = 9-((9+9)/9)\n", " 8 = ((9*9)-9)/9\n", " 9 = 9+(9*(9-9))\n", " 10 = 99/9.9\n", " 11 = 9+((9+9)/9)\n", " 12 = (9+99)/9\n", " 13 = 9+(√9+(9/9))\n", " 14 = 9+(9/(.9+.9))\n", " 15 = 9+((9+9)/√9)\n", " 16 = 9+((9/.9)-√9)\n", " 17 = 9+(9-(9/9))\n", " 18 = 99-(9*9)\n", " 19 = 9+(9+(9/9))\n", " 20 = 9+(99/9)\n", " 21 = (9+9.9)/.9\n", " 22 = 9+(√9+(9/.9))\n", " 23 = √9+((9+9)/.9)\n", " 24 = (99/√9)-9\n", " 25 = 9+(√9!+(9/.9))\n", " 26 = (9*√9)-(9/9)\n", " 27 = 9+(9+√(9*9))\n", " 28 = 9+(9+(9/.9))\n", " 29 = 9+((9+9)/.9)\n", " 30 = (9+(9+9))/.9\n", " 31 = (.9+(9*√9))/.9\n", " 32 = (99-√9)/√9\n", " 33 = √9*(99/9)\n", " 34 = (√9+99)/√9\n", " 35 = (√9!+99)/√9\n", " 36 = 9+(9+(9+9))\n", " 37 = (9/.9)+(9*√9)\n", " 38 = √9!*(√9+(√9/.9))\n", " 39 = 9+(9*(√9/.9))\n", " 40 = (9+(9*√9))/.9\n", " 41 = (.9+(√9!*√9!))/.9\n", " 42 = 9+(99/√9)\n", " 43 = √9+(√9!*(√9!/.9))\n", " 44 = (9*√9!)-(9/.9)\n", " 45 = 9*(9/(.9+.9))\n", " 46 = (9/.9)+(√9!*√9!)\n", " 47 = √9*(9+(√9!/.9))\n", " 48 = √9!*(9-(9/9))\n", " 49 = 9+(√9!*(√9!/.9))\n", " 50 = ((9*√9!)-9)/.9\n", " 51 = 9*(9-(√9/.9))\n", " 52 = √9!*(9-(√9/9))\n", " 53 = (9*√9!)-(9/9)\n", " 54 = 9*((9+9)/√9)\n", " 55 = 99/(.9+.9)\n", " 56 = √9!*(9+(√9/9))\n", " 57 = √9*(9+(9/.9))\n", " 58 = √9!*(9+(√9!/9))\n", " 59 = ((9*√9!)-.9)/.9\n", " 60 = √9*((9+9)/.9)\n", " 61 = (.9+(9*√9!))/.9\n" ] }, { "data": { "text/plain": [ "62" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "show((9, 9, 9, 9))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Four 5s" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "202,937 entries for (5, 5, 5, 5)\n", "\n", " 0 = 55-55\n", " 1 = 55/55\n", " 2 = (5/5)+(5/5)\n", " 3 = (5+(5+5))/5\n", " 4 = ((5*5)-5)/5\n", " 5 = 5+(5*(5-5))\n", " 6 = (55/5)-5\n", " 7 = 5+((5+5)/5)\n", " 8 = 5.5+(.5*5)\n", " 9 = 5+(5-(5/5))\n", " 10 = 55/5.5\n", " 11 = 5.5+5.5\n", " 12 = (5+55)/5\n", " 13 = .5+(.5*(5*5))\n", " 14 = 5+((5-.5)/.5)\n", " 15 = (5*5)-(5+5)\n", " 16 = 5+(55/5)\n", " 17 = 5+(5!/(5+5))\n", " 18 = (5-.5)/(.5*.5)\n", " 19 = (5+(5-.5))/.5\n", " 20 = 5+(5+(5+5))\n", " 21 = (5+5.5)/.5\n", " 22 = 55/(.5*5)\n", " 23 = .5+(5*(5-.5))\n", " 24 = (5*5)-(5/5)\n", " 25 = .5*(55-5)\n", " 26 = (5/5)+(5*5)\n", " 27 = (.5*55)-.5\n", " 28 = .5+(.5*55)\n", " 29 = (5!+(5*5))/5\n", " 30 = 55-(5*5)\n", " 31 = 55-(5!/5)\n", " 32 = ((5+5)/5)^5\n", " 33 = .5*(5!*.55)\n", " 34 = 5+(5+(5!/5))\n", " 35 = 5+(5+(5*5))\n", " 36 = (5!+(.5*5!))/5\n", " 37 = (.5^-5)+√(5*5)\n", " 38 = ((5!/5)-5)/.5\n" ] }, { "data": { "text/plain": [ "39" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "show((5, 5, 5, 5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Five 5s " ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "7,624,387 entries for (5, 5, 5, 5, 5)\n", "\n", " 0 = 5*(55-55)\n", "CPU times: user 2min 19s, sys: 1.04 s, total: 2min 20s\n", "Wall time: 2min 21s\n" ] } ], "source": [ "%time show((5, 5, 5, 5, 5), False)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "\n", "# Countdown to 2018\n", "\n", "One more thing: On January 1 2018, [Michael Littman](http://cs.brown.edu/~mlittman/) posted this:\n", "\n", "> 2+0+1×8, 2+0-1+8, (2+0-1)×8, |2-0-1-8|, -2-0+1×8, -(2+0+1-8), sqrt(|2+0-18|), 2+0+1^8, 20-18, 2^(0×18), 2×0×1×8... Happy New Year!\n", "\n", "Can we replicate that countdown? For 2018 and for following years?" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10,303 entries for (2, 0, 1, 8)\n", "\n", " 0 = 2*(0*18)\n", " 1 = 2^(0*18)\n", " 2 = 20-18\n", " 3 = 2.0+(1^8)\n", " 4 = 20*(1-.8)\n", " 5 = -2+((0-1)+8)\n", " 6 = (2*(0-1))+8\n", " 7 = ((2*0)-1)+8\n", " 8 = (2*0)+(1*8)\n", " 9 = (2*0)+(1+8)\n", " 10 = 2.0+(1*8)\n" ] } ], "source": [ "show((2,0,1,8), 10)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "14,957 entries for (2, 0, 1, 9)\n", "\n", " 0 = 2*(0*19)\n", " 1 = 20-19\n", " 2 = 2+(0*19)\n", " 3 = 2+(0.1+.9)\n", " 4 = (2*0)+(1+√9)\n", " 5 = 20/(1+√9)\n", " 6 = -2+((0-1)+9)\n", " 7 = (2*(0-1))+9\n", " 8 = ((2*0)-1)+9\n", " 9 = (2*0)+(1*9)\n", " 10 = 20-(1+9)\n" ] } ], "source": [ "show((2,0,1,9), 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we run into a problem. For the year 2020, the two zeroes don't give us much to work with, and we can't make all the integers in the countdown. But I can turn a 0 into a 1 with `cos(0)`; maybe that will be enough? Let's try it. I'll modify `add_unary_expressions` to assign values for `sin` and `cos`:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "from math import sin, cos\n", "\n", "def add_unary_expressions(result: dict, nesting_level=2) -> dict:\n", " \"Add unary expressions: -v, √v and v! to result dict.\"\n", " for _ in range(nesting_level):\n", " for v in tuple(result):\n", " exp = result[v]\n", " if -v not in result:\n", " assign(result, -v, '-' + exp)\n", " if 0 < v <= 100 and 120 * v == round(120 * v): \n", " assign(result, sqrt(v), '√' + exp)\n", " if 3 <= v <= 6 and v == int(v):\n", " assign(result, factorial(v), exp + '!')\n", " if abs(v) in (0, 45, 90, 180):\n", " assign(result, sin(v), 'sin(' + exp + ')')\n", " assign(result, cos(v), 'cos(' + exp + ')')\n", " return result\n", "\n", "PENALTIES.update(s=1, i=1, n=1, c=1, o=1)" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "8,195 entries for (2, 0, 2, 0)\n", "\n", " 0 = 202*0\n", " 1 = 20/20\n", " 2 = 2+(0*20)\n", " 3 = 2+(.02^0)\n", " 4 = .20*20\n", " 5 = (2^0)/.20\n", " 6 = 2*(cos(0)+2.0)\n", " 7 = 2+(cos(0)/.20)\n", " 8 = 2^(cos(0)+2.0)\n", " 9 = (20/2)-cos(0)\n", " 10 = 2/0.20\n" ] } ], "source": [ "show((2, 0, 2, 0), 10, clear=True) # Clear the cache so we get new results for (2, 0) etc." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "38,875 entries for (2, 0, 2, 1)\n", "\n", " 0 = 2*(0*21)\n", " 1 = -20+21\n", " 2 = 2+(0*21)\n", " 3 = 2+((0*2)+1)\n", " 4 = 2.0*(2*1)\n", " 5 = 2.0+(2+1)\n", " 6 = 2.0*(2+1)\n", " 7 = 2+(0.2^-1)\n", " 8 = 2.0^(2+1)\n", " 9 = (20/2)-1\n", " 10 = 20/(2*1)\n" ] } ], "source": [ "show((2, 0, 2, 1), 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# What's Next?\n", "\n", "One exercise would be adding even more operators, such as:\n", "\n", "- **Floor and Ceiling**: `⌊5.5⌋` = 5, `⌈5.5⌉` = 6\n", "- **Nth root**: `3√8` = 2\n", "- **Percent**: `5%` = 5/100\n", "- **Repeating decimal**: `.4...` = .44444444... = 4/9\n", "- **Double factorial**: `9!!` = 9 × 7 × 5 × 3 × 1 = 945\n", "- **Gamma function**: `Γ(n)` = (n − 1)!\n", "- **Prime counting function**: `π(n)` = number of primes ≲ n; `π(5)` = 3\n", "- **Transcendental functions**: besides `sin` and `cos`, there's `log`, `tan`, `arcsin`, ...\n", "\n", "In the [xkcd forum](http://forums.xkcd.com/viewtopic.php?f=14&t=116813&start=280) they got up to 298 for five fives, using the double factorial and π functions. What would you like to do?" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" } }, "nbformat": 4, "nbformat_minor": 1 }