Add files via upload

This commit is contained in:
Peter Norvig 2021-02-25 18:17:52 -08:00 committed by GitHub
parent 8f387748de
commit 50c728316a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -16,7 +16,7 @@
"Sample puzzle:\n", "Sample puzzle:\n",
"\n", "\n",
" \n", " \n",
"| 6615 | 15552 |   420 | [6x3] |\n", "| 6615 | 15552 |   420 | [6×3] |\n",
"|-------|-------|-------|---|\n", "|-------|-------|-------|---|\n",
"| ? | ? | ? |**210**|\n", "| ? | ? | ? |**210**|\n",
"| ? | ? | ? |**144**|\n", "| ? | ? | ? |**144**|\n",
@ -28,7 +28,7 @@
"\n", "\n",
"Solution:\n", "Solution:\n",
"\n", "\n",
"|6615|15552|  420| [6x3]|\n", "|6615|15552|  420| [6×3]|\n",
"|---|---|---|---|\n", "|---|---|---|---|\n",
"|7|6|5|**210**|\n", "|7|6|5|**210**|\n",
"|9|8|2|**144**|\n", "|9|8|2|**144**|\n",
@ -42,7 +42,7 @@
"\n", "\n",
"\n", "\n",
"\n", "\n",
"\n", "We could solve CrossProduct puzzles by hand, but why not write a program to do it?\n",
" \n", " \n",
"# Data type definitions\n", "# Data type definitions\n",
" \n", " \n",
@ -100,13 +100,15 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# Filling in one row\n", "# Strategy\n",
"\n", "\n",
"A first step in solving the puzzle is filling in a single row of the table.\n", "Here's my strategy:\n",
"- To solve a puzzle, first find all ways to fill the first row, and for each way, solve the rest of the puzzle.\n",
"- To fill a row, first find all ways to fill the first digit, and for each way, fill the rest of the row.\n",
"\n", "\n",
"`fill_one_row(row_prod=210, col_prods=[6615, 15552, 420])` will return a set of 3-digit tuples where each digit of a tuple evenly divides both `row_prod` and the corresponding number in `col_prods`, and together the digits in a tuple multiply to `row_prod`.\n", "So the first step is to define `fill_one_row(row_prod, col_prods)` to return a set of digit-tuples that can legally fill a row that has the given row product in a puzzle with the given column products. \n",
" - If `col_prods` is empty, then there is one solution (the 0-length tuple) if `row_prod` is 1, and no solution otherwise.\n", " - If `col_prods` is empty, then there is one solution (the 0-length tuple) if `row_prod` is 1, and no solution otherwise.\n",
" - Otherwise, try each digit `d` that divides both the `row_prod` and the first `col_prods`, and then try all ways to fill the rest of the row." " - Otherwise, try each digit `d` that divides both the `row_prod` and the first number in `col_prods`, and then try all ways to fill the rest of the row."
] ]
}, },
{ {
@ -149,7 +151,7 @@
} }
], ],
"source": [ "source": [
"fill_one_row(210, [6615, 15552, 420]) # There are two ways to fill the first row" "fill_one_row(210, [6615, 15552, 420]) # There are 2 ways to fill this row"
] ]
}, },
{ {
@ -160,7 +162,14 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"{(9, 9, 9)}" "{(1, 9, 6),\n",
" (3, 3, 6),\n",
" (3, 6, 3),\n",
" (3, 9, 2),\n",
" (9, 1, 6),\n",
" (9, 2, 3),\n",
" (9, 3, 2),\n",
" (9, 6, 1)}"
] ]
}, },
"execution_count": 5, "execution_count": 5,
@ -169,96 +178,17 @@
} }
], ],
"source": [ "source": [
"fill_one_row(729, [90, 90, 90])" "fill_one_row(54, [6615, 15552, 420]) # There are 8 ways to fill this row"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(1, 9, 9, 9),\n",
" (3, 3, 9, 9),\n",
" (3, 9, 3, 9),\n",
" (3, 9, 9, 3),\n",
" (9, 1, 9, 9),\n",
" (9, 3, 3, 9),\n",
" (9, 3, 9, 3),\n",
" (9, 9, 1, 9),\n",
" (9, 9, 3, 3),\n",
" (9, 9, 9, 1)}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fill_one_row(729, [90, 90, 90, 90])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(1, 9, 9, 9), (3, 3, 9, 9), (3, 9, 3, 9), (3, 9, 9, 3)}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fill_one_row(729, [30, 90, 90, 90])"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(1, 7, 7, 7, 7, 7, 7, 7, 7),\n",
" (7, 1, 7, 7, 7, 7, 7, 7, 7),\n",
" (7, 7, 1, 7, 7, 7, 7, 7, 7),\n",
" (7, 7, 7, 1, 7, 7, 7, 7, 7),\n",
" (7, 7, 7, 7, 1, 7, 7, 7, 7),\n",
" (7, 7, 7, 7, 7, 1, 7, 7, 7),\n",
" (7, 7, 7, 7, 7, 7, 1, 7, 7),\n",
" (7, 7, 7, 7, 7, 7, 7, 1, 7),\n",
" (7, 7, 7, 7, 7, 7, 7, 7, 1)}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fill_one_row(7**8, [7]*9)"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# Solving a whole puzzle\n", "Now we can solve the rest of a puzzle:\n",
"\n", "\n",
"- We can solve a whole puzzle with a similar strategy to filling a row.\n", "- `solve(puzzle)` finds the first solution. (A well-formed puzzle has exactly one solution, but some might have more, or none.)\n",
"- For every possible way of filling the first row, try every way of recursively solving the rest of the puzzle. \n", "- `solutions(puzzle)` yields all possible solutions to a puzzle. There are three main cases to consider:\n",
"- `solve` finds the first solution to a puzzle. (A well-formed puzzle has exactly one solution, but some might have more or less.)\n",
"- `solutions` yields all possible solutions to a puzzle. There are three main cases to consider:\n",
" - A puzzle with no rows has the empty table, `[]`, as a solution, as long as the column products are all 1.\n", " - A puzzle with no rows has the empty table, `[]`, as a solution, as long as the column products are all 1.\n",
" - A puzzle with rows might have solutions, as long as the column products are all integers. Call `fill_row` to get all possible ways to fill the first row, and for each one recursively call `solutions` to get all the possible ways of filling the rest of the rows (making sure to pass in an altered `col_prods` where each element is divided by the corresponding element in the first row).\n", " - A puzzle with rows might have solutions, as long as the column products are all integers. Call `fill_row` to get all possible ways to fill the first row, and for each one recursively call `solutions` to get all the possible ways of filling the rest of the rows (making sure to pass in an altered `col_prods` where each element is divided by the corresponding element in the first row).\n",
" - Otherwise there are no solutions." " - Otherwise there are no solutions."
@ -266,7 +196,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 6,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -291,12 +221,12 @@
"source": [ "source": [
"# Solutions\n", "# Solutions\n",
"\n", "\n",
"Here are solutions to the puzzles posed by *The Riddler*:" "Here are solutions to the three puzzles posed by *The Riddler*:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 7,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -307,7 +237,7 @@
" [(7, 8, 5), (3, 8, 7), (9, 6, 3), (9, 8, 5), (3, 5, 4), (4, 8, 8), (9, 2, 7)]]" " [(7, 8, 5), (3, 8, 7), (9, 6, 3), (9, 8, 5), (3, 5, 4), (4, 8, 8), (9, 2, 7)]]"
] ]
}, },
"execution_count": 10, "execution_count": 7,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -320,62 +250,79 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# Prettier solutions\n", "Those are the correct solutions. However, we could make them look nicer.\n",
"\n", "\n",
"Those are the correct solutions. However, we could make them look nicer:" "# Prettier solutions"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 8,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"from IPython.display import Markdown\n", "from IPython.display import Markdown, display\n",
"\n", "\n",
"def show(puzzles): return Markdown('\\n\\n'.join(map(pretty, puzzles)))\n", "def pretty(puzzle) -> Markdown:\n",
"\n", " \"\"\"A puzzle and its solution in pretty Markdown format.\"\"\"\n",
"def pretty(puzzle, table=None) -> str:\n",
" \"\"\"A puzzle with the filled-in table as a pretty Markdown str.\"\"\"\n",
" row_prods, col_prods = puzzle\n", " row_prods, col_prods = puzzle\n",
" table = table or solve(puzzle)\n", " head = row(col_prods + [f'[{len(row_prods)}×{len(col_prods)}]'])\n",
" head = surround(col_prods + [f'[{len(row_prods)}x{len(col_prods)}]'])\n", " dash = row(['---'] * (1 + len(col_prods)))\n",
" dash = surround(['---'] * (1 + len(col_prods)))\n", " rest = [row(r + (f'**{rp}**',))\n",
" rest = [surround(row + (f'**{rp}**',))\n", " for r, rp in zip(solve(puzzle), row_prods)]\n",
" for row, rp in zip(table, row_prods)]\n", " return Markdown('\\n'.join([head, dash, *rest]))\n",
" return '\\n'.join([head, dash, *rest])\n",
"\n", "\n",
"def surround(items, delim='|') -> str: \n", "def row(items) -> str: \n",
" \"\"\"Like delim.join, but delimiter is outside items as well as between.\"\"\"\n", " \"\"\"Make a markdown table row.\"\"\"\n",
" return delim + delim.join(map(str, items)) + delim" " return '|' + '|'.join(map(str, items)) + '|'"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 9,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"text/markdown": [ "text/markdown": [
"|3000|3969|640|[5x3]|\n", "|3000|3969|640|[5×3]|\n",
"|---|---|---|---|\n", "|---|---|---|---|\n",
"|3|9|5|**135**|\n", "|3|9|5|**135**|\n",
"|5|9|1|**45**|\n", "|5|9|1|**45**|\n",
"|8|1|8|**64**|\n", "|8|1|8|**64**|\n",
"|5|7|8|**280**|\n", "|5|7|8|**280**|\n",
"|5|7|2|**70**|\n", "|5|7|2|**70**|"
"\n", ],
"|6615|15552|420|[6x3]|\n", "text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"|6615|15552|420|[6×3]|\n",
"|---|---|---|---|\n", "|---|---|---|---|\n",
"|7|6|5|**210**|\n", "|7|6|5|**210**|\n",
"|9|8|2|**144**|\n", "|9|8|2|**144**|\n",
"|3|9|2|**54**|\n", "|3|9|2|**54**|\n",
"|5|9|3|**135**|\n", "|5|9|3|**135**|\n",
"|1|4|1|**4**|\n", "|1|4|1|**4**|\n",
"|7|1|7|**49**|\n", "|7|1|7|**49**|"
"\n", ],
"|183708|245760|117600|[7x3]|\n", "text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"|183708|245760|117600|[7×3]|\n",
"|---|---|---|---|\n", "|---|---|---|---|\n",
"|7|8|5|**280**|\n", "|7|8|5|**280**|\n",
"|3|8|7|**168**|\n", "|3|8|7|**168**|\n",
@ -389,13 +336,13 @@
"<IPython.core.display.Markdown object>" "<IPython.core.display.Markdown object>"
] ]
}, },
"execution_count": 12,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "display_data"
} }
], ],
"source": [ "source": [
"show(puzzles)" "for p in puzzles:\n",
" display(pretty(p))"
] ]
}, },
{ {
@ -413,7 +360,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": 10,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -441,16 +388,16 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14, "execution_count": 11,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"[(2, 8, 7), (3, 3, 7), (9, 3, 3), (6, 1, 1), (3, 5, 9)]" "[(6, 1, 7), (9, 7, 3), (9, 3, 5), (5, 6, 9), (6, 6, 4)]"
] ]
}, },
"execution_count": 14, "execution_count": 11,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -461,7 +408,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 12,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -470,7 +417,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 13,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -479,7 +426,7 @@
"False" "False"
] ]
}, },
"execution_count": 16, "execution_count": 13,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -490,31 +437,51 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 17, "execution_count": 14,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"text/markdown": [
"|972|360|1323|[5x3]|\n",
"|---|---|---|---|\n",
"|2|8|7|**112**|\n",
"|3|3|7|**63**|\n",
"|3|3|9|**81**|\n",
"|6|1|1|**6**|\n",
"|9|5|3|**135**|"
],
"text/plain": [ "text/plain": [
"<IPython.core.display.Markdown object>" "4"
] ]
}, },
"execution_count": 17, "execution_count": 14,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"show([puz])" "len(list(solutions(puz)))"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"|14580|756|3780|[5×3]|\n",
"|---|---|---|---|\n",
"|6|1|7|**42**|\n",
"|9|7|3|**189**|\n",
"|5|3|9|**135**|\n",
"|9|6|5|**270**|\n",
"|6|6|4|**144**|"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pretty(puz)"
] ]
}, },
{ {
@ -526,7 +493,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 18, "execution_count": 16,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -563,15 +530,15 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 19, "execution_count": 17,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 2.01 s, sys: 25.5 ms, total: 2.04 s\n", "CPU times: user 1.56 s, sys: 48.6 ms, total: 1.6 s\n",
"Wall time: 2.05 s\n" "Wall time: 1.56 s\n"
] ]
}, },
{ {
@ -580,7 +547,7 @@
"True" "True"
] ]
}, },
"execution_count": 19, "execution_count": 17,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -598,21 +565,21 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 20, "execution_count": 18,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 3.64 s, sys: 26.5 ms, total: 3.67 s\n", "CPU times: user 3.03 s, sys: 10.3 ms, total: 3.04 s\n",
"Wall time: 3.69 s\n" "Wall time: 3.03 s\n"
] ]
}, },
{ {
"data": { "data": {
"text/markdown": [ "text/markdown": [
"|24576|979776|274400|2177280|1792000|524880|[10x6]|\n", "|24576|979776|274400|2177280|1792000|524880|[10×6]|\n",
"|---|---|---|---|---|---|---|\n", "|---|---|---|---|---|---|---|\n",
"|4|1|5|6|2|2|**480**|\n", "|4|1|5|6|2|2|**480**|\n",
"|1|7|1|3|4|3|**252**|\n", "|1|7|1|3|4|3|**252**|\n",
@ -629,25 +596,26 @@
"<IPython.core.display.Markdown object>" "<IPython.core.display.Markdown object>"
] ]
}, },
"execution_count": 20, "execution_count": 18,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"%time show(random_puzzles(1, 10, 6))" "[p10x6] = random_puzzles(1, 10, 6)\n",
"%time pretty(p10x6)"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"In general, the time to solve a puzzle can grow exponentially in the number of cells. Consider a row in a six-column puzzle, where the products are all 5040. That gives us 3,960 possibilities: " "In general, the time to solve a puzzle can grow exponentially in the number of cells. Consider a row in a six-column puzzle, where the products are all 5040. There are 3,960 ways to fill this row: "
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 21, "execution_count": 19,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -656,7 +624,7 @@
"3960" "3960"
] ]
}, },
"execution_count": 21, "execution_count": 19,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -675,17 +643,17 @@
"# Faster Speed\n", "# Faster Speed\n",
"\n", "\n",
"To speed things up, we could encode the puzzle as a constraint satisfaction problem (CSP), and use a highly-optimized [CSP solver](https://developers.google.com/optimization/cp/cp_solver). But even without going to a professional-grade CSP solver, we could borrow the heuristics they use. There are four main considerations in CSP solving:\n", "To speed things up, we could encode the puzzle as a constraint satisfaction problem (CSP), and use a highly-optimized [CSP solver](https://developers.google.com/optimization/cp/cp_solver). But even without going to a professional-grade CSP solver, we could borrow the heuristics they use. There are four main considerations in CSP solving:\n",
"- **Variable definition**: In `solutions`, we are treating each row as a variable, and asking \"which of the possible values returned by `fill_one_row` will work as the value of this row? An alternative would be to treat each cell as a variable, and fill in the puzzle one cell at a time rather than one row at a time. This has the advantage that each variable has only 9 possible values, not thousands of possibilities.\n", "- **Variable definition**: In `solutions`, we are treating each **row** as a variable, and asking \"which of the possible values returned by `fill_one_row` will work as the value of this row? An alternative would be to treat each **cell** as a variable, and fill in the puzzle one cell at a time rather than one row at a time. This has the advantage that each variable has only 9 possible values, not thousands of possibilities.\n",
"- **Variable ordering**: In `solutions`, we consider the variables (the rows) in strict top-to-bottom order. It is usually more efficient to reorder the variables, filling in first the variable with the minimum number of possible values. The reasoning is that if you have a variable with only 2 possibilities, you have a 50% chance of guessing right the first time, whereas if there were 100 possibilities, you have only a 1% chance of guessing right.\n", "- **Variable ordering**: In `solutions`, we consider the variables (the rows) in strict top-to-bottom order. It is usually more efficient to reorder the variables, filling in first the variable with the minimum number of possible values. The reasoning is that if you have a variable with only 2 possibilities, you have a 50% chance of guessing right the first time, whereas if there were 100 possibilities, you have only a 1% chance of guessing right.\n",
"- **Value ordering**: The function `fill_one_row` returns values in sorted lexicographic order, lowest first. If we only care about finding the first solution, then we should reorder the values to pick the one that imposes the least constraints first (that is, allows the most possibilities for the other variables).\n", "- **Value ordering**: The function `fill_one_row` returns values in sorted lexicographic order, lowest first. We could reorder the values to pick the one that imposes the least constraints first (that is, the value that allows the most possibilities for the other variables).\n",
"- **Domain-specific heuristics**: CSP solvers are general, but sometimes knowledge that is specific to a problem can be helpful. One fact about CrossProduct is that the digits 5 and 7 are special in the sense that if a row (or column) product is divisible by 5 (or 7), then the digit 5 (or 7) must appear in the row (or column). That is not true for the other digits (for example, if a row product is divisible by 8, then an 8 may appear in the row, or it might be a 2 and a 4, or three 6s, etc.).\n", "- **Domain-specific heuristics**: CSP solvers are general, but sometimes knowledge that is specific to a problem can be helpful. One fact about CrossProduct is that the digits 5 and 7 are special in the sense that if a row (or column) product is divisible by 5 (or 7), then the digit 5 (or 7) must appear in the row (or column). That is not true for the other digits (for example, if a row product is divisible by 8, then an 8 may appear in the row, or it might be a 2 and a 4, or three 6s, etc.).\n",
"\n", "\n",
"Usually variable ordering has the biggest effect on run time. Let's try it. The function `reorder` takes a puzzle and returns a version of the puzzle with the row products permuted so that the rows with the fewest possible fillers come first:" "Usually variable ordering is the most productive heuristic. Let's try it. The function `reorder` takes a puzzle and returns a version of the puzzle with the row products permuted so that the rows with the fewest possible fillers come first:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 22, "execution_count": 20,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -699,7 +667,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 23, "execution_count": 21,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -709,7 +677,7 @@
" Puzzle(row_prods=[256, 280, 162, 360, 126, 168, 60], col_prods=[183708, 245760, 117600]))" " Puzzle(row_prods=[256, 280, 162, 360, 126, 168, 60], col_prods=[183708, 245760, 117600]))"
] ]
}, },
"execution_count": 23, "execution_count": 21,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -721,7 +689,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 24, "execution_count": 22,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -730,14 +698,15 @@
"{256: 1, 280: 2, 162: 2, 360: 2, 126: 5, 168: 7, 60: 8}" "{256: 1, 280: 2, 162: 2, 360: 2, 126: 5, 168: 7, 60: 8}"
] ]
}, },
"execution_count": 24, "execution_count": 22,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"# How many ways are there to fill each row?\n", "# How many ways are there to fill each row?\n",
"{r: len(fill_one_row(r, p2.col_prods)) for r in reorder(p2).row_prods}" "{r: len(fill_one_row(r, p2.col_prods)) \n",
" for r in reorder(p2).row_prods}"
] ]
}, },
{ {
@ -749,7 +718,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 25, "execution_count": 23,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -758,15 +727,15 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 26, "execution_count": 24,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 24.6 s, sys: 317 ms, total: 24.9 s\n", "CPU times: user 20.6 s, sys: 453 ms, total: 21.1 s\n",
"Wall time: 24.8 s\n" "Wall time: 20.7 s\n"
] ]
}, },
{ {
@ -775,7 +744,7 @@
"True" "True"
] ]
}, },
"execution_count": 26, "execution_count": 24,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -786,15 +755,15 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 27, "execution_count": 25,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 156 ms, sys: 4.68 ms, total: 160 ms\n", "CPU times: user 134 ms, sys: 3.41 ms, total: 137 ms\n",
"Wall time: 157 ms\n" "Wall time: 134 ms\n"
] ]
}, },
{ {
@ -803,7 +772,7 @@
"True" "True"
] ]
}, },
"execution_count": 27, "execution_count": 25,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -816,7 +785,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"That's a nice improvement—over 100 times faster! I'm curious whether we would get even more speedup by treating each cell as a separate variable, but I'll leave that as an exercise for the reader." "That's a nice improvement—150 times faster on this small test set! I'm curious whether we would get even more speedup by treating each cell as a separate variable, or by considering value ordering, but I'll leave that as an exercise for the reader."
] ]
}, },
{ {
@ -830,7 +799,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 28, "execution_count": 26,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -839,7 +808,7 @@
"True" "True"
] ]
}, },
"execution_count": 28, "execution_count": 26,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -893,6 +862,17 @@
" (9, 4, 2),\n", " (9, 4, 2),\n",
" (9, 8, 1)}\n", " (9, 8, 1)}\n",
" \n", " \n",
" assert fill_one_row(7**8, [7]*9) == {\n",
" (1, 7, 7, 7, 7, 7, 7, 7, 7),\n",
" (7, 1, 7, 7, 7, 7, 7, 7, 7),\n",
" (7, 7, 1, 7, 7, 7, 7, 7, 7),\n",
" (7, 7, 7, 1, 7, 7, 7, 7, 7),\n",
" (7, 7, 7, 7, 1, 7, 7, 7, 7),\n",
" (7, 7, 7, 7, 7, 1, 7, 7, 7),\n",
" (7, 7, 7, 7, 7, 7, 1, 7, 7),\n",
" (7, 7, 7, 7, 7, 7, 7, 1, 7),\n",
" (7, 7, 7, 7, 7, 7, 7, 7, 1)}\n",
" \n",
" assert solve(Puzzle([210, 144, 54, 135, 4, 49], [6615, 15552, 420])) == [\n", " assert solve(Puzzle([210, 144, 54, 135, 4, 49], [6615, 15552, 420])) == [\n",
" (7, 6, 5), \n", " (7, 6, 5), \n",
" (9, 8, 2), \n", " (9, 8, 2), \n",
@ -938,7 +918,7 @@
" assert well_formed(Puzzle([64, 224, 189, 270, 405, 144, 105], \n", " assert well_formed(Puzzle([64, 224, 189, 270, 405, 144, 105], \n",
" [308700, 12960, 1119744]))\n", " [308700, 12960, 1119744]))\n",
" \n", " \n",
" assert surround((1, 2, 3)) == '|1|2|3|'\n", " assert row((1, 2, 3)) == '|1|2|3|'\n",
" \n", " \n",
" col_prods = [193536, 155520, 793800]\n", " col_prods = [193536, 155520, 793800]\n",
" assert (reorder(Puzzle([10, 48, 36, 7, 32, 81, 252, 160, 21, 90], col_prods)) == \n", " assert (reorder(Puzzle([10, 48, 36, 7, 32, 81, 252, 160, 21, 90], col_prods)) == \n",
@ -965,7 +945,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.7.7" "version": "3.7.6"
} }
}, },
"nbformat": 4, "nbformat": 4,