Add files via upload
This commit is contained in:
parent
fcf8bc81af
commit
cc9c671d47
File diff suppressed because it is too large
Load Diff
407
ipynb/CrossProduct.ipynb
Normal file
407
ipynb/CrossProduct.ipynb
Normal file
@ -0,0 +1,407 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div align=\"right\" style=\"text-align: right\"><i>Peter Norvig, Feb 2020</i></div>\n",
|
||||
"\n",
|
||||
"# CrossProduct Puzzle\n",
|
||||
"\n",
|
||||
"The 538 Riddler [poses a puzzle](https://fivethirtyeight.com/features/can-you-cross-like-a-boss/) called ***CrossProduct***, which works like this:\n",
|
||||
"> *Fill in each empty cell of the table with a single digit, 1—9, so that the product of the digits in each row equals the number to the right of the row, and the product of the digits in each column equals the number below the column.*\n",
|
||||
"\n",
|
||||
"<table class=\"viz full\" data-carpenter-slug=\"wissner-gross.riddler.7\"><thead style=\"border-bottom: none\"><tr><th class=\"number\" style=\"text-align: center;border-right: none;width: 25%;border-left: none\" colspan=\"3\"></th>\n",
|
||||
"<th class=\"number\" style=\"text-align: center;border-right: none;width: 25%;border-left: none\" colspan=\"1\"></th>\n",
|
||||
"</tr></thead><tbody><tr style=\"border-top: 1px solid #cdcdcd\"><td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%;border-left: 1px solid #cdcdcd\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 2px solid;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"border-right: 1px solid #cdcdcd;width: 25%\">210</td>\n",
|
||||
"</tr><tr><td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%;border-left: 1px solid #cdcdcd\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 2px solid;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"border-right: 1px solid #cdcdcd;width: 25%\">144</td>\n",
|
||||
"</tr><tr><td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%;border-left: 1px solid #cdcdcd\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 2px solid;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"border-right: 1px solid #cdcdcd;width: 25%\">54</td>\n",
|
||||
"</tr><tr><td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%;border-left: 1px solid #cdcdcd\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 2px solid;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"border-right: 1px solid #cdcdcd;width: 25%\">135</td>\n",
|
||||
"</tr><tr><td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%;border-left: 1px solid #cdcdcd\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 2px solid;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"border-right: 1px solid #cdcdcd;width: 25%\">4</td>\n",
|
||||
"</tr><tr><td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%;border-left: 1px solid #cdcdcd\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 2px solid;width: 25%\"></td>\n",
|
||||
"<td class=\"number\" style=\"border-right: 1px solid #cdcdcd;width: 25%\">49</td>\n",
|
||||
"</tr><tr style=\"border-top: 2px solid;border-bottom: 1px solid #cdcdcd\"><td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%;border-left: 1px solid #cdcdcd\">6,615</td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 1px solid #cdcdcd;width: 25%\">15,552</td>\n",
|
||||
"<td class=\"number\" style=\"text-align: center;border-right: 2px solid;width: 25%\">420</td>\n",
|
||||
"<td class=\"number\" style=\"border-right: 1px solid #cdcdcd;width: 25%\"></td>\n",
|
||||
"</tr></tbody></table>\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"# Data Type definitions\n",
|
||||
" \n",
|
||||
"Here are the data types we will use in trying to solve CrossProduct puzzles: \n",
|
||||
"- `Row`: a sequence of digits that forms a row in the table, e.g. `(7, 6, 5)`.\n",
|
||||
"- `Table`: a filled-in table (but not the row and column products): a list of rows, e.g. `[(7, 6, 5), (9, 8, 2), ...]`.\n",
|
||||
"- `Products`: a list of the numbers that corresponding digits must multiply to, e.g. in the puzzle above, `[210, 144, 54, 135, 4, 49]` for the row products, and `[6615, 15552, 420]` for the column products."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Tuple, List, Set, Iterable, Optional\n",
|
||||
"\n",
|
||||
"Row = Tuple[int, ...] \n",
|
||||
"Table = List[Row] \n",
|
||||
"Products = List[int] "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Filling in one row\n",
|
||||
"\n",
|
||||
"A first step in solving the puzzle is filling in a single row of the table.\n",
|
||||
"<br>`fill_row(n, k)` will return the set of all `k`-digit tuples whose product is `n`.\n",
|
||||
"<br>By default there are 3 digits in a row, but any number of digits is allowed."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def fill_row(n, k=3) -> Set[Row]:\n",
|
||||
" \"All permutations of k digits that multiply to n.\"\n",
|
||||
" if k == 0:\n",
|
||||
" return {()} if n == 1 else set()\n",
|
||||
" else:\n",
|
||||
" return {(d, *rest) for d in range(1, 10)\n",
|
||||
" if (n / d).is_integer()\n",
|
||||
" for rest in fill_row(n // d, k - 1)}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"For example:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fill_row(210)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fill_row(729)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"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": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fill_row(729, 4)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{(1, 5, 5, 5, 5, 5),\n",
|
||||
" (5, 1, 5, 5, 5, 5),\n",
|
||||
" (5, 5, 1, 5, 5, 5),\n",
|
||||
" (5, 5, 5, 1, 5, 5),\n",
|
||||
" (5, 5, 5, 5, 1, 5),\n",
|
||||
" (5, 5, 5, 5, 5, 1)}"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fill_row(5**5, 6)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Solving the whole puzzle\n",
|
||||
"\n",
|
||||
"We can now solve the whole puzzle with a simple brute-force strategy: for every possible way of filling the first row, try every way of recursively solving the rest of the puzzle, and yield a table for each way that works. We'll define the function `solutions` to yield all possible solutions, and `solve` to find just the first solution. (A well-formed puzzle will have exactly one solution, but some puzzles might have no solution, or multiple solutions.)\n",
|
||||
"\n",
|
||||
"The function `solutions` has three main cases to consider:\n",
|
||||
"- Any puzzle with a non-integer column product is unsolvable. Return without yielding anything.\n",
|
||||
"- The empty puzzle (with no rows) has the empty table, `[]`, as a solution, as long as the column products are all 1. \n",
|
||||
"- In the general case, call `fill_row` to get all possible ways to fill the first row, and 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` that accounts for the first row). "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def solutions(row_prods: Products, col_prods: Products) -> Iterable[Table]:\n",
|
||||
" \"\"\"Yield table(s) that solve the puzzle.\n",
|
||||
" The product of the digits in row r must equal row_prods[r], for all r.\n",
|
||||
" The product of the digits in column c must equal col_prods[c], for all c.\"\"\"\n",
|
||||
" if not all(c == int(c) for c in col_prods):\n",
|
||||
" return\n",
|
||||
" if not row_prods and all(c == 1 for c in col_prods):\n",
|
||||
" yield []\n",
|
||||
" elif row_prods:\n",
|
||||
" for row1 in fill_row(row_prods[0], len(col_prods)):\n",
|
||||
" for rows in solutions(row_prods[1:], divide(col_prods, row1)):\n",
|
||||
" yield [row1, *rows]\n",
|
||||
" \n",
|
||||
"def solve(r, c) -> Optional[Table]: return next(solutions(r, c), None)\n",
|
||||
" \n",
|
||||
"def divide(A, B) -> List[float]: return [a / b for a, b in zip(A, B)]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Solutions\n",
|
||||
"\n",
|
||||
"Here are solutions to the puzzles posed by *The Riddler*:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[(3, 9, 5), (5, 9, 1), (8, 1, 8), (5, 7, 8), (5, 7, 2)]"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"solve([135, 45, 64, 280, 70], [3000, 3969, 640])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[(7, 6, 5), (9, 8, 2), (3, 9, 2), (5, 9, 3), (1, 4, 1), (7, 1, 7)]"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# The puzzle that appears at the top of this notebook\n",
|
||||
"solve([210, 144, 54, 135, 4, 49], [6615, 15552, 420])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Tests\n",
|
||||
"\n",
|
||||
"A suite of unit tests:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def test():\n",
|
||||
" assert fill_row(1, 0) == {()}\n",
|
||||
" assert fill_row(2, 0) == set()\n",
|
||||
" assert fill_row(9, 1) == {(9,)}\n",
|
||||
" assert fill_row(10, 1) == set()\n",
|
||||
" assert fill_row(73, 3) == set()\n",
|
||||
" \n",
|
||||
" assert solve([], []) == []\n",
|
||||
" assert solve([], [1]) == []\n",
|
||||
" assert solve([], [2]) == None\n",
|
||||
" assert solve([5], [5]) == [(5,)]\n",
|
||||
" assert solve([0], [0]) == None # Maybe should allow zero as a digit?\n",
|
||||
" \n",
|
||||
" assert fill_row(729, 3) == {(9, 9, 9)} # Unique fill\n",
|
||||
" \n",
|
||||
" assert fill_row(729, 4) == {\n",
|
||||
" (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)}\n",
|
||||
" \n",
|
||||
" assert max(range(1, 9*9*9 + 1), key=lambda n: len(fill_row(n, 3))) == 72\n",
|
||||
" assert fill_row(72, 3) == { # 72 has the most ways to fill a 3-digit row\n",
|
||||
" (1, 8, 9),\n",
|
||||
" (1, 9, 8),\n",
|
||||
" (2, 4, 9),\n",
|
||||
" (2, 6, 6),\n",
|
||||
" (2, 9, 4),\n",
|
||||
" (3, 3, 8),\n",
|
||||
" (3, 4, 6),\n",
|
||||
" (3, 6, 4),\n",
|
||||
" (3, 8, 3),\n",
|
||||
" (4, 2, 9),\n",
|
||||
" (4, 3, 6),\n",
|
||||
" (4, 6, 3),\n",
|
||||
" (4, 9, 2),\n",
|
||||
" (6, 2, 6),\n",
|
||||
" (6, 3, 4),\n",
|
||||
" (6, 4, 3),\n",
|
||||
" (6, 6, 2),\n",
|
||||
" (8, 1, 9),\n",
|
||||
" (8, 3, 3),\n",
|
||||
" (8, 9, 1),\n",
|
||||
" (9, 1, 8),\n",
|
||||
" (9, 2, 4),\n",
|
||||
" (9, 4, 2),\n",
|
||||
" (9, 8, 1)}\n",
|
||||
" \n",
|
||||
" assert solve([6, 120, 504], [28, 80, 162]) == [\n",
|
||||
" (1, 2, 3), \n",
|
||||
" (4, 5, 6), \n",
|
||||
" (7, 8, 9)]\n",
|
||||
" \n",
|
||||
" assert solve([210, 144, 54, 135, 4, 49], [6615, 15552, 420]) == [\n",
|
||||
" (7, 6, 5), \n",
|
||||
" (9, 8, 2), \n",
|
||||
" (3, 9, 2), \n",
|
||||
" (5, 9, 3), \n",
|
||||
" (1, 4, 1), \n",
|
||||
" (7, 1, 7)]\n",
|
||||
" \n",
|
||||
" assert sorted(solutions([8, 8, 1], [8, 8, 1])) == [ # Multi-solution puzzle\n",
|
||||
" [(1, 8, 1), \n",
|
||||
" (8, 1, 1), \n",
|
||||
" (1, 1, 1)],\n",
|
||||
" [(2, 4, 1), \n",
|
||||
" (4, 2, 1), \n",
|
||||
" (1, 1, 1)],\n",
|
||||
" [(4, 2, 1), \n",
|
||||
" (2, 4, 1), \n",
|
||||
" (1, 1, 1)],\n",
|
||||
" [(8, 1, 1), \n",
|
||||
" (1, 8, 1), \n",
|
||||
" (1, 1, 1)]]\n",
|
||||
" \n",
|
||||
" assert not list(solutions([8, 8, 1], [8, 8, 2])) # Unsolvable puzzle\n",
|
||||
" \n",
|
||||
" assert solve([1470, 720, 270, 945, 12, 343], \n",
|
||||
" [6615, 15552, 420, 25725]) == [ # 4 column puzzle\n",
|
||||
" (7, 6, 5, 7),\n",
|
||||
" (9, 8, 2, 5),\n",
|
||||
" (3, 9, 2, 5),\n",
|
||||
" (5, 9, 3, 7),\n",
|
||||
" (1, 4, 1, 3),\n",
|
||||
" (7, 1, 7, 7)]\n",
|
||||
" \n",
|
||||
" return True\n",
|
||||
" \n",
|
||||
"test()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
695
ipynb/Life.ipynb
695
ipynb/Life.ipynb
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user