From 15c89493bee4f8dec871eca9bb42bf7894fcec0b Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Thu, 7 Mar 2019 15:55:47 -0800 Subject: [PATCH] Add files via upload --- ipynb/Countdown.ipynb | 1343 ++++++++++++++++++++--------------------- 1 file changed, 642 insertions(+), 701 deletions(-) diff --git a/ipynb/Countdown.ipynb b/ipynb/Countdown.ipynb index 284a596..f2741fb 100644 --- a/ipynb/Countdown.ipynb +++ b/ipynb/Countdown.ipynb @@ -22,19 +22,15 @@ "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", + "> 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", - "> `(10 + 9) * (8` ...\n", - "\n", - "> or\n", - "\n", - "> `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. We'll start with a simpler version of the puzzle." + "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. " ] }, { @@ -49,12 +45,25 @@ "source": [ "# Four Operations, No Brackets\n", "\n", - "Suppose for the moment we are not allowed to use brackets. Then 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:" + "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, @@ -69,37 +78,14 @@ "('+', '+', '+', '+', '+', '+', '+', '+', '+')" ] }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import itertools\n", - "from functools import lru_cache # Used later\n", - "\n", - "ops = next(itertools.product(('+', '-', '*', '/'), repeat=9))\n", - "ops" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'10+9+8+7+6+5+4+3+2+1'" - ] - }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "'10{}9{}8{}7{}6{}5{}4{}3{}2{}1'.format(*ops)" + "ops = next(product(('+', '-', '*', '/'), repeat=9))\n", + "ops" ] }, { @@ -110,7 +96,7 @@ { "data": { "text/plain": [ - "55" + "'10+9+8+7+6+5+4+3+2+1'" ] }, "execution_count": 3, @@ -118,6 +104,26 @@ "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(_)" ] @@ -126,12 +132,12 @@ "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 `countdown_no_brackets` to put the pieces together:" + "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": 4, + "execution_count": 5, "metadata": { "button": false, "new_sheet": false, @@ -142,22 +148,22 @@ "outputs": [], "source": [ "def evaluate(exp):\n", - " \"eval exp, or return None if there is an arithmetic error.\"\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 countdown_no_brackets(target, operators=('+', '-', '*', '/')):\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 itertools.product(operators, repeat=9))\n", + " for ops in product(operators, repeat=9))\n", " return [exp for exp in exps if evaluate(exp) == target]" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -166,13 +172,13 @@ "[]" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "countdown_no_brackets(2016)" + "simple_countdown(2016)" ] }, { @@ -185,12 +191,12 @@ } }, "source": [ - "Too bad; we did all that work and didn't find a solution. What years *can* we find solutions for? I'll modify `countdown_no_brackets` 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." + "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": 6, + "execution_count": 7, "metadata": { "button": false, "new_sheet": false, @@ -202,31 +208,31 @@ { "data": { "text/plain": [ - "{1979: '10*9*8+7*6*5*4*3/2-1',\n", + "{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", - " 1981: '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", + " 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", - " 2019: '10*9*8*7/6/5*4*3+2+1'}" + " 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": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def countdown_no_brackets(targets, operators=('+', '-', '*', '/'), \n", + "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 itertools.product(operators, repeat=fmt.count('{}')))\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", - "countdown_no_brackets(range(1900, 2100))" + "simple_countdown(range(1900, 2100))" ] }, { @@ -239,7 +245,7 @@ } }, "source": [ - "Interesting: in the 20th and 21st centuries, there are only two \"golden eras\" where the countdown equation works: the three year period centered on 1980, and the seven year period that is centered on 2016, but omits 2016. " + "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. " ] }, { @@ -254,7 +260,10 @@ "source": [ "# Four Operations, With Brackets\n", "\n", - "What is the problem I want to solve? I want to know if I can create an expression whose value is 2016. But to get there I'll solve a more general problem: given a sequence of numbers, like `(10, 9, 8)`, what expressions can I make with them?\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", @@ -270,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": { "button": false, "new_sheet": false, @@ -290,7 +299,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": { "button": false, "new_sheet": false, @@ -313,7 +322,7 @@ " ((10, 9, 8, 7, 6, 5, 4, 3, 2), (1,))]" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -337,11 +346,11 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "@lru_cache()\n", + "@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", @@ -349,17 +358,15 @@ " else: \n", " result = {}\n", " for (Lnums, Rnums) in splits(numbers):\n", - " for (L, R) in pairs(expressions(Lnums), expressions(Rnums)):\n", + " for (L, R) in product(expressions(Lnums), expressions(Rnums)):\n", " Lexp = '(' + expressions(Lnums)[L]\n", " Rexp = expressions(Rnums)[R] + ')'\n", - " if R != 0: \n", - " result[L / R] = Lexp + '/' + Rexp\n", " result[L * R] = Lexp + '*' + Rexp\n", " result[L - R] = Lexp + '-' + Rexp\n", " result[L + R] = Lexp + '+' + Rexp\n", - " return result\n", - " \n", - "def pairs(left, right): return ((L, R) for L in left for R in right)" + " if R != 0: \n", + " result[L / R] = Lexp + '/' + Rexp\n", + " return result" ] }, { @@ -392,7 +399,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -401,39 +408,13 @@ "{10: '10'}" ] }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "expressions((10,))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "button": false, - "new_sheet": false, - "run_control": { - "read_only": false - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{1: '(9-8)', 1.125: '(9/8)', 17: '(9+8)', 72: '(9*8)'}" - ] - }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "expressions((9, 8))" + "expressions((10,))" ] }, { @@ -450,28 +431,7 @@ { "data": { "text/plain": [ - "{-62: '(10-(9*8))',\n", - " -7: '((10-9)-8)',\n", - " -6.888888888888889: '((10/9)-8)',\n", - " 0.125: '((10-9)/8)',\n", - " 0.1388888888888889: '((10/9)/8)',\n", - " 0.5882352941176471: '(10/(9+8))',\n", - " 2.375: '((10+9)/8)',\n", - " 8: '((10-9)*8)',\n", - " 8.875: '(10-(9/8))',\n", - " 8.88888888888889: '((10/9)*8)',\n", - " 9: '((10-9)+8)',\n", - " 9.11111111111111: '((10/9)+8)',\n", - " 10.0: '(10*(9-8))',\n", - " 11: '((10+9)-8)',\n", - " 11.125: '(10+(9/8))',\n", - " 11.25: '((10*9)/8)',\n", - " 27: '((10+9)+8)',\n", - " 82: '((10*9)-8)',\n", - " 98: '((10*9)+8)',\n", - " 152: '((10+9)*8)',\n", - " 170: '(10*(9+8))',\n", - " 720: '((10*9)*8)'}" + "{72: '(9*8)', 1: '(9-8)', 17: '(9+8)', 1.125: '(9/8)'}" ] }, "execution_count": 12, @@ -479,6 +439,53 @@ "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))" ] @@ -500,7 +507,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": { "button": false, "new_sheet": false, @@ -513,17 +520,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 28.3 s, sys: 728 ms, total: 29 s\n", - "Wall time: 29.2 s\n" + "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)'" + "'(((((((((10*9)+8)*7)-6)-5)-4)*3)+2)+1)'" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -547,36 +554,282 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{2010: '((((10*((9+((8+7)*6))+(5/4)))+3)*2)-1)',\n", + "{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", + " 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)'}" + " 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": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "{y: expressions(c10)[y] for y in range(2010, 2025)}" + "{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." ] }, { @@ -588,17 +841,17 @@ "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", + ">\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 is \"most famous\" because it goes back to a [1914 publication by W. Ball](https://archive.org/details/mathematicalrecr00ball). The \"0 to 9\" solution is easy:" + "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": 15, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -608,15 +861,15 @@ " 1: '(((4/4)-4)+4)',\n", " 2: '((4/(4+4))*4)',\n", " 3: '(((4+4)+4)/4)',\n", - " 4: '(((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", + " 8: '(((4+4)/4)*4)',\n", " 9: '(((4/4)+4)+4)'}" ] }, - "execution_count": 15, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -625,6 +878,13 @@ "{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": {}, @@ -646,34 +906,38 @@ "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, but Python gives a `ValueError` for `sqrt(-1)`.\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:\n", - "- Arbitrarily limit expressions to two nested unary operations to avoid infinite expressions.\n", - "- Use floats for all computation (to avoid out of memory errors), and accept that there are some approximations. \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": 16, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "from operator import add, sub, mul, truediv\n", "from math import sqrt, factorial\n", - "from collections import defaultdict, Counter\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", @@ -683,7 +947,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -698,13 +962,13 @@ "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)`: adds expressions like `√44` and `4!` and return `result`. \n", - "- `add_binary_expressions(result, numbers)`: adds expressions like `4+√44` and `4^4` and return `result`." + "- `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": 18, + "execution_count": 25, "metadata": { "button": false, "new_sheet": false, @@ -714,23 +978,23 @@ }, "outputs": [], "source": [ - "@lru_cache(1000)\n", - "def expressions(numbers):\n", + "@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(numbers) -> dict:\n", - " \"All ways of making numbers from these numbers, in order, and a decimal point.\"\n", - " D = ''.join(map(str, numbers))\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, numbers) -> dict:\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 pairs(expressions(Lnums), expressions(Rnums)):\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", @@ -741,8 +1005,8 @@ " assign(result, do(pow, L, R), Lexp + '^' + Rexp)\n", " return result\n", " \n", - "def add_unary_expressions(result, nesting_level=2) -> dict:\n", - " \"Add unary expressions: -v, √v and v!\"\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", @@ -764,19 +1028,19 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ - "def assign(result, value, exp): \n", - " \"Assign result[value] = exp, unless we already have a lighter exp, or value is too extreme.\"\n", + "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) -> int: return len(exp) + sum(PENALTIES[c] for c in exp)" + "def weight(exp: str) -> int: return len(exp) + sum(PENALTIES[c] for c in exp)" ] }, { @@ -789,12 +1053,12 @@ } }, "source": [ - "I'll define a function to create a table of consecutive integers (starting at 0) that can be made by a sequence of numbers:" + "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": 20, + "execution_count": 27, "metadata": { "button": false, "new_sheet": false, @@ -804,23 +1068,23 @@ }, "outputs": [], "source": [ - "def show(numbers, clear=True):\n", - " \"Print a table of expressions for all consecutive integers i from 0 up to the first unmakeable integer.\"\n", + "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 itertools.count(0): # All integers from 0\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):\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": 21, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -837,30 +1101,30 @@ " 5 = (4+(4*4))/4\n", " 6 = 4+((4+4)/4)\n", " 7 = (44/4)-4\n", - " 8 = 4+(4+(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", + " 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", + " 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", + " 23 = ((4*4!)-4)/4\n", " 24 = 4+(4+(4*4))\n", - " 25 = (4+(4!*4))/4\n", + " 25 = (4+(4*4!))/4\n", " 26 = (4/.4)+(4*4)\n", - " 27 = 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", + " 31 = 4!+((4+4!)/4)\n", " 32 = (4*4)+(4*4)\n", " 33 = 4!+((4-.4)/.4)\n", " 34 = 44-(4/.4)\n", @@ -874,22 +1138,22 @@ " 42 = √4+(44-4)\n", " 43 = 44-(4/4)\n", " 44 = 4+(44-4)\n", - " 45 = (4/4)+44\n", + " 45 = 44+(4/4)\n", " 46 = 4+(44-√4)\n", " 47 = 4!+(4!-(4/4))\n", " 48 = 4*(4+(4+4))\n", - " 49 = (√4/.4)+44\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 = (4/.4)+44\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 = (4*4)+44\n", + " 60 = 44+(4*4)\n", " 61 = (4/4)+(4!/.4)\n", " 62 = (4*(4*4))-√4\n", " 63 = ((4^4)-4)/4\n", @@ -898,12 +1162,12 @@ " 66 = √4+(4*(4*4))\n", " 67 = √4+((√4+4!)/.4)\n", " 68 = 4+(4*(4*4))\n", - " 69 = (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 11.2 s, sys: 60.4 ms, total: 11.3 s\n", - "Wall time: 11.3 s\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" ] }, { @@ -912,7 +1176,7 @@ "73" ] }, - "execution_count": 21, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -936,7 +1200,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 29, "metadata": { "button": false, "new_sheet": false, @@ -951,7 +1215,7 @@ "'(4!*(4!+(4!/.4)))'" ] }, - "execution_count": 22, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -981,7 +1245,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -1010,19 +1274,17 @@ " 17 = 22-(√.2^-2)\n", " 18 = 22-(2*2)\n", " 19 = (2+(2-.2))/.2\n", - " 20 = (2/.2)+(2/.2)\n", + " 20 = 22-√(2*2)\n", " 21 = 22-(2/2)\n", " 22 = 2+(22-2)\n", - " 23 = (2/2)+22\n", + " 23 = 22+(2/2)\n", " 24 = 2*(2+(2/.2))\n", " 25 = 2/(.2*(.2*2))\n", " 26 = 2+(2+22)\n", - " 27 = (√.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", - "CPU times: user 1.78 s, sys: 16.9 ms, total: 1.8 s\n", - "Wall time: 1.8 s\n" + " 30 = (2+(2*2))/.2\n" ] }, { @@ -1031,13 +1293,13 @@ "31" ] }, - "execution_count": 23, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "%time show((2, 2, 2, 2))" + "show((2, 2, 2, 2))" ] }, { @@ -1049,7 +1311,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 31, "metadata": {}, "outputs": [ { @@ -1071,7 +1333,7 @@ " 10 = 99/9.9\n", " 11 = 9+((9+9)/9)\n", " 12 = (9+99)/9\n", - " 13 = √9+(9+(9/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", @@ -1080,48 +1342,46 @@ " 19 = 9+(9+(9/9))\n", " 20 = 9+(99/9)\n", " 21 = (9+9.9)/.9\n", - " 22 = √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", + " 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", + " 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", + " 37 = (9/.9)+(9*√9)\n", " 38 = √9!*(√9+(√9/.9))\n", - " 39 = 9+(√9*(9/.9))\n", - " 40 = (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", + " 44 = (9*√9!)-(9/.9)\n", " 45 = 9*(9/(.9+.9))\n", - " 46 = (√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", + " 50 = ((9*√9!)-9)/.9\n", " 51 = 9*(9-(√9/.9))\n", " 52 = √9!*(9-(√9/9))\n", - " 53 = (√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", + " 59 = ((9*√9!)-.9)/.9\n", " 60 = √9*((9+9)/.9)\n", - " 61 = (.9+(√9!*9))/.9\n", - "CPU times: user 7.42 s, sys: 40.3 ms, total: 7.46 s\n", - "Wall time: 7.47 s\n" + " 61 = (.9+(9*√9!))/.9\n" ] }, { @@ -1130,13 +1390,13 @@ "62" ] }, - "execution_count": 24, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "%time show((9, 9, 9, 9))" + "show((9, 9, 9, 9))" ] }, { @@ -1148,7 +1408,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 32, "metadata": { "button": false, "new_sheet": false, @@ -1201,9 +1461,7 @@ " 35 = 5+(5+(5*5))\n", " 36 = (5!+(.5*5!))/5\n", " 37 = (.5^-5)+√(5*5)\n", - " 38 = ((5!/5)-5)/.5\n", - "CPU times: user 3.33 s, sys: 17.6 ms, total: 3.35 s\n", - "Wall time: 3.35 s\n" + " 38 = ((5!/5)-5)/.5\n" ] }, { @@ -1212,13 +1470,13 @@ "39" ] }, - "execution_count": 25, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "%time show((5, 5, 5, 5))" + "show((5, 5, 5, 5))" ] }, { @@ -1230,8 +1488,10 @@ }, { "cell_type": "code", - "execution_count": 26, - "metadata": {}, + "execution_count": 33, + "metadata": { + "scrolled": false + }, "outputs": [ { "name": "stdout", @@ -1240,441 +1500,15 @@ "7,624,387 entries for (5, 5, 5, 5, 5)\n", "\n", " 0 = 5*(55-55)\n", - " 1 = 5^(55-55)\n", - " 2 = 55/(.5*55)\n", - " 3 = .5*((55/5)-5)\n", - " 4 = 5-(55/55)\n", - " 5 = 5*(55/55)\n", - " 6 = 5+(55/55)\n", - " 7 = ((5+55)/5)-5\n", - " 8 = .5*(5+(55/5))\n", - " 9 = (55-(5+5))/5\n", - " 10 = (55/5)-(5/5)\n", - " 11 = 5*(55/(5*5))\n", - " 12 = (5/5)+(55/5)\n", - " 13 = (5+(5+55))/5\n", - " 14 = (5*5)-(55/5)\n", - " 15 = 5+(55/5.5)\n", - " 16 = 5+(5.5+5.5)\n", - " 17 = 5+((5+55)/5)\n", - " 18 = 5.5+(.5*(5*5))\n", - " 19 = (5*5)-(.5+5.5)\n", - " 20 = 55/(5*.55)\n", - " 21 = 5+(5+(55/5))\n", - " 22 = (55+55)/5\n", - " 23 = (5+(55/.5))/5\n", - " 24 = (5+55)/(.5*5)\n", - " 25 = 55-(5+(5*5))\n", - " 26 = 5*(5+(5/(5*5)))\n", - " 27 = 5+(55/(.5*5))\n", - " 28 = .5*(.5+55.5)\n", - " 29 = 5+((5*5)-(5/5))\n", - " 30 = 5*((55/5)-5)\n", - " 31 = .5+(5.5+(5*5))\n", - " 32 = (5+(55/5))/.5\n", - " 33 = .55*(5+55)\n", - " 34 = (5!+(55-5))/5\n", - " 35 = 5+(55-(5*5))\n", - " 36 = (5*5)+(55/5)\n", - " 37 = 5+(((5+5)/5)^5)\n", - " 38 = .5+(5*(5+(.5*5)))\n", - " 39 = ((5*5)-5.5)/.5\n", - " 40 = 55-(5+(5+5))\n", - " 41 = (5!*.55)-(5*5)\n", - " 42 = (5+5.5)/(.5*.5)\n", - " 43 = (.5^-5)+(55/5)\n", - " 44 = 55-(55/5)\n", - " 45 = (5*5!)-555\n", - " 46 = 55+((.5-5)/.5)\n", - " 47 = (5*(5+(5-.5)))-.5\n", - " 48 = .5+(5*(5+(5-.5)))\n", - " 49 = 55-(.5+5.5)\n", - " 50 = 55.5-5.5\n", - " 51 = .5+(55.5-5)\n", - " 52 = 55-(.5+(.5*5))\n", - " 53 = 55-((5+5)/5)\n", - " 54 = ((5*55)-5)/5\n", - " 55 = .5*(55+55)\n", - " 56 = (5+(5*55))/5\n", - " 57 = 55+((5+5)/5)\n", - " 58 = (.5*5)+55.5\n", - " 59 = 5+(55-(5/5))\n", - " 60 = 5*((5+55)/5)\n", - " 61 = 5.5+55.5\n", - " 62 = (.5*(5*(5*5)))-.5\n", - " 63 = .5+(.5*(5*(5*5)))\n", - " 64 = 55+((5-.5)/.5)\n", - " 65 = .5*(5+(5*(5*5)))\n", - " 66 = 55+(55/5)\n", - " 67 = 55+(5!/(5+5))\n", - " 68 = 5.5+(.5*(5+5!))\n", - " 69 = 5+(.5^(-.5-5.5))\n", - " 70 = 5+(5+(5+55))\n", - " 71 = 55+(.5/(.5^5))\n", - " 72 = (.5+5.5)!/(5+5)\n", - " 73 = (5*5)+(5!/(.5*5))\n", - " 74 = (5!/5)+(55-5)\n", - " 75 = 55+((5*5)-5)\n", - " 76 = 5+(5+(5!*.55))\n", - " 77 = 5+(5!*(.5+(.5/5)))\n", - " 78 = 55+((5!-5)/5)\n", - " 79 = (5!+(5*55))/5\n", - " 80 = 5*(5+(55/5))\n", - " 81 = 5+(.5*(5!+(.5^-5)))\n", - " 82 = (.5^-5)+(55-5)\n", - " 83 = (.5*5!)+((5!-5)/5)\n", - " 84 = 5+((5!/5)+55)\n", - " 85 = 5+(55+(5*5))\n", - " 86 = (55/.5)-(5!/5)\n", - " 87 = (555-5!)/5\n", - " 88 = 5*(.55/(.5^5))\n", - " 89 = (5*5)+((.5^-5)/.5)\n", - " 90 = (55-(5+5))/.5\n", - " 91 = (5*5)+(5!*.55)\n", - " 92 = 5+((.5^-5)+55)\n", - " 93 = .5+(5!-(.5*55))\n", - " 94 = 5!-((5/5)+(5*5))\n", - " 95 = (55/.55)-5\n", - " 96 = 5!+((5/5)-(5*5))\n", - " 97 = 5!+((.5^-5)-55)\n", - " 98 = 5!-(55/(.5*5))\n", - " 99 = (55-5.5)/.5\n", - " 100 = 5*(5+(5+(5+5)))\n", - " 101 = (55.5-5)/.5\n", - " 102 = (.5+(5*5))/(.5*.5)\n", - " 103 = 55+(5!/(.5*5))\n", - " 104 = ((55-.5)/.5)-5\n", - " 105 = 55+(55-5)\n", - " 106 = (555/5)-5\n", - " 107 = 5!-(.5+(.5*(5*5)))\n", - " 108 = (55-(5/5))/.5\n", - " 109 = (55/.5)-(5/5)\n", - " 110 = (555-5)/5\n", - " 111 = (5/5)+(55/.5)\n", - " 112 = (5+555)/5\n", - " 113 = .5+(5*(5*(5-.5)))\n", - " 114 = 5+((55-.5)/.5)\n", - " 115 = 5+(55+55)\n", - " 116 = 5+(555/5)\n", - " 117 = 5!-((5+(5+5))/5)\n", - " 118 = ((5*5!)-(5+5))/5\n", - " 119 = 5!-(55/55)\n", - " 120 = 5+(5+(55/.5))\n", - " 121 = (5+55.5)/.5\n", - " 122 = (5*((5*5)-.5))-.5\n", - " 123 = .5+(5*((5*5)-.5))\n", - " 124 = (5*(5*5))-(5/5)\n", - " 125 = .5*(5*(55-5))\n", - " 126 = (5/5)+(5*(5*5))\n", - " 127 = (5*(.5+(5*5)))-.5\n", - " 128 = .5+(5*(.5+(5*5)))\n", - " 129 = (5!-55.5)/.5\n", - " 130 = (5+(5+55))/.5\n", - " 131 = 5!+(5.5+5.5)\n", - " 132 = 5!+((5+55)/5)\n", - " 133 = (5+(5!*5.5))/5\n", - " 134 = (5!/5)+(55/.5)\n", - " 135 = .5*((5*55)-5)\n", - " 136 = 5+(5!+(55/5))\n", - " 137 = (.5*(5*55))-.5\n", - " 138 = .5+(.5*(5*55))\n", - " 139 = ((.5+5.5)!/5)-5\n", - " 140 = .5*(5+(5*55))\n", - " 141 = 5!+((5+5.5)/.5)\n", - " 142 = 5!+(55/(.5*5))\n", - " 143 = ((.5+5.5)!-5)/5\n", - " 144 = ((55/5)-5)!/5\n", - " 145 = (5*(5+(5*5)))-5\n", - " 146 = 5!+((5/5)+(5*5))\n", - " 147 = 5!+((.5*55)-.5)\n", - " 148 = .5+(5!+(.5*55))\n", - " 149 = (5!/5)+(5*(5*5))\n", - " 150 = 5*(55-(5*5))\n", - " 151 = 5!+(55-(5!/5))\n", - " 152 = 5!+(((5+5)/5)^5)\n", - " 153 = 5!+(.5*(5!*.55))\n", - " 154 = 5+(5+(5!+(5!/5)))\n", - " 155 = 5+(5*(5+(5*5)))\n", - " 156 = (5!+(5!*5.5))/5\n", - " 157 = (.5^-5)+(5*(5*5))\n", - " 158 = ((5!/5)+55)/.5\n", - " 159 = (5/(.5^5))-(5/5)\n", - " 160 = (55+(5*5))/.5\n", - " 161 = (5/5)+(5/(.5^5))\n", - " 162 = (5*(.5+(.5^-5)))-.5\n", - " 163 = .5+(5*(.5+(.5^-5)))\n", - " 164 = 5!+(.5*(5!-(.5^-5)))\n", - " 165 = 55+(55/.5)\n", - " 166 = 5!+((5!-5)/(.5*5))\n", - " 167 = 5!+(((5!/.5)-5)/5)\n", - " 168 = (5+(.5*.5))/(.5^5)\n", - " 169 = 5!+(((5*5)-.5)/.5)\n", - " 170 = 5+(5+(5/(.5^5)))\n", - " 171 = (5.5/(.5^5))-5\n", - "CPU times: user 2min 33s, sys: 747 ms, total: 2min 34s\n", - "Wall time: 2min 34s\n" + "CPU times: user 2min 19s, sys: 1.04 s, total: 2min 20s\n", + "Wall time: 2min 21s\n" ] - }, - { - "data": { - "text/plain": [ - "172" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ "%time show((5, 5, 5, 5, 5), False)" ] }, - { - "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. Another approach is 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": 27, - "metadata": { - "button": false, - "new_sheet": false, - "run_control": { - "read_only": false - } - }, - "outputs": [], - "source": [ - "@lru_cache()\n", - "def counts(numbers):\n", - " \"Return a dict 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": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Counter({0: 1, 1.0: 1, 4: 2})" - ] - }, - "execution_count": 28, - "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": 29, - "metadata": { - "button": false, - "new_sheet": false, - "run_control": { - "read_only": false - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "counts((10, 9, 8))[27]" - ] - }, - { - "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": 30, - "metadata": { - "button": false, - "new_sheet": false, - "run_control": { - "read_only": false - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "30066" - ] - }, - "execution_count": 30, - "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": 31, - "metadata": { - "button": false, - "new_sheet": false, - "run_control": { - "read_only": false - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{2015.999999999997: 15,\n", - " 2015.999999999999: 10,\n", - " 2015.9999999999993: 14,\n", - " 2015.9999999999995: 1930,\n", - " 2015.9999999999998: 5868,\n", - " 2016.0: 30066,\n", - " 2016.0000000000002: 5792,\n", - " 2016.0000000000005: 510,\n", - " 2016.0000000000018: 264,\n", - " 2016.000000000002: 18,\n", - " 2016.0000000000023: 12}" - ] - }, - "execution_count": 31, - "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": 32, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "44499" - ] - }, - "execution_count": 32, - "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": { @@ -1692,36 +1526,7 @@ "\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?" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2.0+(1*8), (2*0)+(1+8), (2*0)+(1*8), ((2*0)-1)+8, (2*(0-1))+8, -2+((0-1)+8), 20*(1-.8), 2.0+(1^8), 20-18, 2^(0*18), 2*(0*18) ... Happy New Year!\n" - ] - } - ], - "source": [ - "def littman_countdown(year):\n", - " \"Return a Littman countdown for the year, given as a tuple of numbers.\"\n", - " return ', '.join(unbracket(expressions(year)[i])\n", - " for i in reversed(range(11)))\n", - "\n", - "print(littman_countdown((2, 0, 1, 8)), '... Happy New Year!')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similar results, with some alternatives for some numbers. Let's get ready for next year:" + "Can we replicate that countdown? For 2018 and for following years?" ] }, { @@ -1733,12 +1538,145 @@ "name": "stdout", "output_type": "stream", "text": [ - "20-(1+9), (2*0)+(1*9), ((2*0)-1)+9, (2*(0-1))+9, -2+((0-1)+9), 20/(1+√9), (2*0)+(1+√9), 2+(0.1+.9), 2+(0*19), 20-19, 2*(0*19) ... Happy New Year!\n" + "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": [ - "print(littman_countdown((2, 0, 1, 9)), '... Happy New Year!')" + "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)" ] }, { @@ -1749,13 +1687,16 @@ "\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", - "- **Transcendental functions**: `log, sin, cos, tan, arcsin, ...` \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", - "What would you like to do?" + "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?" ] } ], @@ -1775,7 +1716,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.3" + "version": "3.7.0" } }, "nbformat": 4,