diff --git a/ipynb/Countdown.ipynb b/ipynb/Countdown.ipynb
index 9c39852..881de93 100644
--- a/ipynb/Countdown.ipynb
+++ b/ipynb/Countdown.ipynb
@@ -11,11 +11,11 @@
}
},
"source": [
- "
Peter Norvig
Jan 2016
revised 2018, 2020, 2021
\n",
+ "Peter Norvig
Jan 2016
revised 2018, 2020, 2021, 2023
\n",
"\n",
- "# Making Numbers: Countdowns, Four 4s, Five 5s, ...\n",
+ "# Number Expression Puzzles: Countdowns, Four 4s, Five 5s, ...\n",
"\n",
- "In this notebook we solve a range of related puzzles that all involve making mathematical expressions by combining numbers and operators in various ways to make target numeric values. First some imports, and then we can look at the first problem."
+ "In this notebook we solve a range of related puzzles that all involve making mathematical expressions by combining numbers and operations in various ways to make target numeric values. First some imports, and then we can look at the first problem."
]
},
{
@@ -28,13 +28,12 @@
"source": [
"from collections import Counter, defaultdict, namedtuple\n",
"from fractions import Fraction\n",
- "from functools import lru_cache\n",
"from itertools import product, permutations\n",
"from math import sqrt, factorial, floor, ceil\n",
"from operator import add, sub, mul, neg, truediv as div\n",
"from typing import List, Tuple, Dict, Union, Sequence, Collection, Set, Optional\n",
- "import re\n",
"import functools\n",
+ "import re\n",
"\n",
"cache = functools.cache if 'cache' in functools.__dict__ else functools.lru_cache(None)"
]
@@ -81,10 +80,9 @@
"\n",
"I'll define `expressions(numbers)` to return an **expression table**: a dict of `{value: expression}` for all expressions (strings) whose numeric value is a `value` that can be made using all the `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",
+ " expressions((3, 2)) = {1: '(3-2)', 1.5: '(3/2)', 5: '(3+2)', 6: '(3*2)'}\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 (with `lru_cache`) so we don't need to re-compute them later. How do we break the problem into parts? If there is only one number, then there is only one expression, the number itself. If there are multiple numbers, then consider all ways of splitting the numbers 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 (taking care not to divide by 0):"
+ "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 (with `cache`) so we don't need to re-compute them later. How do we break the problem into parts? If there is only one number, then there is only one expression, the number itself. If there are multiple numbers, then consider all ways of splitting the numbers into two parts, finding all the expressions that can be made with each part, and combining pairs of expressions with any of the four operations (taking care not to divide by 0). Here is the definition:"
]
},
{
@@ -95,13 +93,13 @@
},
"outputs": [],
"source": [
- "Exp = str # An expression is a string, like '(10+(9-8))'\n",
- "ExpTable = Dict[float, Exp] # A table, like {11: '(10+(9-8))', ...}\n",
+ "Exp = str # A string, like '(10+(9-8))'\n",
+ "ExpTable = Dict[float, Exp] # A dict, like {11: '(10+(9-8))', ...}\n",
" \n",
- "@lru_cache(None)\n",
- "def expressions(numbers: tuple) -> ExpTable:\n",
+ "@cache\n",
+ "def expressions(numbers: Tuple[int]) -> ExpTable:\n",
" \"\"\"Return a {value: exp} table for all expressions that can be made from `numbers`.\"\"\"\n",
- " if len(numbers) == 1: # expressions((10,)) => {10: '10'}\n",
+ " if len(numbers) == 1: \n",
" return {numbers[0]: str(numbers[0])}\n",
" else: \n",
" table = {}\n",
@@ -115,23 +113,46 @@
" table[L + R] = Lexp + '+' + Rexp\n",
" return table\n",
" \n",
- "def splits(sequence, start=1) -> List[Tuple[Sequence, Sequence]]:\n",
- " \"Split sequence into two non-empty parts, in all ways.\"\n",
+ "def splits(sequence) -> List[Tuple[Sequence, Sequence]]:\n",
+ " \"\"\"Split sequence into two non-empty parts, in all ways.\"\"\"\n",
" return [(sequence[:i], sequence[i:]) \n",
- " for i in range(start, len(sequence))]"
+ " for i in range(1, len(sequence))]"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
- "id": "ViyfBGBUKEA7",
+ "id": "Ief1-PqFKEA6",
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"source": [
+ "If we call `expressions((3, 2, 1))`, then as we trace through the execution, at one particular point we get (among other things):\n",
+ "\n",
+ " len(numbers) == 3\n",
+ " Lnums = (3, 2)\n",
+ " Rnums = (1,)\n",
+ " L = 6\n",
+ " R = 1\n",
+ " Lexp = '((3*2)'\n",
+ " Rexp = '1)' \n",
+ " table[7] = '((3*2)+1)'\n",
+ "\n",
+ "\n",
+ "The various recursive calls to `expressions` end up giving these results:\n",
+ "\n",
+ " expressions((3,)) ➔ {3: '3'}\n",
+ " expressions((2, 1)) ➔ {1: '(2-1)', 2: '(2*1)', 3: '(2+1)'}\n",
+ " expressions((3, 2)) ➔ {1: '(3-2)', 1.5: '(3/2)', 5: '(3+2)', 6: '(3*2)'}\n",
+ " expressions((1,)) ➔ {1: '1'}\n",
+ " expressions((2,)) ➔ {2: '2'}\n",
+ " expressions((3, 2, 1)) ➔ {0: '((3-2)-1)', 0.5: '((3/2)-1)', 1.0: '((3-2)*1)', 1.5: '((3/2)*1)', \n",
+ " 2: '((3-2)+1)', 2.5: '((3/2)+1)', 3.0: '(3*(2-1))', 4: '((3+2)-1)', \n",
+ " 5.0: '((3+2)*1)', 6.0: '((3+2)+1)', 7: '((3*2)+1)', 9: '(3*(2+1))'}\n",
+ " \n",
"Some tests to make sure we got this right:"
]
},
@@ -148,23 +169,24 @@
},
"outputs": [],
"source": [
- "assert splits((3, 2, 1)) == [((3,), (2, 1)), \n",
- " ((3, 2), (1,))]\n",
+ "assert splits((3, 2, 1)) == [((3,), (2, 1)), ((3, 2), (1,))]\n",
"\n",
"assert expressions((3,)) == {3: '3'}\n",
"assert expressions((2, 1)) == {1: '(2-1)', 2: '(2*1)', 3: '(2+1)'}\n",
"assert expressions((3, 2)) == {1.5: '(3/2)', 6: '(3*2)', 1: '(3-2)', 5: '(3+2)'}\n",
"assert expressions((1,)) == {1: '1'}\n",
"\n",
- "assert expressions((3, 2, 1)) == {0: '((3-2)-1)', 0.5: '((3/2)-1)', 1: '((3-2)*1)',\n",
- " 1.5: '((3/2)*1)', 2: '((3-2)+1)', 2.5: '((3/2)+1)', 3: '(3*(2-1))',\n",
- " 4: '((3+2)-1)', 5: '((3+2)*1)', 6: '((3+2)+1)', 7: '((3*2)+1)', 9: '(3*(2+1))'}\n",
+ "assert expressions((3, 2, 1)) == {0: '((3-2)-1)', 0.5: '((3/2)-1)', 1: '((3-2)*1)',\n",
+ " 1.5: '((3/2)*1)', 2: '((3-2)+1)', 2.5: '((3/2)+1)', 3: '(3*(2-1))',\n",
+ " 4: '((3+2)-1)', 5: '((3+2)*1)', 6: '((3+2)+1)', 7: '((3*2)+1)', 9: '(3*(2+1))'}\n",
+ "\n",
+ "assert expressions((3, 2, 1))[7] == '((3*2)+1)'\n",
"\n",
"assert splits((5, 4, 3, 2, 1)) == [\n",
- " ((5,), (4, 3, 2, 1)), \n",
- " ((5, 4), (3, 2, 1)), \n",
- " ((5, 4, 3), (2, 1)), \n",
- " ((5, 4, 3, 2), (1,))]"
+ " ((5,), (4, 3, 2, 1)), \n",
+ " ((5, 4), (3, 2, 1)), \n",
+ " ((5, 4, 3), (2, 1)), \n",
+ " ((5, 4, 3, 2), (1,))]"
]
},
{
@@ -178,7 +200,7 @@
}
},
"source": [
- "That looks reasonable. Let's solve the whole puzzle.\n",
+ "That looks good. Let's solve the whole puzzle.\n",
"\n"
]
},
@@ -199,8 +221,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "CPU times: user 19.1 s, sys: 427 ms, total: 19.5 s\n",
- "Wall time: 19.5 s\n"
+ "CPU times: user 19.6 s, sys: 467 ms, total: 20.1 s\n",
+ "Wall time: 20.1 s\n"
]
},
{
@@ -291,13 +313,13 @@
"\n",
"Alex Bellos had another challenge: \n",
"\n",
- "- *I was half hoping a computer scientist would let me know exactly how many distinct solutions there are with only the four basic operations. Maybe someone will.*\n",
+ "- *I was half hoping a computer scientist would let me know exactly how many distinct solutions there are with only the four basic operations.*\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",
+ "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: two expressions are distinct if they are not exactly the same character string. But I won't argue with anyone who prefers a different definition of \"distinct solution.\"\n",
"\n",
- "So how can I count expressions? I can mimic `expressions`, but make a table of counts, rather than expression strings. There is only 1 way to make a single number into an expression, and in general, combining two subexpressions into a larger expression using an operator can be done in a number of ways equal to the product of the ways each of the two subexpressions can be made:"
+ "So how can I count expressions? I can mimic `expressions`, but make a table of counts, rather than expression strings. There is only 1 way to make a single number into an expression, and in general, combining two subexpressions into a larger expression using an operation can be done in a number of ways equal to the product of the ways each of the two subexpressions can be made:"
]
},
{
@@ -313,11 +335,11 @@
},
"outputs": [],
"source": [
- "@lru_cache(None)\n",
- "def expression_counts(numbers: tuple) -> Counter:\n",
+ "@cache\n",
+ "def expression_counts(numbers: Tuple[int]) -> 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",
+ " if len(numbers) == 1: \n",
+ " return Counter(numbers) # Same as Counter({numbers[0]: 1})\n",
" else: \n",
" table = Counter()\n",
" for (Lnums, Rnums) in splits(numbers):\n",
@@ -396,11 +418,11 @@
}
},
"source": [
- "This says there are 30,066 distinct expressions for 2016. Is that the answer Alex wanted?\n",
+ "This claims there are 30,066 distinct expressions for 2016. Is that the answer Alex wanted?\n",
"\n",
"# The Trouble with Round-off Error\n",
"\n",
- "I don't think it is the right answer. The trouble is: round-off error.\n",
+ "**I don't think it is the right answer.** The trouble is: round-off error.\n",
"\n",
"Let's find all the values in `expressions(c10)` that are very near to 2016, within ±0.0000000001:"
]
@@ -479,7 +501,7 @@
}
],
"source": [
- "def exact(exp) -> Exp: return re.sub(r\"([0-9]+)\", r\"Fraction(\\1)\", exp)\n",
+ "def exact(exp) -> str: return re.sub(r\"([0-9]+)\", r\"Fraction(\\1)\", exp)\n",
"\n",
"exact('1/(5-2)')"
]
@@ -526,7 +548,7 @@
"source": [
"sum(expression_counts(c10)[y] \n",
" for y, exp in expressions(c10).items()\n",
- " if abs(y - 2016) < 1e-6 and eval(exact(exp)) == 2016)"
+ " if abs(y - 2016) < 1 and eval(exact(exp)) == 2016)"
]
},
{
@@ -540,7 +562,7 @@
}
},
"source": [
- "I can claim that the answer is **44,499**, but I would feel more confident if I did *all* the computations with exact arithmetic, and if the result was independently verified by someone else, and passed an extensive test suite. And of course, if you have a different definition of \"distinct solution,\" you will get a different answer."
+ "I can claim that the answer is **44,499**, but I don't have complete confidence in that claim. It was easy to verify that `'(((((((((10*9)+8)*7)-6)-5)-4)*3)+2)+1)'` is a correct solution for `expressions(c10)[2016]` by doing simple arithmetic. But there is no simple test to verify that 44,499 is correct; I would want code reviews and tests, and hopefully an independent implementation. And of course, if you have a different definition of \"distinct solution,\" you will get a different answer."
]
},
{
@@ -559,7 +581,7 @@
">\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 (and brackets).\n",
"\n",
- "This puzzle goes back to a [1914 publication](https://archive.org/details/mathematicalrecr00ball) by the mathematician/magician [W. W. Rouse Ball](https://en.wikipedia.org/wiki/W._W._Rouse_Ball). The solution is easy:"
+ "This puzzle goes back to a [1914 publication](https://archive.org/details/mathematicalrecr00ball) by the famed mathematician/magician [W. W. Rouse Ball](https://en.wikipedia.org/wiki/W._W._Rouse_Ball). The solution is easy:"
]
},
{
@@ -609,44 +631,43 @@
"id": "Aq98pwf4KEA_"
},
"source": [
- "# New Math Operations, and Permutations\n",
+ "# Additional Math 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",
+ "- *You can introduce **new mathematical operations** such as powers, square roots, concatenation and decimals, or the factorial symbol, `!`.*\n",
"\n",
- "It seems there are a lot of similar puzzles that all have slightly different rules for which numbers are required and which operators are allowed. Many of the puzzles allow **permutations** of the digits: instead of requiring (2, 0, 1, 6) to appear in that order, they are allowed to appear in any order. Some allow **concatenation** of digits (e.g. the digits (1, 6) can form the expression `'16'`) and some allow **decimal points** (e.g., `'1.6'`). Some allow **unary operators** (e.g. `'√9'` or `'-(2*3)'`).\n",
+ "It seems there many variations of these ***mathematical expression*** puzzles, all with slightly different rules for what operations are allowed, what expression values are to be made, what digits the expressions are composed of, and whether those digits may be **concatenated**, **permuted**, or contain a **decimal point**. Some variants allow **unary operations** (e.g. `'√9'` or `3!`).\n",
"\n",
- "Hanling these additional kinds of expressions introduces some issues; here's how I'll handle them:\n",
+ "Allowing additional operations introduces some issues; here's how I'll handle them:\n",
"\n",
- "- **Imaginaries**: `√-1` is an imaginary number (I won't allow imaginary numbers).\n",
- "- **Irrationals**: `√2` is an irrational number (I'll use floating point approximations).\n",
- "- **Round-off error**:`49*(1/49) == 0.9999999999999999`, not `1` (I'll try to round off, but nmight make errors of ommission or commission).\n",
- "- **Overflow**: `10.^(9.^8.)` gives an `OverflowError` (I'll drop overflow results, possibly making an error of ommission).\n",
- "- **Unlikely numbers**: `(.9^(4!))^(√2)` is a valid number, but unlikely to eventually lead to an integer. (I'll drop unlikely operations.)\n",
- "- **Arbitraily deeply nested unary operators**: e.g, `√√√√√√√√(4!!!!!!!!)` (I'll limit the unary operator nesting level to 2).\n",
+ "- **Imaginary numbers**: `√-1` (I won't allow imaginary numbers.)\n",
+ "- **Irrational numbers**: `√2` (I'll use floating point approximations.)\n",
+ "- **Round-off error**:`49*(1/49) == 0.9999999999999999` (I'll try to round off appropriately, but might make errors.)\n",
+ "- **Overflow**: `10.^(9.^8.)` raises `OverflowError` (I'll drop overflow results, but might miss good results.)\n",
+ "- **Huge integers**: `10^(9*(8+7))` more bytes of storage than all the molecules in the universe. (I'll avoid generating huge integers.)\n",
+ "- **Unlikely numbers**: `(.9^(8!))^(√7)` is a valid number, but unlikely to eventually lead to an integer. (I'll drop unlikely operations.)\n",
+ "- **Deeply nested unary operations**: `√√√√√√√√(--4!!!!!!!!)` is legal; where do you end? (I'll limit unary operation nesting to 2).\n",
"\n",
"\n",
- "\n",
- "To facilitate solving a range of puzzles with different operators, I will redefine `expressions` to take a second argument, `ops`, specifying the allowable operators as a string of one-character codes. The table below lists binary operators in the left column; unary operators in the middle; and two pseudo-operations on digits on the right (I chose comma for concatenation, because it is familiar in numbers like: `'1,000,000'`).\n",
- "\n",
- "|Code|Operator|Code|Operator|Code|Operator|\n",
- "|------|--------|------|--------|----|---|\n",
- "|`+`|addition: 1 + 2 = 3 |`_`|unary minus: -2 = -2|`.`|decimal point: 1.23|\n",
- "|`-`|subtraction: 3 - 2 = 1 |`√`|square root: √9 = 3|`,`|concatenation of digits: 123|\n",
- "|`*`|multiplication: 2 * 3 = 6|`!`|factorial: 4! = 24 |\n",
- "|`/`|division: 6 / 3 = 2 |`⌊`|floor:⌊4.4⌋ = 4|\n",
- "|`^`|exponentiation: 2 ^ 3 = 8|`⌈`|ceiling: ⌈4.4⌉ = 5|\n",
+ "I will use a one-character code to describe each allowable operation. There are three kinds of operations: binary (two arguments), unary (one argument), and digit (operates on a string of digits). This table gives the character codes, operation names, and examples:\n",
"\n",
"\n",
- "I will define the data type `Operator` to define an operator, giving its code symbol, its arity (binary or unary), the Python function to call to do the calculation, the format string for unary operations, and for some operations, a `guard` that says when it is applicable. For example:\n",
- " - Division is applicable when the second argument is not zero.\n",
- " - I only allow square roots for a small range of positive numbers that become integers when multiplied by 9!.\n",
- " - I only allow factorials for 0 to 9.\n",
+ "|Binary Operations|Unary Operations|Digit Operations|\n",
+ "|--------------|--------------|-------|\n",
+ "|`+` addition: 1 + 2 = 3 |`_` unary minus: -4 = -4|`.` decimal point: 1.23|\n",
+ "|`-` subtraction: 3 - 2 = 1 |`√` square root: √4 = 2|`,` concatenation of digits: 123|\n",
+ "|`*` multiplication: 2 * 3 = 6|`!` factorial: 4! = 24 |\n",
+ "|`/` division: 6 / 3 = 2 |`⌊` floor:⌊4.4⌋ = 4|\n",
+ "|`^` exponentiation: 2 ^ 3 = 8|`⌈` ceiling: ⌈4.4⌉ = 5|\n",
"\n",
- "These some that are not mathematically necessary, but keep the table from being overpopulated with numbers that are unlikely to help lead to solutions. Unfortunately, that means some good solutions will be missed. \n",
"\n",
- "For binary operations, the symbol is used to construct the expression string, but for unary ops the `fmt` field gives a format string; this allows us to have prefix and postfix operators. The function `operators` picks out the operators you want from the `OPERATORS` global variable. You can augment this with new operators as you wish."
+ "I will define the data type `Operation` to hold a description of each operation: the code symbol; the arity (binary or unary); a Python function to call to do the calculation; a format string (for unary operations); and a predicate that says when the operation is applicable. The applicability test can avoid both errors and results that are unlikely to be helpful. For example:\n",
+ " - Division is applicable when the right side is not zero.\n",
+ " - Square root is only applicable to a small range of positive numbers that become integers when multiplied by 10!.\n",
+ " - Factorial is only applicable to integers 0 to 9 (bigger numbers are unlikely to be useful).\n",
+ "\n",
+ "For binary operations, the symbol is used to construct the expression string, but for unary ops the `fmt` field gives a format string; this allows us to have prefix and postfix unary operations. "
]
},
{
@@ -657,27 +678,19 @@
},
"outputs": [],
"source": [
- "def true(*args): return True\n",
+ "Operation = namedtuple('Operation', 'symbol, func, fmt, applicable', defaults=[None, lambda *args: True])\n",
"\n",
- "Operator = namedtuple('Operator', 'symbol, func, fmt, guard', defaults=[None, true])\n",
- "\n",
- "OPERATORS = {\n",
- " 2: {Operator('+', add),\n",
- " Operator('-', sub),\n",
- " Operator('*', mul),\n",
- " Operator('/', div, None, lambda L, R: R != 0),\n",
- " Operator('^', pow, None, lambda L, R: -10 <= R <= 10 and (L > 0 or R == int(R)))},\n",
- " 1: {Operator('√', sqrt, '√{}', lambda v: 0 < v <= 256 and (362880. * v).is_integer()),\n",
- " Operator('!', factorial, '{}!', lambda v: v in range(10)),\n",
- " Operator('_', neg, '-{}'),\n",
- " Operator('⌊', floor, '⌊{}⌋'),\n",
- " Operator('⌈', ceil, '⌈{}⌉')}}\n",
- "\n",
- "OPS = '+-*/^_√!.,' # Default set of operators; omits floor and ceiling.\n",
- "\n",
- "def operators(arity: int, ops: str) -> List[Operator]:\n",
- " \"\"\"All the operators in OPERATORS with given arity whose code symbol is one of `ops`.\"\"\"\n",
- " return [op for op in OPERATORS[arity] if op.symbol in ops]"
+ "OPERATIONS = {\n",
+ " 2: {Operation('+', add),\n",
+ " Operation('-', sub),\n",
+ " Operation('*', mul),\n",
+ " Operation('/', div, None, lambda L, R: R != 0),\n",
+ " Operation('^', pow, None, lambda L, R: (-10 <= R <= 10) and (L > 0 or R == int(R)))},\n",
+ " 1: {Operation('√', sqrt, '√x', lambda v: 0 < v <= 256 and (3628800. * v).is_integer()),\n",
+ " Operation('!', factorial, 'x!', lambda v: v in range(10)),\n",
+ " Operation('_', neg, '-x'),\n",
+ " Operation('⌊', floor, '⌊x⌋'),\n",
+ " Operation('⌈', ceil, '⌈x⌉')}}"
]
},
{
@@ -686,7 +699,7 @@
"id": "GbpkLTRLKEA_"
},
"source": [
- "I'll define the function `operate` to compute an arithmetic operation, catch any errors, and try to correct round-off errors. The idea is that since my expressions start with integers, results that are close to an integer probably are 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 equal to `1`. Instead, I'll try to avoid such expressions by silently dropping (returning `None` from `operate`) any intermediate value whose magnitude is outside the range of 10-10 to 1010 (except of course I will accept an exact 0)."
+ "I'll define the function `operate` to compute an arithmetic operation, catch any errors, and try to correct round-off errors. The idea is that since my expressions start with integers, results that are very close to an integer probably are an 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 is not equal to `1`. I'll try to avoid such expressions by silently dropping (returning `None` from `operate`) any intermediate value whose absolute value is outside the range of 10-10 to 1010 (except of course I will accept an exact 0)."
]
},
{
@@ -697,11 +710,11 @@
},
"outputs": [],
"source": [
- "def operate(operator, *args) -> Optional[float]: \n",
+ "def operate(operation, *args) -> Optional[float]: \n",
" \"Return op(*args), trying to correct for roundoff error, or `None` if too big/small/erroneous.\"\n",
- " if operator.guard(*args):\n",
+ " if operation.applicable(*args):\n",
" try:\n",
- " val = operator.func(*args)\n",
+ " val = operation.func(*args)\n",
" except (ArithmeticError, ValueError):\n",
" return None\n",
" return (0.0 if val == 0 else\n",
@@ -719,12 +732,18 @@
"source": [
"# Refactoring `expressions`\n",
"\n",
- "I'll take this opportunity to refactor `expressions` to use the new `OPERATORS`. I introduce three subfunctions:\n",
+ "I'll take this opportunity to refactor `expressions` to use the new `OPERATIONS`. The new `expressions` takes four arguments:\n",
+ "- `numbers`: as before, an ordered tuple of component integers from which to make expressions.\n",
+ "- `ops`: a character string containing the allowable operation character codes. Default `OPS`, which is `'+-*/^_√!.,'`.\n",
+ "- `nesting`: the maximum nesting level of unary operations. Default `2`, so `'3!!'` is ok, but `'3!!!'` is not.\n",
+ "- `permute`: whether the `numbers` may be permuted. If true then `(2, 3)` can produce `'3^2'`.\n",
+ "\n",
+ "I introduce three subfunctions, corresponding to the three types of operations:\n",
"- `digit_expressions(digits, ops)`: returns a table of expressions made with all the digits, in the given order.\n",
" - If `','` is in `ops` allow concatenation of digits (e.g. `{44: '44'}`).\n",
" - If `'.'` is in ops allow decimals (e.g. `{4.4: '4.4', 0.44: '.44'}`. \n",
" - If `digits` is a single digit, always allow it (e.g. `{4: '4'}`).\n",
- "- `add_binary_expressions(table, numbers, ops, nesting)`: builds binary expressions using all numbers in order (e.g. `{5: '(2+3)'}`).\n",
+ "- `add_binary_expressions(table, numbers, ops, nesting)`: add binary expressions to table using all numbers in order (e.g. `{5: '(2+3)'}`).\n",
"- `add_unary_expressions(table, ops, nesting)`: add expressions like `√4` and `4!` to `table` and return `table`. \n",
"\n"
]
@@ -742,11 +761,13 @@
},
"outputs": [],
"source": [
+ "OPS = '+-*/^_√!.,' # Default set of operations; omits floor and ceiling.\n",
+ "\n",
"@cache\n",
- "def expressions(numbers: Collection[int], ops=OPS, nesting=2, permute=False) -> ExpTable:\n",
+ "def expressions(numbers: Tuple[int], ops=OPS, nesting=2, permute=False) -> ExpTable:\n",
" \"Return {value: expr} for all expressions that can be made from numbers using ops.\"\n",
- " orderings = (permutations(numbers) if permute else [numbers])\n",
" table = {}\n",
+ " orderings = (permutations(numbers) if permute else [numbers]) # Handle permutations once at top level\n",
" for nums in orderings:\n",
" table.update(digit_expressions(nums, ops))\n",
" add_binary_expressions(table, nums, ops, nesting)\n",
@@ -770,7 +791,7 @@
"\n",
"def add_binary_expressions(table, numbers, ops, nesting) -> ExpTable:\n",
" \"Add binary expressions by splitting numbers and combining with an op.\"\n",
- " binary_ops = operators(2, ops)\n",
+ " binary_ops = operations(2, ops)\n",
" for (Lnums, Rnums) in splits(numbers):\n",
" Ltable = expressions(Lnums, ops, nesting, False)\n",
" Rtable = expressions(Rnums, ops, nesting, False)\n",
@@ -782,12 +803,22 @@
" \n",
"def add_unary_expressions(table, ops: str, nesting: int) -> ExpTable:\n",
" \"Add unary expressions (e.g. -v, √v and v!) to table\"\n",
- " unary_ops = operators(1, ops)\n",
+ " unary_ops = operations(1, ops)\n",
" for _ in range(nesting):\n",
" for v in tuple(table):\n",
" for op in unary_ops:\n",
- " assign(table, operate(op, v), op.fmt.format(table[v]))\n",
- " return table"
+ " assign(table, operate(op, v), op.fmt.replace('x', table[v]))\n",
+ " return table\n",
+ "\n",
+ "def operations(arity: int, ops: str) -> List[Operation]:\n",
+ " \"\"\"All the operations in OPERATIONS with given arity whose code symbol is one of `ops`.\"\"\"\n",
+ " return [op for op in OPERATIONS[arity] if op.symbol in ops]\n",
+ "\n",
+ "def splits(sequence, start=1) -> List[Tuple[Sequence, Sequence]]:\n",
+ " \"\"\"Split sequence into two parts, in all ways.\n",
+ " If start=1, the two parts are non-empty. If start=0, allow empty left part.\"\"\"\n",
+ " return [(sequence[:i], sequence[i:]) \n",
+ " for i in range(start, len(sequence))]"
]
},
{
@@ -812,31 +843,11 @@
" if val is not None and (val not in table or weight(exp) < weight(table[val])): \n",
" table[val] = exp\n",
" \n",
- "WEIGHTS = Counter({'√':4, '!':2, '.':1, '^':1, '/':0.2, '-':0.1, '⌊': 5, '⌈':5})\n",
+ "WEIGHTS = Counter({'√':4, '!':2, '.':2, '/':0.2, '-':0.1, '⌊': 5, '⌈':5})\n",
" \n",
"def weight(exp: str) -> int: return len(exp) + sum(WEIGHTS[c] for c in exp)"
]
},
- {
- "cell_type": "code",
- "execution_count": 18,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "{16: '16', -16: '-16'}"
- ]
- },
- "execution_count": 18,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "add_unary_expressions({16: '16'}, '_', 2) "
- ]
- },
{
"cell_type": "markdown",
"metadata": {
@@ -848,7 +859,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 18,
"metadata": {
"id": "mJJ8AxFXKEA_"
},
@@ -889,7 +900,7 @@
},
{
"cell_type": "code",
- "execution_count": 20,
+ "execution_count": 19,
"metadata": {
"button": false,
"id": "ndmqhDXnKEBA",
@@ -901,7 +912,8 @@
"outputs": [],
"source": [
"def show(numbers: tuple, limit=None, ops=OPS, nesting=2, cols=4, permute=False):\n",
- " \"\"\"Print expressions for integers from 0 up to limit or the first unmakeable integer.\"\"\"\n",
+ " \"\"\"Print expressions for integers from 0 up to limit or the first unmakeable integer.\n",
+ " Break output into `cols` columns.\"\"\"\n",
" table = expressions(numbers, ops, nesting, permute)\n",
" print(f'Can make 0 to {unmakeable(table)-1} with expressions({numbers}, ops=\"{ops}\", permute={permute}).'\n",
" f' [{len(table):,} table entries]\\n')\n",
@@ -934,7 +946,7 @@
},
{
"cell_type": "code",
- "execution_count": 21,
+ "execution_count": 20,
"metadata": {
"id": "N43Lq2hKKEBA",
"outputId": "51a24d77-2e45-453b-b329-0e9f24b4efed"
@@ -944,15 +956,15 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Can make 0 to 72 with expressions((4, 4, 4, 4), ops=\"+-*/^_√!.,\", permute=False). [711,642 table entries]\n",
+ "Can make 0 to 72 with expressions((4, 4, 4, 4), ops=\"+-*/^_√!.,\", permute=False). [721,878 table entries]\n",
"\n",
" 0 = 44-44 19 = 4!-(4+(4/4)) 38 = 44-(4!/4) 57 = ((.4+4!)/.4)-4 \n",
" 1 = 44/44 20 = 4*(4+(4/4)) 39 = 44-(√4/.4) 58 = ((4^4)-4!)/4 \n",
" 2 = 4*(4/(4+4)) 21 = (4+4.4)/.4 40 = 44-√(4*4) 59 = (4!/.4)-(4/4) \n",
- " 3 = (4+(4+4))/4 22 = √4*(44/4) 41 = (.4+(4*4))/.4 60 = 44+(4*4) \n",
- " 4 = 4+(4*(4-4)) 23 = ((4*4!)-4)/4 42 = √4+(44-4) 61 = (4/4)+(4!/.4) \n",
- " 5 = (4+(4*4))/4 24 = 4+(4+(4*4)) 43 = 44-(4/4) 62 = (4*(4*4))-√4 \n",
- " 6 = 4+((4+4)/4) 25 = (4+(4*4!))/4 44 = 4+(44-4) 63 = ((4^4)-4)/4 \n",
+ " 3 = 4-(4^(4-4)) 22 = √4*(44/4) 41 = (.4+(4*4))/.4 60 = 44+(4*4) \n",
+ " 4 = 4+(4*(4-4)) 23 = 4!-(4^(4-4)) 42 = √4+(44-4) 61 = (4/4)+(4!/.4) \n",
+ " 5 = 4+(4^(4-4)) 24 = 4+(4+(4*4)) 43 = 44-(4/4) 62 = (4*(4*4))-√4 \n",
+ " 6 = 4+((4+4)/4) 25 = 4!+(4^(4-4)) 44 = 4+(44-4) 63 = ((4^4)-4)/4 \n",
" 7 = (44/4)-4 26 = 4+(44/√4) 45 = 44+(4/4) 64 = (4+4)*(4+4) \n",
" 8 = 4+(4+(4-4)) 27 = 4+(4!-(4/4)) 46 = 4+(44-√4) 65 = (4+(4^4))/4 \n",
" 9 = 4+(4+(4/4)) 28 = 44-(4*4) 47 = 4!+(4!-(4/4)) 66 = √4+(4*(4*4)) \n",
@@ -962,16 +974,14 @@
" 13 = 4!-(44/4) 32 = (4*4)+(4*4) 51 = (.4+(4!-4))/.4 70 = (4!+(4^4))/4 \n",
" 14 = 4+(4+(4!/4)) 33 = 4!+((4-.4)/.4) 52 = 4+(4+44) 71 = (4!+4.4)/.4 \n",
" 15 = 4+(44/4) 34 = 44-(4/.4) 53 = 4!+(4!+(√4/.4)) 72 = 4+(4!+44) \n",
- " 16 = .4*(44-4) 35 = 4!+(44/4) 54 = 44+(4/.4) \n",
- " 17 = (4/4)+(4*4) 36 = 44-(4+4) 55 = 44/(.4+.4) \n",
- " 18 = .4+(4*4.4) 37 = ((.4+4!)/.4)-4! 56 = 4*(4+(4/.4)) \n",
- "CPU times: user 18.5 s, sys: 18.5 ms, total: 18.6 s\n",
- "Wall time: 18.6 s\n"
+ " 16 = 4+(4+(4+4)) 35 = 4!+(44/4) 54 = 44+(4/.4) \n",
+ " 17 = (4*4)+(4/4) 36 = 44-(4+4) 55 = 44/(.4+.4) \n",
+ " 18 = (44/√4)-4 37 = ((.4+4!)/.4)-4! 56 = 4!+(4*(4+4)) \n"
]
}
],
"source": [
- "%time show((4, 4, 4, 4))"
+ "show((4, 4, 4, 4))"
]
},
{
@@ -990,7 +1000,7 @@
},
{
"cell_type": "code",
- "execution_count": 22,
+ "execution_count": 21,
"metadata": {
"button": false,
"id": "ZWCn9F1_KEBA",
@@ -1007,7 +1017,7 @@
"'((4+4)!/(4!-4))'"
]
},
- "execution_count": 22,
+ "execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
@@ -1050,7 +1060,7 @@
},
{
"cell_type": "code",
- "execution_count": 23,
+ "execution_count": 22,
"metadata": {
"id": "e807aDInKEBA",
"outputId": "e672d88a-bc1d-456d-da6b-b720c878e939"
@@ -1060,15 +1070,15 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Can make 0 to 30 with expressions((2, 2, 2, 2), ops=\"+-*/^_√!.,\", permute=False). [109,291 table entries]\n",
+ "Can make 0 to 30 with expressions((2, 2, 2, 2), ops=\"+-*/^_√!.,\", permute=False). [112,539 table entries]\n",
"\n",
- " 0 = 22-22 8 = 2+(2+(2+2)) 16 = 2*(2*(2+2)) 24 = 22+√(2+2) \n",
+ " 0 = 22-22 8 = 2+(2+(2*2)) 16 = 2*(2*(2*2)) 24 = 2+(√22^2) \n",
" 1 = 22/22 9 = (22/2)-2 17 = 22-(√.2^-2) 25 = (2-2.2)^-2 \n",
- " 2 = 2+(2*(2-2)) 10 = 22/2.2 18 = 22-(2+2) 26 = 2+(2+22) \n",
- " 3 = (2+(2+2))/2 11 = 22/√(2+2) 19 = (2+(2-.2))/.2 27 = 22+(√.2^-2) \n",
- " 4 = .2*(22-2) 12 = (2+22)/2 20 = 22-√(2+2) 28 = 2+(2+(2+2)!) \n",
+ " 2 = 2+(2*(2-2)) 10 = 22/2.2 18 = 22-(2*2) 26 = 2+(2+22) \n",
+ " 3 = 2+(2^(2-2)) 11 = 22/(√2^2) 19 = (2+(2-.2))/.2 27 = 22+(√.2^-2) \n",
+ " 4 = 2*(2+(2-2)) 12 = (2+22)/2 20 = 22-(√2^2) 28 = 2+(2+(2*2)!) \n",
" 5 = 2+(2+(2/2)) 13 = 2+(22/2) 21 = 22-(2/2) 29 = 2+(2+(.2^-2)) \n",
- " 6 = (2*(2+2))-2 14 = (2^(2+2))-2 22 = 2+(22-2) 30 = (2+(2+2))/.2 \n",
+ " 6 = (2*(2*2))-2 14 = (2^(2*2))-2 22 = 2+(22-2) 30 = (2+(2*2))/.2 \n",
" 7 = 2+(2/(2*.2)) 15 = (2+(2/2))/.2 23 = 22+(2/2) \n"
]
}
@@ -1088,7 +1098,7 @@
},
{
"cell_type": "code",
- "execution_count": 24,
+ "execution_count": 23,
"metadata": {
"id": "Ll4xl6TOKEBA",
"outputId": "850ea56d-da8b-4f1f-c684-ba75f1c60afb"
@@ -1098,24 +1108,24 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Can make 0 to 61 with expressions((9, 9, 9, 9), ops=\"+-*/^_√!.,\", permute=False). [774,333 table entries]\n",
+ "Can make 0 to 61 with expressions((9, 9, 9, 9), ops=\"+-*/^_√!.,\", permute=False). [791,279 table entries]\n",
"\n",
- " 0 = 99-99 16 = 9+((9/.9)-√9) 32 = (99-√9)/√9 48 = √9!*(9-(9/9)) \n",
+ " 0 = 99-99 16 = 9+(√9!+(9/9)) 32 = (99-√9)/√9 48 = √9!*(9-(9/9)) \n",
" 1 = 99/99 17 = 9+(9-(9/9)) 33 = √9*(99/9) 49 = 9+(√9!*(√9!/.9)) \n",
" 2 = (99/9)-9 18 = 99-(9*9) 34 = (√9+99)/√9 50 = ((9*√9!)-9)/.9 \n",
- " 3 = (9+(9+9))/9 19 = 9+(9+(9/9)) 35 = (√9!+99)/√9 51 = 9*(9-(√9/.9)) \n",
- " 4 = 9-(9/(.9+.9)) 20 = 9+(99/9) 36 = 9+(9+(9+9)) 52 = √9!*(9-(√9/9)) \n",
+ " 3 = 9^(9/(9+9)) 19 = 9+(9+(9/9)) 35 = (√9!+99)/√9 51 = 9*(9-(√9/.9)) \n",
+ " 4 = √9+(9^(9-9)) 20 = 9+(99/9) 36 = 9+(9+(9+9)) 52 = √9!*(9-(√9/9)) \n",
" 5 = √9+((9+9)/9) 21 = (9+9.9)/.9 37 = (9/.9)+(9*√9) 53 = (9*√9!)-(9/9) \n",
- " 6 = ((9+(9+9))/9)! 22 = 9+(√9+(9/.9)) 38 = √9!*(√9+(√9/.9)) 54 = (9*9)-(9*√9) \n",
+ " 6 = (9^(9/(9+9)))! 22 = 9+(√9+(9/.9)) 38 = √9!*(√9+(√9/.9)) 54 = (9*9)-(9*√9) \n",
" 7 = 9-((9+9)/9) 23 = √9+((9+9)/.9) 39 = 9+(9*(√9/.9)) 55 = 99/(.9+.9) \n",
- " 8 = ((9*9)-9)/9 24 = (99/√9)-9 40 = (9+(9*√9))/.9 56 = 9!/(9*(9-√9)!) \n",
+ " 8 = 9-(9^(9-9)) 24 = (99/√9)-9 40 = (9+(9*√9))/.9 56 = 9!/(9*(9-√9)!) \n",
" 9 = 9+(9*(9-9)) 25 = 9+(√9!+(9/.9)) 41 = (.9+(√9!*√9!))/.9 57 = √9*(9+(9/.9)) \n",
" 10 = 99/9.9 26 = (9*√9)-(9/9) 42 = 9+(99/√9) 58 = √9!*(9+(√9!/9)) \n",
" 11 = 9+((9+9)/9) 27 = 9+(9+√(9*9)) 43 = √9+(√9!*(√9!/.9)) 59 = ((9*√9!)-.9)/.9 \n",
" 12 = (9+99)/9 28 = 9+(9+(9/.9)) 44 = (9*√9!)-(9/.9) 60 = √9*((9+9)/.9) \n",
- " 13 = 9+(√9+(9/9)) 29 = 9+((9+9)/.9) 45 = 9*(9/(.9+.9)) 61 = (.9+(9*√9!))/.9 \n",
- " 14 = √9+(99/9) 30 = (9+(9+9))/.9 46 = (9/.9)+(√9!*√9!) \n",
- " 15 = 9+((9+9)/√9) 31 = (.9+(9*√9))/.9 47 = √9*(9+(√9!/.9)) \n"
+ " 13 = 9+(√9+(9/9)) 29 = 9+((9+9)/.9) 45 = 9+(9+(9*√9)) 61 = (.9+(9*√9!))/.9 \n",
+ " 14 = √9+(99/9) 30 = (99-9)/√9 46 = (9/.9)+(√9!*√9!) \n",
+ " 15 = 9+((9+9)/√9) 31 = (99-√9!)/√9 47 = √9*(9+(√9!/.9)) \n"
]
}
],
@@ -1134,7 +1144,7 @@
},
{
"cell_type": "code",
- "execution_count": 25,
+ "execution_count": 24,
"metadata": {
"button": false,
"id": "Mmed82RmKEBA",
@@ -1149,25 +1159,46 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Can make 0 to 38 with expressions((5, 5, 5, 5), ops=\"+-*/^_√!.,\", permute=False). [344,933 table entries]\n",
+ "Can make 0 to 38 with expressions((5, 5, 5, 5), ops=\"+-*/^_√!.,\", permute=False). [345,331 table entries]\n",
"\n",
" 0 = 55-55 10 = 55/5.5 20 = 5+(5+(5+5)) 30 = 55-(5*5) \n",
" 1 = 55/55 11 = 5.5+5.5 21 = (5+5.5)/.5 31 = 55-(5!/5) \n",
- " 2 = 5!/(5+55) 12 = (5+55)/5 22 = 55/(5*.5) 32 = (5.5-5)^-5 \n",
- " 3 = (5+(5+5))/5 13 = (5!-55)/5 23 = 55-(.5^-5) 33 = .5*(5!*.55) \n",
- " 4 = ((5*5)-5)/5 14 = (5!/5)-(5+5) 24 = (5*5)-(5/5) 34 = 5+(5+(5!/5)) \n",
- " 5 = 5+(5*(5-5)) 15 = (5*5)-(5+5) 25 = .5*(55-5) 35 = 5+(5+(5*5)) \n",
- " 6 = (55/5)-5 16 = 5+(55/5) 26 = (5/5)+(5*5) 36 = (5!+(.5*5!))/5 \n",
+ " 2 = 5!/(5+55) 12 = (5+55)/5 22 = 55/(5*.5) 32 = ((5+5)/5)^5 \n",
+ " 3 = (5+(5+5))/5 13 = (5!-55)/5 23 = 55-(.5^-5) 33 = (5/5)+(.5^-5) \n",
+ " 4 = 5-(5^(5-5)) 14 = (5!/5)-(5+5) 24 = (5*5)-(5/5) 34 = 5+(5+(5!/5)) \n",
+ " 5 = 5+(5*(5-5)) 15 = (5*5)-(5+5) 25 = 5*(5+(5-5)) 35 = 5+(5+(5*5)) \n",
+ " 6 = (55/5)-5 16 = 5+(55/5) 26 = (5*5)+(5/5) 36 = (5!+(.5*5!))/5 \n",
" 7 = 5+((5+5)/5) 17 = 5+(5!/(5+5)) 27 = (5*5.5)-.5 37 = 5+(.5^-√(5*5)) \n",
" 8 = 5.5+(5*.5) 18 = ((5!-5)/5)-5 28 = .5+(5*5.5) 38 = ((5!/5)-5)/.5 \n",
- " 9 = 5+(5-(5/5)) 19 = (5!-(5*5))/5 29 = (5!+(5*5))/5 \n",
- "CPU times: user 9.37 s, sys: 5.85 ms, total: 9.37 s\n",
- "Wall time: 9.37 s\n"
+ " 9 = 5+(5-(5/5)) 19 = (5!-(5*5))/5 29 = (5!+(5*5))/5 \n"
]
}
],
"source": [
- "%time show((5, 5, 5, 5))"
+ "show((5, 5, 5, 5))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Can make 0 to 16 with expressions((2, 0, 1, 8), ops=\"+-*/_√!\", permute=False). [2,263 table entries]\n",
+ "\n",
+ " 0 = 2*(0*(1*8)) 5 = -(2+(0+1))+8 10 = 2+(0+(1*8)) 15 = (2+0!)!+(1+8) \n",
+ " 1 = (2*(0*(1*8)))! 6 = (2*(0-1))+8 11 = 2+(0+(1+8)) 16 = 2*(0+(1*8)) \n",
+ " 2 = 2+(0*(1*8)) 7 = ((2*0)-1)+8 12 = 2+(0!+(1+8)) \n",
+ " 3 = 2+(0*(1*8))! 8 = (2*0)+(1*8) 13 = ((2+0!)!-1)+8 \n",
+ " 4 = (2*-(0!+1))+8 9 = (2*0)+(1+8) 14 = 2*((0-1)+8) \n"
+ ]
+ }
+ ],
+ "source": [
+ "show((2,0,1,8), ops='+-*/_√!')"
]
},
{
@@ -1200,110 +1231,29 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "10 = (2.0+(1*8))\n",
- " 9 = ((2.0-1)+8)\n",
- " 8 = ((2.0-1)*8)\n",
- " 7 = (-2.0+(1+8))\n",
- " 6 = (-2.0+(1*8))\n",
- " 5 = (-(2.0+1)+8)\n",
- " 4 = (20*(1-.8))\n",
- " 3 = (2.0+(1^8))\n",
- " 2 = (20-18)\n",
- " 1 = (2^(.0*18))\n",
- " 0 = (2*(.0*18))\n",
- "\n",
- "\n",
- "10 = (20-(1+9))\n",
- " 9 = ((2.0-1)*9)\n",
- " 8 = (-2.0+(1+9))\n",
- " 7 = (-2.0+(1*9))\n",
- " 6 = (-(2.0+1)+9)\n",
- " 5 = (.20^(-1^9))\n",
- " 4 = ((-.20^-1)+9)\n",
- " 3 = (2.0+(1^9))\n",
- " 2 = (20/(1+9))\n",
- " 1 = (20-19)\n",
- " 0 = (2*(.0*19))\n",
- "\n",
- "\n",
- "10 = (2/0.20)\n",
- " 9 = ((20/2)-.0!)\n",
- " 8 = (2^(.0!+2.0))\n",
- " 7 = (2+(.0!/.20))\n",
- " 6 = (2*(.0!+2.0))\n",
- " 5 = ((2^.0)/.20)\n",
- " 4 = (20*.20)\n",
- " 3 = (2+(.02^.0))\n",
- " 2 = (2+(.0*20))\n",
- " 1 = (20/20)\n",
- " 0 = (20-20)\n",
- "\n",
- "\n",
- "10 = (20/(2*1))\n",
- " 9 = ((20/2)-1)\n",
- " 8 = (2.0^(2+1))\n",
- " 7 = (2+(0.2^-1))\n",
- " 6 = (2.0*(2+1))\n",
- " 5 = (2.0+(2+1))\n",
- " 4 = (20*(2*.1))\n",
- " 3 = (2.0+(2-1))\n",
- " 2 = (2+(.0*21))\n",
- " 1 = (-20+21)\n",
- " 0 = (2*(.0*21))\n",
- "\n",
- "\n",
- "10 = (20-(2/.2))\n",
- " 9 = ((20-2)/2)\n",
- " 8 = ((20/2)-2)\n",
- " 7 = (2+(.0!+(2+2)))\n",
- " 6 = (2.0+(2+2))\n",
- " 5 = (20/(2+2))\n",
- " 4 = (-20+(2+2)!)\n",
- " 3 = (2.0+(2/2))\n",
- " 2 = (-20+22)\n",
- " 1 = (20^(2-2))\n",
- " 0 = (20*(2-2))\n",
- "\n",
- "\n",
- "10 = (2.0*(2+3))\n",
- " 9 = (((2^.0)+2)*3)\n",
- " 8 = (2.0+(2*3))\n",
- " 7 = ((20/2)-3)\n",
- " 6 = ((20-2)/3)\n",
- " 5 = (.20^(2-3))\n",
- " 4 = (20/(2+3))\n",
- " 3 = (-20+23)\n",
- " 2 = (2+(.0*23))\n",
- " 1 = (.20*(2+3))\n",
- " 0 = (2*(.0*23))\n",
- "\n",
- "\n",
- "10 = (20*(2/4))\n",
- " 9 = ((20-2)/√4)\n",
- " 8 = (2.0+(2+4))\n",
- " 7 = (2.0+(2/.4))\n",
- " 6 = ((20/2)-4)\n",
- " 5 = ((2.0/2)+4)\n",
- " 4 = (-20+24)\n",
- " 3 = ((2.0/-2)+4)\n",
- " 2 = (2+(.0*24))\n",
- " 1 = (20*(.2/4))\n",
- " 0 = (2*(.0*24))\n",
- "\n",
- "\n"
+ "10: 2+(0+(1*8)) 20-(1+9) 20/(2+0) 20/(2*1) 20/√(2*2) 2*(0+(2+3)) 20*(2/4) \n",
+ " 9: (2*0)+(1+8) (2*0)+(1*9) (20/2)-0! (20/2)-1 (20-2)/2 2+(0!+(2*3)) (20-2)/√4 \n",
+ " 8: (2*0)+(1*8) ((2*0)-1)+9 2+(0+(2+0!)!) 2+(0+(2+1)!) (20/2)-2 2+(0+(2*3)) 2+(0+(2+4)) \n",
+ " 7: ((2*0)-1)+8 (2*(0-1))+9 (2*(0!+2))+0! (2*(0!+2))+1 2+(0!+(2*2)) (20/2)-3 2+((0*2)!+4) \n",
+ " 6: (2*(0-1))+8 -(2+(0+1))+9 2*(0+(2+0!)) 2*(0+(2+1)) 2+(0+(2*2)) (20-2)/3 (20/2)-4 \n",
+ " 5: -(2+(0+1))+8 20/(1+√9) 2+(0+(2+0!)) 2+(0+(2+1)) 20/(2*2) 2+((0*2)+3) (2/(0+2))+4 \n",
+ " 4: √(-2+(0+18)) (2*0)+(1+√9) 2*(0+(2+0)) 2*(0+(2*1)) 2*((0*2)+2) 20/(2+3) -20+24 \n",
+ " 3: 2+(0*18)! 2+(0*19)! 2+(0*20)! 2+((0*2)+1) 2+(0*22)! -20+23 2+(0*24)! \n",
+ " 2: 20-18 2+(0*19) 2+(0*20) 2+(0*21) -20+22 2+(0*23) 2+(0*24) \n",
+ " 1: (2*(0*18))! 20-19 20/20 -20+21 (2*(0*22))! (2*(0*23))! (2*(0*24))! \n",
+ " 0: 2*(0*18) 2*(0*19) 202*0 2*(0*21) 2*(0*22) 2*(0*23) 2*(0*24) \n"
]
}
],
"source": [
- "def countdown(years):\n",
- " \"\"\"Print a countdown using the digits of this year, in order.\"\"\"\n",
- " for year in years:\n",
- " table = expressions(tuple(int(d) for d in str(year)))\n",
- " for i in reversed(range(11)):\n",
- " print(f'{i:2d} = {table[i]}')\n",
- " print('\\n')\n",
+ "def countdowns(years):\n",
+ " \"\"\"Print a countdown of expressions with values from 10 to 0, using the digits (in order) of each year.\"\"\"\n",
+ " for i in reversed(range(11)):\n",
+ " def digits(year): return tuple(int(d) for d in str(year))\n",
+ " exps = [expressions(digits(year), ops='+-*/_√!,')[i] for year in years]\n",
+ " print(f'{i:2d}:', *[f'{unbracket(x):16}' for x in exps])\n",
" \n",
- "countdown(range(2018, 2025))"
+ "countdowns(range(2018, 2025))"
]
},
{
@@ -1314,7 +1264,7 @@
"source": [
"# Can you make 24?\n",
"\n",
- "In the [538 Riddler for July 10th 2020](https://fivethirtyeight.com/features/can-you-make-24/), Zach Wissner-Gross asks \"Can you make 24?\" from the digits (2, 3, 3, 4), in any order, with just the five binary operators. "
+ "In the [538 Riddler for July 10th 2020](https://fivethirtyeight.com/features/can-you-make-24/), Zach Wissner-Gross asks \"Can you make 24?\" from the digits (2, 3, 3, 4), in any order, with just the five binary operations. "
]
},
{
@@ -1406,13 +1356,13 @@
" '(((8*2)-8)*3)',\n",
" '((2^(8-3))-8)',\n",
" '((2^3)+(8+8))',\n",
- " '((8-(2+3))*8)',\n",
" '((8-(3+2))*8)',\n",
" '(3*((2*8)-8))',\n",
" '(3*((8*2)-8))',\n",
" '(3*((8+8)/2))',\n",
- " '(8*(8-(2+3)))',\n",
- " '(8*(8-(3+2)))'}"
+ " '(8*(8-(3+2)))',\n",
+ " '(8+((2^3)+8))',\n",
+ " '(8+(8+(2^3)))'}"
]
},
"execution_count": 29,
@@ -1642,7 +1592,7 @@
"text": [
"6 = (0!+(0!+0!))!\n",
"6 = (1+(1+1))!\n",
- "6 = (2+(2+2))\n",
+ "6 = (2+(2*2))\n",
"6 = ((3*3)-3)\n",
"6 = (4-(4/4))!\n",
"6 = (5+(5/5))\n",
@@ -1667,7 +1617,7 @@
"source": [
"# Even More Fours: The Power of Floor and Ceiling\n",
"\n",
- "With the standard set of `OPS`, we got all the integers up to 72 with four 4s. If we add the floor and ceiling operators, we get a big jump: suddenly, all the results of a division or a square root that didn't form an integer can now be coerced into integers. Instead of getting the integers only up to 72, we now get all the way up to 1644 (although it takes a full minute to get there, and I'll just show the first 100, to sace space):"
+ "With the standard set of `OPS`, we got all the integers up to 72 with four 4s. If we add the floor and ceiling operations, we get a big jump: suddenly, all the results of a division or a square root that didn't form an integer can now be coerced into integers. Instead of getting the integers only up to 72, we now get all the way up to 1644 (although it takes a full minute to get there, and I'll just show the first 200, to sace space): "
]
},
{
@@ -1682,40 +1632,65 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Can make 0 to 1644 with expressions((4, 4, 4, 4), ops=\"+-*/^_√!.,⌊⌈\", permute=False). [1,184,901 table entries]\n",
+ "Can make 0 to 1644 with expressions((4, 4, 4, 4), ops=\"+-*/^_√!.,⌊⌈\", permute=False). [1,205,301 table entries]\n",
"\n",
- " 0 = 44-44 25 = (4+(4*4!))/4 50 = 44+(4!/4) 75 = (4!+(4!/4))/.4 \n",
- " 1 = 44/44 26 = 4+(44/√4) 51 = 44+⌈√44⌉ 76 = (4*(4!-4))-4 \n",
- " 2 = 4*(4/(4+4)) 27 = 4+(4!-(4/4)) 52 = 4+(4+44) 77 = ⌈(44*(4^.4))⌉ \n",
- " 3 = (4+(4+4))/4 28 = 44-(4*4) 53 = 44+⌊(.4*4!)⌋ 78 = (4*(4!-4))-√4 \n",
- " 4 = ⌊4.444⌋ 29 = 4+(4!+(4/4)) 54 = 44+(4/.4) 79 = ⌈(4*(4!-4.4))⌉ \n",
- " 5 = ⌈4.444⌉ 30 = (4+(4+4))/.4 55 = 44/(.4+.4) 80 = 4*(4+(4*4)) \n",
- " 6 = 4+((4+4)/4) 31 = 4!+((4+4!)/4) 56 = 4*(4+(4/.4)) 81 = (4-(4/4))^4 \n",
- " 7 = (44/4)-4 32 = (4*4)+(4*4) 57 = ((.4+4!)/.4)-4 82 = √4+(4*(4!-4)) \n",
- " 8 = 4+(4+(4-4)) 33 = ⌊(4*(4+4.4))⌋ 58 = ((4^4)-4!)/4 83 = 44+⌊(.4^-4)⌋ \n",
- " 9 = 4+(4+(4/4)) 34 = 44-(4/.4) 59 = (4!/.4)-(4/4) 84 = (√4*44)-4 \n",
- " 10 = 44/4.4 35 = 4!+(44/4) 60 = 44+(4*4) 85 = (4!+(4/.4))/.4 \n",
- " 11 = 44/√(4*4) 36 = 44-(4+4) 61 = (4/4)+(4!/.4) 86 = (44/.4)-4! \n",
- " 12 = (4+44)/4 37 = 44-⌈√44⌉ 62 = (4*(4*4))-√4 87 = (√4*44)-⌈.4⌉ \n",
- " 13 = 4!-(44/4) 38 = 44-(4!/4) 63 = ((4^4)-4)/4 88 = 44+44 \n",
- " 14 = 4+(4+(4!/4)) 39 = 44-⌈4.4⌉ 64 = (4+4)*(4+4) 89 = ⌈(√4*44.4)⌉ \n",
- " 15 = 4+(44/4) 40 = 44-√(4*4) 65 = (4+(4^4))/4 90 = (4*4!)-(4!/4) \n",
- " 16 = .4*(44-4) 41 = ⌈44.4⌉-4 66 = √4+(4*(4*4)) 91 = (4*4!)-⌈4.4⌉ \n",
- " 17 = (4/4)+(4*4) 42 = √4+(44-4) 67 = 4!+(44-⌈.4⌉) 92 = 4+(√4*44) \n",
- " 18 = .4+(4*4.4) 43 = 44-(4/4) 68 = 4+(4*(4*4)) 93 = ⌊((4.4^4)/4)⌋ \n",
- " 19 = ⌊(44*.44)⌋ 44 = 4+(44-4) 69 = 4!+⌈44.4⌉ 94 = (4*(4!-.4))-.4 \n",
- " 20 = 4*(4+(4/4)) 45 = 44+(4/4) 70 = (4!+(4^4))/4 95 = (4*4!)-(4/4) \n",
- " 21 = (4+4.4)/.4 46 = 4+(44-√4) 71 = (4!+4.4)/.4 96 = √4*(4+44) \n",
- " 22 = √4*(44/4) 47 = 4+(44-⌈.4⌉) 72 = 4+(4!+44) 97 = (4/4)+(4*4!) \n",
- " 23 = ((4*4!)-4)/4 48 = 4*(4+(4+4)) 73 = 4+⌊(44/√.4)⌋ 98 = .4+(4*(.4+4!)) \n",
- " 24 = 4+(4+(4*4)) 49 = 4+⌈44.4⌉ 74 = 4+((4+4!)/.4) 99 = ⌈(.4*(4^4))⌉-4 \n",
- "CPU times: user 55.4 s, sys: 44.6 ms, total: 55.5 s\n",
- "Wall time: 55.5 s\n"
+ " 0 = 44-44 50 = 44+(4!/4) 100 = 44/.44 150 = (4!+(4!*4!))/4 \n",
+ " 1 = 44/44 51 = 44+⌈√44⌉ 101 = (4*4!)+⌈4.4⌉ 151 = 4+⌊(4^(4-.4))⌋ \n",
+ " 2 = 4*(4/(4+4)) 52 = 4+(4+44) 102 = (.4*(4^4))-.4 152 = (4*44)-4! \n",
+ " 3 = 4-(4^(4-4)) 53 = ⌊(4*√(4*44))⌋ 103 = (4*4!)+⌈√44⌉ 153 = ⌈(4/(.4^4))⌉-4 \n",
+ " 4 = 4+(4*(4-4)) 54 = 44+(4/.4) 104 = 4+(4+(4*4!)) 154 = 4+(4!/(.4*.4)) \n",
+ " 5 = 4+(4^(4-4)) 55 = 44/(.4+.4) 105 = (44-√4)/.4 155 = ⌊(4!*√44)⌋-4 \n",
+ " 6 = 4+((4+4)/4) 56 = 4!+(4*(4+4)) 106 = (44/.4)-4 156 = (4*4!)+(4!/.4) \n",
+ " 7 = (44/4)-4 57 = ((.4+4!)/.4)-4 107 = ⌈(4!*4.44)⌉ 157 = ⌈(44*(4!^.4))⌉ \n",
+ " 8 = 4+(4+(4-4)) 58 = ((4^4)-4!)/4 108 = (4*(4+4!))-4 158 = ⌊(44*(4-.4))⌋ \n",
+ " 9 = 4+(4+(4/4)) 59 = (4!/.4)-(4/4) 109 = (44-.4)/.4 159 = ⌈(44*(4-.4))⌉ \n",
+ " 10 = 44/4.4 60 = 44+(4*4) 110 = ⌊44.4⌋/.4 160 = 4*(44-4) \n",
+ " 11 = 44/√(4*4) 61 = (4/4)+(4!/.4) 111 = 444/4 161 = ⌈((4-.44)^4)⌉ \n",
+ " 12 = (4+44)/4 62 = (4*(4*4))-√4 112 = (4*4)+(4*4!) 162 = ⌈((4+(4!^4))^.4)⌉ \n",
+ " 13 = 4!-(44/4) 63 = ((4^4)-4)/4 113 = ⌈(.44*(4^4))⌉ 163 = ⌊((4-.4)^4)⌋-4 \n",
+ " 14 = 4+(4+(4!/4)) 64 = (4+4)*(4+4) 114 = 4+(44/.4) 164 = 44+⌈4.4⌉! \n",
+ " 15 = 4+(44/4) 65 = (4+(4^4))/4 115 = (√4+44)/.4 165 = 4+⌊(4!^(4*.4))⌋ \n",
+ " 16 = 4+(4+(4+4)) 66 = √4+(4*(4*4)) 116 = 4+(4*(4+4!)) 166 = ⌊(4!*√(4+44))⌋ \n",
+ " 17 = (4*4)+(4/4) 67 = ⌈(44/√.44)⌉ 117 = ⌊(4!^(4!/(4*4)))⌋ 167 = ⌈(4!*√(4+44))⌉ \n",
+ " 18 = (44/√4)-4 68 = 4+(4*(4*4)) 118 = (4+(4/4))!-√4 168 = 4*(44-√4) \n",
+ " 19 = 4!-(4+(4/4)) 69 = 4!+⌈44.4⌉ 119 = ⌈4.4⌉!-(4/4) 169 = ⌊((4^4)*√.44)⌋ \n",
+ " 20 = 4*(4+(4/4)) 70 = (4!+(4^4))/4 120 = (4+44)/.4 170 = (4!+44)/.4 \n",
+ " 21 = (4+4.4)/.4 71 = (4!+4.4)/.4 121 = (44/4)^√4 171 = 4+⌊((4-.4)^4)⌋ \n",
+ " 22 = √4*(44/4) 72 = 4+(4!+44) 122 = √4+(4+(4/4))! 172 = (4*44)-4 \n",
+ " 23 = 4!-(4^(4-4)) 73 = 4+⌊(44/√.4)⌋ 123 = ⌊(4.4*(4+4!))⌋ 173 = ⌈(4*(4+(.4^-4)))⌉ \n",
+ " 24 = 4+(4+(4*4)) 74 = 4+((4+4!)/.4) 124 = 4+(4+(4/4))! 174 = (4*44)-√4 \n",
+ " 25 = 4!+(4^(4-4)) 75 = (4!+(4!/4))/.4 125 = ⌈(44*√(4+4))⌉ 175 = (4*44)-⌈.4⌉ \n",
+ " 26 = 4+(44/√4) 76 = (4*(4!-4))-4 126 = ((4^4)-4)/√4 176 = 44*√(4*4) \n",
+ " 27 = 4+(4!-(4/4)) 77 = ⌈(44*(4^.4))⌉ 127 = ((4^4)-√4)/√4 177 = ⌊(4*44.4)⌋ \n",
+ " 28 = 44-(4*4) 78 = (4*(4!-4))-√4 128 = 4*(4*(4+4)) 178 = √4+(4*44) \n",
+ " 29 = 4+(4!+(4/4)) 79 = ⌈(4*(4!-4.4))⌉ 129 = (√4+(4^4))/√4 179 = ((4!/4)!-4)/4 \n",
+ " 30 = (4+(4+4))/.4 80 = 4*(4+(4*4)) 130 = (4+(4^4))/√4 180 = 4+(4*44) \n",
+ " 31 = 4!+((4+4!)/4) 81 = (4-(4/4))^4 131 = ⌈(4!*(4!/4.4))⌉ 181 = (4+(4!/4)!)/4 \n",
+ " 32 = (4*4)+(4*4) 82 = √4+(4*(4!-4)) 132 = 4+((4^4)/√4) 182 = √4+((4!/4)!/4) \n",
+ " 33 = ⌊(4*(4+4.4))⌋ 83 = 44+⌊(.4^-4)⌋ 133 = ⌈((4!-4)*√44)⌉ 183 = ⌈(4!*(4+(4-.4)))⌉ \n",
+ " 34 = 44-(4/.4) 84 = (√4*44)-4 134 = 4!+(44/.4) 184 = 4*(√4+44) \n",
+ " 35 = 4!+(44/4) 85 = (4!+(4/.4))/.4 135 = ⌈(4!*(4+(4*.4)))⌉ 185 = ⌊((4+4!)*√44)⌋ \n",
+ " 36 = 44-(4+4) 86 = (44/.4)-4! 136 = √4*(4!+44) 186 = (4!+(4!/4)!)/4 \n",
+ " 37 = 44-⌈√44⌉ 87 = (√4*44)-⌈.4⌉ 137 = ⌊((4-(4^-.4))^4)⌋ 187 = ⌊(4.4^4)⌋/√4 \n",
+ " 38 = 44-(4!/4) 88 = 44+44 138 = ((4!*4!)-4!)/4 188 = (4!*(4+4))-4 \n",
+ " 39 = 44-⌈4.4⌉ 89 = ⌈(√4*44.4)⌉ 139 = ⌊(4^(4-.44))⌋ 189 = ⌈((4+4)*(4!-.4))⌉ \n",
+ " 40 = 44-√(4*4) 90 = (4*4!)-(4!/4) 140 = 44+(4*4!) 190 = (4!*(4+4))-√4 \n",
+ " 41 = ⌈44.4⌉-4 91 = ⌈(444/√4!)⌉ 141 = ⌈(4*((.4^-4)-4))⌉ 191 = ⌊(4^((4+4!)^.4))⌋ \n",
+ " 42 = √4+(44-4) 92 = 4+(√4*44) 142 = (4!*(4!/4))-√4 192 = 4*(4+44) \n",
+ " 43 = 44-(4/4) 93 = ⌊((4.4^4)/4)⌋ 143 = ((4!*4!)-4)/4 193 = ⌊(44*4.4)⌋ \n",
+ " 44 = 4+(44-4) 94 = ⌈((4.4^4)/4)⌉ 144 = (4+(4+4))^√4 194 = ⌈(44*4.4)⌉ \n",
+ " 45 = 44+(4/4) 95 = (4*4!)-(4/4) 145 = (4+(4!*4!))/4 195 = ⌊(4*(√4!+44))⌋ \n",
+ " 46 = 4+(44-√4) 96 = √4*(4+44) 146 = (4!/(.4*.4))-4 196 = 4+(4!*(4+4)) \n",
+ " 47 = 4!+(4!-(4/4)) 97 = (4/4)+(4*4!) 147 = ⌊((4^4)/(4^.4))⌋ 197 = ⌈(44*√(4!-4))⌉ \n",
+ " 48 = 4*(4+(4+4)) 98 = 4+((4*4!)-√4) 148 = 4+(4!*(4!/4)) 198 = ⌈(4!*√(4!+44))⌉ \n",
+ " 49 = 4+⌈44.4⌉ 99 = ⌊(4^√(44/4))⌋ 149 = ⌊(.4*(4.4^4))⌋ 199 = ⌊(44^(.4+⌈.4⌉))⌋ \n",
+ "CPU times: user 57 s, sys: 93.1 ms, total: 57.1 s\n",
+ "Wall time: 57.1 s\n"
]
}
],
"source": [
- "%time show((4, 4, 4, 4), limit=100, ops=OPS + \"⌊⌈\")"
+ "%time show((4, 4, 4, 4), limit=200, ops=OPS + \"⌊⌈\")"
]
},
{
@@ -1725,11 +1700,11 @@
"tags": []
},
"source": [
- "We could get up to 2892 with `show((4, 4, 4, 4), ops=OPS + \"⌊⌈\", nesting=3)`.\n",
+ "We could get up to 2892 with nesting level 3.\n",
"\n",
"# Even More Fives: Five 5s\n",
"\n",
- "In the [xkcd forum](http://forums.xkcd.com/viewtopic.php?f=14&t=116813&start=280) they took up the problem of **five 5s** and got all the integers up to 298, using the \"[double factorial function](https://en.wikipedia.org/wiki/Double_factorial)\" and [π function](https://en.wikipedia.org/wiki/Prime-counting_function). We can get up to 171, using just the default operators, but with five digits it does take about seven minutes, whereas all the other puzzles with four or three digits (and without floor and ceiling) took less than a minute. I suspect you could go much further using floor and ceiling, but that computation would take even longer, so for now let's stick with our default set of operations:"
+ "In the [xkcd forum](http://forums.xkcd.com/viewtopic.php?f=14&t=116813&start=280) they took up the problem of **five 5s** and got all the integers up to 298, using the \"[double factorial function](https://en.wikipedia.org/wiki/Double_factorial)\" and [π function](https://en.wikipedia.org/wiki/Prime-counting_function). We can get up to 171, using just the default operations, but with five digits it does take about seven minutes, whereas all the other puzzles with four or three digits (and without floor and ceiling) took less than a minute. I suspect you could go much further using floor and ceiling, but that computation would take even longer, so for now let's stick with our default set of operations:"
]
},
{
@@ -1744,53 +1719,53 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Can make 0 to 171 with expressions((5, 5, 5, 5, 5), ops=\"+-*/^_√!.,\", permute=False). [14,809,921 table entries]\n",
+ "Can make 0 to 171 with expressions((5, 5, 5, 5, 5), ops=\"+-*/^_√!.,\", permute=False). [14,827,653 table entries]\n",
"\n",
- " 0 = 5*(55-55) 43 = 55-(5!/(5+5)) 86 = (55/.5)-(5!/5) 129 = (5!-55.5)/.5 \n",
- " 1 = 5^(55-55) 44 = 55-(55/5) 87 = (555-5!)/5 130 = 5!+(55/5.5) \n",
+ " 0 = 5*(55-55) 43 = 55-(5!/(5+5)) 86 = (55/.5)-(5!/5) 129 = 5+(5+(5!-(5/5))) \n",
+ " 1 = 5^(55-55) 44 = 55-(55/5) 87 = (555-5!)/5 130 = 5+((5^5)/(5*5)) \n",
" 2 = 55/(5*5.5) 45 = (5*5!)-555 88 = 5*(.55/(.5^5)) 131 = (55+(5*5!))/5 \n",
- " 3 = .5*((55/5)-5) 46 = 55+((.5-5)/.5) 89 = 5!+((5!/5)-55) 132 = 5!+((5+55)/5) \n",
- " 4 = 5-(55/55) 47 = 5+(5+(5+(.5^-5))) 90 = (55-(5+5))/.5 133 = (5+(5!*5.5))/5 \n",
- " 5 = 5+(55-55) 48 = 5!/(5*(5.5-5)) 91 = (5*5)+(5!*.55) 134 = (5!/5)+(55/.5) \n",
+ " 3 = (5+(5*5))/(5+5) 46 = 55+((.5-5)/.5) 89 = 5!+((5!/5)-55) 132 = 5!+((5+55)/5) \n",
+ " 4 = 5-(55/55) 47 = 5+(5+(5+(.5^-5))) 90 = 5!+((5*5)-55) 133 = (5+(5!*5.5))/5 \n",
+ " 5 = 5+(55-55) 48 = 5!*((5+5)/(5*5)) 91 = (5*5)+(5!*.55) 134 = (5!/5)+(55/.5) \n",
" 6 = 5+(55/55) 49 = 55-(5+(5/5)) 92 = 5+(55+(.5^-5)) 135 = (5!+555)/5 \n",
- " 7 = ((5+55)/5)-5 50 = 55.5-5.5 93 = .5+(5!-(5*5.5)) 136 = 5+(5!+(55/5)) \n",
- " 8 = .5*(5+(55/5)) 51 = 55+((5/5)-5) 94 = 5!-((5/5)+(5*5)) 137 = (5*(5*5.5))-.5 \n",
+ " 7 = ((5+55)/5)-5 50 = 5+(55-(5+5)) 93 = (5*(5*5))-(.5^-5) 136 = 5+(5!+(55/5)) \n",
+ " 8 = 5+((5+(5+5))/5) 51 = 55+((5/5)-5) 94 = 5!-((5*5)+(5/5)) 137 = (5*(5*5.5))-.5 \n",
" 9 = 5!-(555/5) 52 = 55-(.5+(5*.5)) 95 = (55/.55)-5 138 = .5+(5*(5*5.5)) \n",
" 10 = 5!-(55+55) 53 = 55-((5+5)/5) 96 = 5!+((5/5)-(5*5)) 139 = ((5+(5/5))!/5)-5 \n",
- " 11 = 5*(55/(5*5)) 54 = ((5*55)-5)/5 97 = 5!+((.5^-5)-55) 140 = .5*(5+(5*55)) \n",
+ " 11 = 5*(55/(5*5)) 54 = 55-(5^(5-5)) 97 = 5!+((.5^-5)-55) 140 = .5*(5+(5*55)) \n",
" 12 = (5/5)+(55/5) 55 = .5*(55+55) 98 = 5!-(55/(5*.5)) 141 = 5!+((5+5.5)/.5) \n",
- " 13 = (5+(5+55))/5 56 = (5+(5*55))/5 99 = (55-5.5)/.5 142 = 5!+(55/(5*.5)) \n",
- " 14 = (5*5)-(55/5) 57 = 55+((5+5)/5) 100 = (55/.5)-(5+5) 143 = ((5+(5/5))!-5)/5 \n",
+ " 13 = (5+(5+55))/5 56 = 55+(5^(5-5)) 99 = (55-5.5)/.5 142 = 5!+(55/(5*.5)) \n",
+ " 14 = (5*5)-(55/5) 57 = 55+((5+5)/5) 100 = 5*(5+(5+(5+5))) 143 = ((5+(5/5))!-5)/5 \n",
" 15 = 5+(55/5.5) 58 = (5*.5)+55.5 101 = (55.5-5)/.5 144 = ((55/5)-5)!/5 \n",
- " 16 = (55+(5*5))/5 59 = 5+(55-(5/5)) 102 = (.5+(5*5))/(.5*.5) 145 = (5*(5+(5*5)))-5 \n",
- " 17 = 5+((5+55)/5) 60 = 5+(5+(55-5)) 103 = 55+(5!/(5*.5)) 146 = 5!+((5/5)+(5*5)) \n",
+ " 16 = (55+(5*5))/5 59 = 5+(55-(5/5)) 102 = 5+(5!+((5-5!)/5)) 145 = (5*(5+(5*5)))-5 \n",
+ " 17 = 5+((5+55)/5) 60 = 5+(5+(55-5)) 103 = 55+(5!/(5*.5)) 146 = 5!+((5*5)+(5/5)) \n",
" 18 = 5+((5!-55)/5) 61 = 5.5+55.5 104 = 5!-(5+(55/5)) 147 = 5!+((5*5.5)-.5) \n",
" 19 = (5*5)-(5+(5/5)) 62 = (55-(5!/5))/.5 105 = 55+(55-5) 148 = .5+(5!+(5*5.5)) \n",
" 20 = 55/(5*.55) 63 = .5+(5*(5*(5*.5))) 106 = (555/5)-5 149 = (5!/5)+(5*(5*5)) \n",
" 21 = 5+(5+(55/5)) 64 = .5^(5-(55/5)) 107 = 5!+((55-5!)/5) 150 = 5*(55-(5*5)) \n",
- " 22 = (55+55)/5 65 = 5+(5!-(5+55)) 108 = (55-(5/5))/.5 151 = 5!+(55-(5!/5)) \n",
- " 23 = (5+(55/.5))/5 66 = 55+(55/5) 109 = (55/.5)-(5/5) 152 = 5!+((5.5-5)^-5) \n",
- " 24 = (5-(55/55))! 67 = 55+(5!/(5+5)) 110 = (555-5)/5 153 = 5!+(.5*(5!*.55)) \n",
+ " 22 = (55+55)/5 65 = 5+(5!-(5+55)) 108 = 5!-((5+55)/5) 151 = 5!+(55-(5!/5)) \n",
+ " 23 = (5*5)-((5+5)/5) 66 = 55+(55/5) 109 = ((5*5!)-55)/5 152 = 5!+(((5+5)/5)^5) \n",
+ " 24 = (5-(55/55))! 67 = 55+(5!/(5+5)) 110 = (555-5)/5 153 = 5!+((5/5)+(.5^-5)) \n",
" 25 = 55-(5+(5*5)) 68 = 5.5+(.5*(5+5!)) 111 = 555/√(5*5) 154 = 5+(5+(5!+(5!/5))) \n",
- " 26 = 5*(5+(5/(5*5))) 69 = 5+(.5^-(5+(5/5))) 112 = (5+555)/5 155 = 5+(5*(5+(5*5))) \n",
+ " 26 = (5*5)+(5^(5-5)) 69 = 5+(.5^-(5+(5/5))) 112 = (5+555)/5 155 = 5+(5*(5+(5*5))) \n",
" 27 = 5+(55/(5*.5)) 70 = 5+(5+(5+55)) 113 = 5!-(5+((5+5)/5)) 156 = (5!+(5!*5.5))/5 \n",
" 28 = .5*(55+(5/5)) 71 = 55+(.5/(.5^5)) 114 = 5+(5!-(55/5)) 157 = (.5^-5)+(5*(5*5)) \n",
" 29 = 5+((5*5)-(5/5)) 72 = (5+(5/5))!/(5+5) 115 = 5+(55+55) 158 = (55+(5!/5))/.5 \n",
" 30 = 5*((55/5)-5) 73 = (5*5)+(5!/(5*.5)) 116 = 5+(555/5) 159 = (5/(.5^5))-(5/5) \n",
- " 31 = 5+((5/5)+(5*5)) 74 = 55+((5!/5)-5) 117 = 5!-((5+(5+5))/5) 160 = (55+(5*5))/.5 \n",
- " 32 = (5+(55/5))/.5 75 = 55+((5*5)-5) 118 = 5!-(5!/(5+55)) 161 = (5/5)+(5/(.5^5)) \n",
+ " 31 = 5+((5*5)+(5/5)) 74 = 55+((5!/5)-5) 117 = 5!-((5+(5+5))/5) 160 = 5+((5*55)-5!) \n",
+ " 32 = ((5+5)^5)/(5^5) 75 = 55+((5*5)-5) 118 = 5!-(5!/(5+55)) 161 = (5/5)+(5/(.5^5)) \n",
" 33 = .55*(5+55) 76 = 5+(5+(5!*.55)) 119 = 5!-(55/55) 162 = 5+(5+(5!+(.5^-5))) \n",
- " 34 = (5!+(55-5))/5 77 = 5+(5!*(.5+(.5/5))) 120 = 5!+(55-55) 163 = .5+(5*(.5+(.5^-5)))\n",
+ " 34 = (5!+(55-5))/5 77 = 5+(.5*(5!+(5!/5))) 120 = 5!+(55-55) 163 = 5!+((5!/(5*.5))-5) \n",
" 35 = 5+(55-(5*5)) 78 = 55+((5!-5)/5) 121 = 5!+(55/55) 164 = 5!+(.5*(5!-(.5^-5)))\n",
" 36 = (5*5)+(55/5) 79 = (5!+(5*55))/5 122 = 5!+(5!/(5+55)) 165 = 55+(55/.5) \n",
- " 37 = 5+((5.5-5)^-5) 80 = 5*(5+(55/5)) 123 = 5!+((5+(5+5))/5) 166 = 5!+((5!-5)/(5*.5)) \n",
- " 38 = 5+(.5*(5!*.55)) 81 = 5+(.5*(5!+(.5^-5))) 124 = (5*(5*5))-(5/5) 167 = 5!+(((5!/.5)-5)/5) \n",
- " 39 = ((5*5)-5.5)/.5 82 = 55+((.5^-5)-5) 125 = 5*(.5*(55-5)) 168 = (5!+(5+(5/5))!)/5 \n",
+ " 37 = 5+(((5+5)/5)^5) 80 = 5*(5+(55/5)) 123 = 5!+((5+(5+5))/5) 166 = 5!+((5!-5)/(5*.5)) \n",
+ " 38 = 5+((5/5)+(.5^-5)) 81 = 5+(.5*(5!+(.5^-5))) 124 = (5*(5*5))-(5/5) 167 = 5!+((5!+(5!-5))/5) \n",
+ " 39 = 5+(5+(5+(5!/5))) 82 = 55+((.5^-5)-5) 125 = 5*(5*(5+(5-5))) 168 = (5!+(5+(5/5))!)/5 \n",
" 40 = 55-(5+(5+5)) 83 = (.5*5!)+((5!-5)/5) 126 = (5/5)+(5*(5*5)) 169 = 5!+((5*5)+(5!/5)) \n",
- " 41 = (5!*.55)-(5*5) 84 = 5+(55+(5!/5)) 127 = (5!*(5.5/5))-5 170 = 5!+((5*5)+(5*5)) \n",
+ " 41 = (5!*.55)-(5*5) 84 = 5+(55+(5!/5)) 127 = 5+(5!+((5+5)/5)) 170 = 5!+((5*5)+(5*5)) \n",
" 42 = (5+5.5)/(.5*.5) 85 = 5+(55+(5*5)) 128 = (5-(5/5))/(.5^5) 171 = (5.5/(.5^5))-5 \n",
- "CPU times: user 8min 21s, sys: 938 ms, total: 8min 22s\n",
- "Wall time: 8min 22s\n"
+ "CPU times: user 8min 42s, sys: 4.86 s, total: 8min 47s\n",
+ "Wall time: 34min 53s\n"
]
}
],
@@ -1806,7 +1781,7 @@
"source": [
"# f(n): Unmakeable from (1, ..., *n*)\n",
"\n",
- "The facebook group \"actually good math problems\" posed the problem of determining the values of `f(n)`, which is defined to be the smallest positive integer that can not be made from, in our terms, `expressions(range(n + 1), '+-*/^,', permute=True)`. Computing up to `f(5)` is fast, but `f(6)` requires hundreds of billions of combinations and 100 minutes to run; it would take some work to make it more efficient, or to move on to `f(7)`."
+ "The facebook group \"actually good math problems\" posed the problem of determining the values of `f(n)`, which is defined to be the smallest positive integer that can not be made from (in our terms) `expressions(range(n + 1), '+-*/^,', permute=True)`. Computing up to `f(5)` is fast, but computing that `f(6) = 9562` requires hundreds of billions of combinations and 100 minutes to run; it would take some work to make it more efficient, or to move on to `f(7)`."
]
},
{
@@ -1835,16 +1810,12 @@
"f(2) = 4\n",
"f(3) = 25\n",
"f(4) = 175\n",
- "f(5) = 1099\n",
- "f(6) = 9562\n",
- "CPU times: user 1h 38min 47s, sys: 4.47 s, total: 1h 38min 52s\n",
- "Wall time: 1h 42min 19s\n"
+ "f(5) = 1099\n"
]
}
],
"source": [
- "%%time\n",
- "for n in range(7):\n",
+ "for n in range(6):\n",
" print(f'f({n}) = {f(n)}')"
]
},
@@ -1856,7 +1827,7 @@
"source": [
"# What's Next?\n",
"\n",
- "One exercise would be adding even more operators, such as:\n",
+ "One exercise would be adding even more operations, such as:\n",
"\n",
"- **Nth root**: `3√8` = 2\n",
"- **Percent**: `50%` = 0.5\n",