Add files via upload

This commit is contained in:
Peter Norvig 2019-08-20 01:38:12 -07:00 committed by GitHub
parent eda2a2cc14
commit 0bc3b35b6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -4,29 +4,29 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"<div align=\"right\"><i>Peter Norvig<br>2012; updated 18 August 2019</i></div>\n",
"<div align=\"right\"><i>Peter Norvig<br>2012; updated August 2019</i></div>\n",
"\n",
"# Weighing Twelve Balls on a Balance Scale\n",
"\n",
"> *You are given twelve identical-looking balls and a two-sided scale. One of the balls is of a different weight, although you don't know whether it's lighter or heavier. How can you use just three weighings of the scale to determine not only what the different ball is, but also whether it's lighter or heavier?*\n",
"\n",
"This is a traditional brain-teaser puzzle, meant to be solved with paper and pencil. \n",
"But I want to solve not just this specific puzzle, but related puzzles where you can vary (a) the number of balls, (b) the number of weighings allowed, and (c) whether the odd ball might be heavier, lighter, or either, or neither. For that I'll ned a program. (I originally solved wrote this program in 2012, but am republishing it here in revised form because the problem was mentioned in the [538 Riddler](https://fivethirtyeight.com/features/which-billiard-ball-is-rigged/) for 16 August 2019.)\n",
"But I want to solve not just this specific puzzle, but related puzzles where you can vary (a) the number of balls, (b) the number of weighings allowed, and (c) whether the odd ball might be heavier, lighter, or either, or neither. For that I'll ned a program. (I originally wrote this program in 2012, but am republishing it here in revised form because the problem was mentioned in the [538 Riddler](https://fivethirtyeight.com/features/which-billiard-ball-is-rigged/) for 16 August 2019. It also appeared in a [Numberplay](https://wordplay.blogs.nytimes.com/2014/07/21/12coin/) column in 2014, with coins instead of balls.)\n",
"\n",
"# Design\n",
"\n",
"Here are the concepts I'm dealing with:\n",
"\n",
"- **balls**: In the general case I have N balls. I'll represent with a list like `[1, 2, 3]` for N = 3.\n",
"- **balls**: In the general case I have N balls. I'll represent them with a list like `[1, 2, 3]` for N = 3.\n",
"- **oddballs**: Exactly one of the balls is **odd** in its weight. \n",
"I'll represent the situation where ball N is heavier as +N, and where ball N is lighter as -N. (I'll represent the situation where no ball is odd with `0`; that's not needed for the puzzle stated above, but is a nice extension.) With N = 3, I will represent the set of all possible oddballs as `{+1, -1, +2, -2, +3, -3}`. \n",
"I'll represent the situation where ball N is heavier as +N, and where ball N is lighter as -N. (I'll represent the situation where no ball is odd with `0`; that's not needed for the puzzle stated above, but is a nice extension.) With N = 3, the set of all possible oddballs is `{+1, -1, +2, -2, +3, -3}`. \n",
"- **puzzle**: I'll call `Puzzle(N, W, {-1, +1})` to create a puzzle with N balls, W weighings allowed, and the oddballs allowing for each ball to be lighter (-1) or heavier (+1). The third argument can be any subset of `{-1, 0, +1}`, where the `0` stands for \"allow the possibility that no ball is odd; they all weigh the same.\" \n",
"- **weighing**: I can weigh a collection of balls on the left versus a collection on the right, and the result will be that the left side is greater than, equal to, or less than the right in weight. I'll denote that with the call `weigh(L, R, oddball)`, which returns a string, `'gt'`, `'eq'`, or `'lt'`.\n",
"- **weight**: I'll arbitrarily say that a normal ball weighs 100, a lighter ball 99, and a heavier ball 101.\n",
"- **solution**: Given a puzzle, a solution is a **strategy tree** that can correctly discover the odd ball, whatever it is, in the allowable number of weighings.\n",
"- **strategy tree**: a tree where each node is either a leaf node which is an *oddball* integer, or is an interior node with 5 components: `Tree(L, R, gt, eq, lt)` is a node where `L` and `R` are the collections of balls to be weighed, and `gt`, `eq`, and `lt` are trees for when the result of the weighing is that the total weight of the balls on the left is greater than, equal to, or less than, respectively, the total weight of the balls on the right.\n",
"- **following a path in a tree**: I'll use `follow(tree, oddball)` to say \"follow the path through the tree, doing each weighing under the assumption of the given oddball, and return the leaf node reached by the path&mdash;the oddball that the tree predicts. Note that the function `follow` gets to see what the oddball is, but the `tree` never gets direct access to that; the tree has to figure it out by doing weighings.\n",
"- **valid tree**: a tree is a valid solution to a puzzle if no branch uses more than the allowable number of weighings, and if, for every possible oddball in the puzzle, following the path through the tree correct returns that oddball as the answer. I'll use `valid(tree, puzzle)` for this.\n",
"- **strategy tree**: A tree with oddballs (integers) as leaf nodes, and interior nodes with 5 components: `Tree(L, R, gt, eq, lt)` is a node where `L` and `R` are the collections of balls to be weighed, and `gt`, `eq`, and `lt` are subtrees for the three possible results of the weighing.\n",
"- **following a path in a tree**: I'll use `follow(tree, oddball)` to say *follow the path through the tree, doing each weighing under the assumption of the given oddball, and return the leaf node reached by the path&mdash;the oddball that the tree predicts.* Note that the function `follow` gets to see what the oddball is, but the `tree` never gets direct access to that; the tree has to figure it out by doing weighings.\n",
"- **valid tree**: a tree is a valid solution to a puzzle if no branch uses more than the allowable number of weighings, and if, for every possible oddball in the puzzle, following the path through the tree correctly returns that oddball as the answer. I'll use `valid(tree, puzzle)` for this.\n",
"\n",
"\n",
"# Implementation\n",
@ -107,21 +107,22 @@
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3\n",
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]\n",
"{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -12, -11, -1, -9, -8, -7, -6, -5, -4, -3, -2, -10}\n"
]
"data": {
"text/plain": [
"(3,\n",
" [1, 2, 3, 4, 5, 6, 7, 8],\n",
" {-8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8})"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p12 = Puzzle(12) # The original puzzle with 12 balls\n",
"p8 = Puzzle(8) \n",
"\n",
"print(p12.weighings)\n",
"print(p12.balls)\n",
"print(p12.oddballs)"
"p8.weighings, p8.balls, p8.oddballs"
]
},
{
@ -234,6 +235,8 @@
}
],
"source": [
"p12 = Puzzle(12, 3)\n",
"\n",
"partition([1, 2], [11, 12], p12.oddballs)"
]
},
@ -289,7 +292,7 @@
"metadata": {},
"outputs": [],
"source": [
"def find_tree(puzzle, oddballs, weighings) -> Tree or Oddball:\n",
"def find_tree(puzzle, oddballs, weighings) -> Tree or Oddball or None:\n",
" \"Find a strategy tree that covers all the oddballs in the given number of weighings.\"\n",
" if len(oddballs) == 1:\n",
" return oddballs.pop() # One oddball possibility left; we're done: leaf node\n",
@ -337,11 +340,11 @@
{
"data": {
"text/plain": [
"([9, 3, 2, 1],\n",
" [10, 11, 4, 12],\n",
" {'gt': {-12, -11, -10, -4, 1, 2, 3, 9},\n",
" 'eq': {-8, -7, -6, -5, 5, 6, 7, 8},\n",
" 'lt': {-9, -3, -2, -1, 4, 10, 11, 12}})"
"([12, 3, 6, 9],\n",
" [10, 5, 11, 7],\n",
" {'gt': {-11, -10, -7, -5, 3, 6, 9, 12},\n",
" 'eq': {-8, -4, -2, -1, 1, 2, 4, 8},\n",
" 'lt': {-12, -9, -6, -3, 5, 7, 10, 11}})"
]
},
"execution_count": 10,
@ -368,11 +371,11 @@
{
"data": {
"text/plain": [
"([8, 12, 10, 4],\n",
" [2, 6, 7, 11],\n",
" {'gt': {-11, -7, -6, -2, 4, 8, 10, 12},\n",
" 'eq': {-9, -5, -3, -1, 1, 3, 5, 9},\n",
" 'lt': {-12, -10, -8, -4, 2, 6, 7, 11}})"
"([2, 3, 1, 12],\n",
" [11, 8, 9, 4],\n",
" {'gt': {-11, -9, -8, -4, 1, 2, 3, 12},\n",
" 'eq': {-10, -7, -6, -5, 5, 6, 7, 10},\n",
" 'lt': {-12, -3, -2, -1, 4, 8, 9, 11}})"
]
},
"execution_count": 11,
@ -401,7 +404,7 @@
{
"data": {
"text/plain": [
"Tree(L=[6, 2, 3, 5], R=[11, 1, 8, 4], gt=Tree(L=[3, 5, 11], R=[6, 4, 2], gt=Tree(L=[8, 11, 4, 5], R=[10, 9, 6, 7], gt=5, eq=3, lt=-4), eq=Tree(L=[6, 1, 9], R=[7, 12, 4], gt=0, eq=-8, lt=-1), lt=Tree(L=[1, 10, 5], R=[2, 11, 7], gt=-11, eq=6, lt=2)), eq=Tree(L=[12, 2, 3, 4], R=[10, 8, 7, 11], gt=Tree(L=[7, 4], R=[10, 5], gt=-10, eq=12, lt=-7), eq=Tree(L=[8, 1, 10, 11], R=[5, 12, 6, 9], gt=-9, eq=0, lt=9), lt=Tree(L=[12, 4, 8, 7], R=[11, 1, 3, 6], gt=7, eq=10, lt=-12)), lt=Tree(L=[2, 6, 8, 11], R=[10, 9, 5, 4], gt=Tree(L=[12, 11, 10], R=[8, 3, 4], gt=11, eq=-5, lt=8), eq=Tree(L=[8, 5, 1], R=[9, 12, 2], gt=1, eq=-3, lt=0), lt=Tree(L=[5, 10], R=[4, 2], gt=-2, eq=-6, lt=4)))"
"Tree(L=[2, 7, 5, 10], R=[4, 9, 11, 8], gt=Tree(L=[5, 9, 7, 8], R=[1, 4, 2, 3], gt=Tree(L=[8, 10, 5], R=[1, 2, 7], gt=5, eq=-4, lt=7), eq=Tree(L=[8, 3, 9, 5], R=[10, 2, 6, 11], gt=-11, eq=0, lt=10), lt=Tree(L=[8, 6, 5], R=[9, 1, 11], gt=-9, eq=2, lt=-8)), eq=Tree(L=[12, 2, 10, 9], R=[7, 6, 11, 3], gt=Tree(L=[8, 3, 12], R=[2, 11, 4], gt=12, eq=-6, lt=-3), eq=Tree(L=[6, 3, 11], R=[8, 1, 4], gt=-1, eq=0, lt=1), lt=Tree(L=[1, 7, 10], R=[11, 6, 12], gt=-12, eq=3, lt=6)), lt=Tree(L=[7, 6, 3, 1], R=[9, 8, 2, 5], gt=Tree(L=[6, 9, 12], R=[2, 8, 4], gt=-2, eq=-5, lt=0), eq=Tree(L=[4], R=[11], gt=4, eq=-10, lt=11), lt=Tree(L=[8, 2, 7], R=[4, 12, 1], gt=8, eq=9, lt=-7)))"
]
},
"execution_count": 12,
@ -428,7 +431,7 @@
{
"data": {
"text/plain": [
"Tree(L=[2], R=[1], gt=-1, eq=-3, lt=-2)"
"Tree(L=[1], R=[2], gt=-2, eq=-3, lt=-1)"
]
},
"execution_count": 13,
@ -456,7 +459,9 @@
"\n",
"# Prettier Output\n",
"\n",
"Let's make the output easier to read. I'll use `[1·2·3·4 ⟘ 9·10·11·12] ➔` to mean *\"The result of weighing balls 1,2,3,4 versus 9,10,11,12 is...\"*. I'll indent for each interior node in the tree. Also, note that at the top node of a tree, there's no sense randomly shuffling the balls&mdash;the only choice that matters is how many balls, `B`, to put on each side. Putting `1·2·3·4` on the left is no different than `3·7·9·12`, because each ball is undifferentiated at the start. I'll alter `random_LR` to accomodate that."
"Let's make the output easier to read. I'll use `[1·2·3·4 ⟘ 9·10·11·12] ➔` to mean *\"Weigh balls 1,2,3,4 versus 9,10,11,12 to get a result...\"*. I'll indent each interior node in the tree, and I'll use `>:` to mean *the result when the left hand side is greater than the right in weight is...* \n",
"\n",
"Also, note that at the top node of a tree, there's no sense randomly shuffling the balls&mdash;the only choice that matters is how many balls, `B`, to put on each side. Putting `1·2·3·4` on the left is no different than `3·7·9·12`, because each ball is undifferentiated at the start. I'll alter `random_LR` for this."
]
},
{
@ -472,9 +477,9 @@
"def indented(tree, i=0, prefix=''):\n",
" \"Pretty, indented string of a strategy tree.\"\n",
" if isinstance(tree, Tree):\n",
" subtrees = f\"{indented(tree.gt, i+1, '>:')} {indented(tree.eq, i+1, '=:')} {indented(tree.lt, i+1, '<:')}\"\n",
" indent = '' if i == 0 else ('\\n' + \" \" * 5 * i)\n",
" return f'{indent}{prefix}[{items(tree.L)} ⟘ {items(tree.R)}] ➔ {subtrees})'\n",
" subtrees = (indented(tree.gt, i+1, '>:'), indented(tree.eq, i+1, '=:'), indented(tree.lt, i+1, '<:'))\n",
" indent = ('' if i == 0 else ('\\n' + ' ' * 5 * i))\n",
" return f'{indent}{prefix}[{items(tree.L)} ⟘ {items(tree.R)}] ➔ {\" \".join(subtrees)})'\n",
" elif tree == 0 or tree == None:\n",
" return f'{prefix}{tree}'\n",
" else:\n",
@ -522,8 +527,8 @@
"text": [
"[1 ⟘ 3] ➔ \n",
" >:[3 ⟘ 2] ➔ >:0 =:+1 <:-3) \n",
" =:[2 ⟘ 3] ➔ >:+2 =:0 <:-2) \n",
" <:[3 ⟘ 2] ➔ >:+3 =:-1 <:0))\n"
" =:[2 ⟘ 1] ➔ >:+2 =:0 <:-2) \n",
" <:[2 ⟘ 3] ➔ >:0 =:-1 <:+3))\n"
]
}
],
@ -542,18 +547,18 @@
"output_type": "stream",
"text": [
"[1·2·3·4 ⟘ 9·10·11·12] ➔ \n",
" >:[2·4·5·10 ⟘ 1·3·6·8] ➔ \n",
" >:[5 ⟘ 4] ➔ >:0 =:+2 <:+4) \n",
" =:[1·11 ⟘ 7·12] ➔ >:-12 =:-9 <:-11) \n",
" <:[3·9 ⟘ 1·12] ➔ >:+3 =:-10 <:+1)) \n",
" =:[2·5·10·11 ⟘ 4·6·8·9] ➔ \n",
" >:[8·11 ⟘ 1·6] ➔ >:-6 =:+5 <:-8) \n",
" =:[7·8 ⟘ 1·6] ➔ >:+7 =:0 <:-7) \n",
" <:[7·11·12 ⟘ 5·6·9] ➔ >:-5 =:+8 <:+6)) \n",
" <:[2·10·11 ⟘ 1·7·9] ➔ \n",
" >:[5·7·8·10 ⟘ 3·4·11·12] ➔ >:+10 =:-1 <:+11) \n",
" =:[1·5·8·10 ⟘ 2·3·11·12] ➔ >:-3 =:-4 <:+12) \n",
" <:[4·5·7·12 ⟘ 2·3·9·10] ➔ >:-2 =:0 <:+9)))\n"
" >:[1·3·6·9 ⟘ 2·4·8·11] ➔ \n",
" >:[3·6·11·12 ⟘ 2·8·9·10] ➔ >:+3 =:+1 <:-11) \n",
" =:[12 ⟘ 1] ➔ >:0 =:-10 <:-12) \n",
" <:[1·7·11·12 ⟘ 4·5·8·9] ➔ >:-9 =:+2 <:+4)) \n",
" =:[3·8·10·11 ⟘ 4·6·7·9] ➔ \n",
" >:[4·9·10·12 ⟘ 1·6·8·11] ➔ >:-6 =:-7 <:+8) \n",
" =:[2·5 ⟘ 1·6] ➔ >:+5 =:0 <:-5) \n",
" <:[7·10·11 ⟘ 1·6·12] ➔ >:+7 =:-8 <:+6)) \n",
" <:[2·4·5·11 ⟘ 3·7·8·10] ➔ \n",
" >:[3·9 ⟘ 2·8] ➔ >:0 =:+11 <:-3) \n",
" =:[6·9·11 ⟘ 3·5·12] ➔ >:+9 =:-1 <:+12) \n",
" <:[5·7·11·12 ⟘ 2·6·9·10] ➔ >:-2 =:-4 <:+10)))\n"
]
}
],
@ -574,7 +579,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"We can solve much larger puzzles. We can do 39 balls with 4 weighings:"
"We can do 12 balls in 3 weighings even if it is possible that all the balls weigh the same (we use `0` to denote this situation):"
]
},
{
@ -586,58 +591,31 @@
"name": "stdout",
"output_type": "stream",
"text": [
"[1·2·3·4·5·6·7·8·9·10·11·12·13 ⟘ 27·28·29·30·31·32·33·34·35·36·37·38·39] ➔ \n",
" >:[3·4·6·7·8·12·21·27·29·33·34·36·37 ⟘ 10·11·14·16·18·22·23·24·25·26·28·30·35] ➔ \n",
" >:[2·9·12·13·17·18·20·26·31·35·39 ⟘ 1·3·6·10·19·21·24·28·29·30·37] ➔ \n",
" >:[1·3·5·11·13·15·16·20·24·29·35·38 ⟘ 7·8·9·10·12·17·26·27·28·34·36·39] ➔ >:-28 =:-30 <:+12) \n",
" =:[3·8·14·20·22·24·28·36·38 ⟘ 4·9·12·18·19·25·29·30·31] ➔ >:+8 =:+7 <:+4) \n",
" <:[5·11·13·15·18·19·20·26·30·32·34·36 ⟘ 1·4·6·7·16·21·25·27·29·31·35·38] ➔ >:-35 =:+3 <:+6)) \n",
" =:[3·7·12·14·15·19·20·23·25·26·29·34 ⟘ 1·4·5·9·11·18·24·30·31·37·38·39] ➔ \n",
" >:[2·6·16·22·26·31·32·33 ⟘ 1·3·9·11·12·13·37·38] ➔ >:-38 =:-39 <:-31) \n",
" =:[2·6·10·16·19·23·26·28·31·32 ⟘ 5·7·11·12·22·24·30·33·35·37] ➔ >:+2 =:+13 <:-32) \n",
" <:[4·5·10·19·23·31·36 ⟘ 6·8·9·11·33·38·39] ➔ >:+5 =:+1 <:+9)) \n",
" <:[2·5·6·7·11·15·18·22·25·28·29·30 ⟘ 3·9·10·13·17·20·23·26·31·33·35·36] ➔ \n",
" >:[1·2·11·13·17·24·26·27·28·31·35·36 ⟘ 5·6·9·12·14·15·16·22·23·32·34·38] ➔ >:+11 =:-33 <:-36) \n",
" =:[5·6·10·12·14·18·20·21·22·28·35·37·39 ⟘ 1·2·3·4·7·9·16·19·23·31·32·34·38] ➔ >:-34 =:-27 <:-37) \n",
" <:[7·8·10·13·16·24·26·29·32·35·36 ⟘ 1·4·5·9·19·25·27·30·31·33·34] ➔ >:+10 =:0 <:-29))) \n",
" =:[1·4·6·20·22·23·27·30·31·32·33·35·38 ⟘ 2·9·10·13·14·15·16·18·25·26·28·36·39] ➔ \n",
" >:[1·2·4·8·10·15·16·18·20·23·31·34·36 ⟘ 3·5·7·13·14·24·27·29·32·33·37·38·39] ➔ \n",
" >:[1·3·5·14·15·17·21·22·23·31·34·35·36 ⟘ 2·4·6·8·12·13·18·27·29·30·33·37·39] ➔ >:+23 =:+20 <:-14) \n",
" =:[4·5·9·11·15·18·21·27·30·34·38 ⟘ 1·2·10·12·14·17·19·22·25·29·31] ➔ >:-25 =:-26 <:+22) \n",
" <:[6·12·13·18·27·35 ⟘ 1·9·11·14·16·29] ➔ >:-16 =:-15 <:-18)) \n",
" =:[6·8·10·11·19·21·27·28·29·33·35·37 ⟘ 1·14·16·18·20·24·31·32·34·36·38·39] ➔ \n",
" >:[4·7·22·25·30·33 ⟘ 8·9·12·21·24·34] ➔ >:-24 =:+19 <:+21) \n",
" =:[6·15·17·18·19·21·27·33·35·36·39 ⟘ 2·3·5·11·12·16·20·24·28·30·38] ➔ >:+17 =:0 <:-17) \n",
" <:[2·4·5·6·12·16·20·22·23·25·26·32·37 ⟘ 3·7·9·15·17·18·21·24·30·34·35·38·39] ➔ >:-21 =:-19 <:+24)) \n",
" <:[2·10·15·18·21·22·26·29·32·34·37 ⟘ 1·3·4·11·12·14·25·28·30·31·33] ➔ \n",
" >:[6·10·15·20·25·27·33 ⟘ 4·7·9·18·19·29·36] ➔ >:+15 =:+26 <:+18) \n",
" =:[1·3·5·11·13·17·23·25·34·37 ⟘ 2·6·12·18·19·20·22·27·30·36] ➔ >:-20 =:+16 <:-23) \n",
" <:[4·10·11·17·18·19·21·25·26·30·38 ⟘ 1·3·8·13·14·20·31·33·34·35·37] ➔ >:+25 =:-22 <:+14))) \n",
" <:[2·3·6·8·23·29·33·34·35·36·37·39 ⟘ 4·7·18·19·20·24·25·26·27·31·32·38] ➔ \n",
" >:[2·4·19·22·26·27·28·29·32·37·38 ⟘ 1·7·8·13·14·15·23·25·30·33·39] ➔ \n",
" >:[3·11·12·15·19·22·23 ⟘ 6·7·9·14·26·29·39] ➔ >:-7 =:+37 <:+29) \n",
" =:[4·9·13·15·19·20·25·28·31·33·34·37·38 ⟘ 1·11·12·14·17·18·23·24·26·27·29·30·36] ➔ >:+34 =:+35 <:+36) \n",
" <:[6·10·11·13·16·17·18·22·28·36·37·38·39 ⟘ 2·5·8·9·15·19·20·23·24·29·32·33·35] ➔ >:+39 =:-4 <:+33)) \n",
" =:[1·7·15·20·22·24·26·27·29·38 ⟘ 2·9·11·12·17·19·28·30·34·35] ➔ \n",
" >:[2·4·5·6·11·15·17·20·22·23·30·31·34 ⟘ 1·10·12·16·19·24·26·28·29·32·35·36·37] ➔ >:-12 =:-9 <:-11) \n",
" =:[3·8·9·13·19·20·25·27·34 ⟘ 2·5·7·11·18·28·29·35·38] ➔ >:-5 =:-10 <:-13) \n",
" <:[4·7·9·10·11·16·17·19·28·29·34·38·39 ⟘ 3·8·12·14·15·18·21·23·25·27·30·31·36] ➔ >:+28 =:-1 <:+30)) \n",
" <:[1·4·6·21·24·31·32·35·36·39 ⟘ 7·8·11·12·13·15·22·26·29·38] ➔ \n",
" >:[1·2·4·12·15·18·21·22·23·24·34·36·38 ⟘ 7·8·9·10·13·25·28·29·30·32·33·37·39] ➔ >:-8 =:+31 <:+32) \n",
" =:[3·4·14·15·23·25·27 ⟘ 5·12·13·22·33·38·39] ➔ >:+27 =:-2 <:-3) \n",
" <:[4·8·9·15·23·24·35·38 ⟘ 5·10·16·21·22·27·34·37] ➔ >:+38 =:-6 <:0))))\n"
"[1·2·3·4 ⟘ 9·10·11·12] ➔ \n",
" >:[3·5·7·12 ⟘ 1·4·6·10] ➔ \n",
" >:[10 ⟘ 7] ➔ >:0 =:+3 <:-10) \n",
" =:[2·10·11 ⟘ 4·6·7] ➔ >:+2 =:-9 <:-11) \n",
" <:[4·7·11·12 ⟘ 5·6·8·9] ➔ >:+4 =:+1 <:-12)) \n",
" =:[2·6·8·12 ⟘ 1·7·10·11] ➔ \n",
" >:[2·8·12 ⟘ 1·6·11] ➔ >:+8 =:-7 <:+6) \n",
" =:[5·8·10·12 ⟘ 3·6·7·9] ➔ >:+5 =:0 <:-5) \n",
" <:[2·3·4 ⟘ 7·8·10] ➔ >:-8 =:-6 <:+7)) \n",
" <:[2·4·5·11 ⟘ 1·3·6·8] ➔ \n",
" >:[4·7·10·12 ⟘ 3·5·9·11] ➔ >:-3 =:-1 <:+11) \n",
" =:[3·11·12 ⟘ 5·7·10] ➔ >:+12 =:+9 <:+10) \n",
" <:[5·6·9 ⟘ 4·8·12] ➔ >:-4 =:-2 <:0)))\n"
]
}
],
"source": [
"do(Puzzle(39, 4))"
"do(Puzzle(12, 3, {-1, 0, +1}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, we can't do 40 balls:"
"We can tackle much larger puzzles. With 4 weighings, we can theoretically handle up to 3<sup>4</sup> = 81 possibilities. Can we solve for 40 balls, each of which might be heavier or lighter?"
]
},
{
@ -661,7 +639,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"We can do 12 balls in 3 weighings even if it is possible that none of the balls is odd:"
"Unfortunately, no. How about 39 balls, and how about we allow for the possibility that no ball is odd. That's 39 &times; 2 + 1 = 79 possibilities."
]
},
{
@ -673,55 +651,111 @@
"name": "stdout",
"output_type": "stream",
"text": [
"[1·2·3·4 ⟘ 9·10·11·12] ➔ \n",
" >:[3·4·9·10 ⟘ 1·6·8·12] ➔ \n",
" >:[4·6·9·12 ⟘ 1·2·5·10] ➔ >:+4 =:+3 <:-12) \n",
" =:[1·5·11·12 ⟘ 4·6·9·10] ➔ >:0 =:+2 <:-11) \n",
" <:[1·9 ⟘ 4·11] ➔ >:+1 =:-10 <:-9)) \n",
" =:[1·7·9·11 ⟘ 5·8·10·12] ➔ \n",
" >:[4·7·8 ⟘ 2·11·12] ➔ >:+7 =:-5 <:-8) \n",
" =:[4·5·7·8 ⟘ 1·2·6·9] ➔ >:-6 =:0 <:+6) \n",
" <:[8·12 ⟘ 3·5] ➔ >:+8 =:-7 <:+5)) \n",
" <:[3·6·8·10 ⟘ 2·4·7·12] ➔ \n",
" >:[5·7·9 ⟘ 4·10·11] ➔ >:-4 =:-2 <:+10) \n",
" =:[5·11 ⟘ 3·9] ➔ >:+11 =:-1 <:+9) \n",
" <:[3·4·9 ⟘ 7·8·11] ➔ >:0 =:+12 <:-3)))\n"
"[1·2·3·4·5·6·7·8·9·10·11·12·13 ⟘ 27·28·29·30·31·32·33·34·35·36·37·38·39] ➔ \n",
" >:[4·6·7·9·10·13·17·18·32·33·35·37·39 ⟘ 1·2·3·11·14·16·19·20·22·23·28·31·36] ➔ \n",
" >:[1·2·4·7·9·16·21·26·28·36·37·39 ⟘ 10·12·15·18·19·22·23·24·27·29·32·34] ➔ \n",
" >:[2·4·6·10·12·15·17·19·20·21·23·31·36 ⟘ 3·5·8·9·11·13·16·18·26·33·35·37·39] ➔ >:+4 =:+7 <:+9) \n",
" =:[10·12·18·19·23·25·30·36·39 ⟘ 4·6·8·14·15·24·28·31·35] ➔ >:-31 =:+13 <:+6) \n",
" <:[6·8·10·16·20·21·26·28·29·33·34 ⟘ 2·3·7·9·11·14·22·25·31·32·39] ➔ >:+10 =:-36 <:-28)) \n",
" =:[1·4·6·8·11·18·19·22·38 ⟘ 9·12·24·27·30·31·33·37·39] ➔ \n",
" >:[6·7·9·13·30·36 ⟘ 23·24·25·26·27·34] ➔ >:-27 =:+8 <:-30) \n",
" =:[1·6·17·19·26·30·31·35·37·39 ⟘ 5·9·14·15·18·20·23·24·27·34] ➔ >:-34 =:-29 <:+5) \n",
" <:[10·11·12·16·23·25·38 ⟘ 2·7·13·21·22·34·36] ➔ >:+12 =:0 <:-38)) \n",
" <:[5·6·8·9·11·13·19·21·22·31·34·36·37 ⟘ 1·2·10·12·14·15·17·24·27·30·32·33·38] ➔ \n",
" >:[2·4·18·26·38 ⟘ 11·15·21·24·32] ➔ >:-32 =:-33 <:+11) \n",
" =:[2·5·6·9·10·11·15·19·24·25·30·35·36 ⟘ 1·7·12·16·17·18·20·21·22·28·31·38·39] ➔ >:-39 =:+3 <:-35) \n",
" <:[1·4·15·30·33 ⟘ 2·9·10·12·16] ➔ >:+1 =:-37 <:+2))) \n",
" =:[5·8·11·18·19·23·24·26·27·28·32·36·37 ⟘ 1·2·9·10·12·15·20·22·25·29·34·35·39] ➔ \n",
" >:[4·8·10·22·23·27·29·32·37 ⟘ 6·13·17·18·20·24·25·33·36] ➔ \n",
" >:[1·2·10·15·19·20·23·32·37 ⟘ 4·6·9·12·13·17·18·22·39] ➔ >:+23 =:-25 <:-20) \n",
" =:[14·28 ⟘ 15·19] ➔ >:-15 =:+26 <:+19) \n",
" <:[6·10·14·18·21·31·34 ⟘ 4·5·7·24·28·30·36] ➔ >:+18 =:-22 <:+24)) \n",
" =:[11·14·17·19·24·32·35·37·38 ⟘ 4·6·8·10·16·22·26·31·33] ➔ \n",
" >:[4·5·13·19·20·22·27·32·33·38 ⟘ 7·11·12·16·17·18·29·36·37·39] ➔ >:-16 =:+14 <:+17) \n",
" =:[1·2·3·9·17·21·26·28·31·32·38 ⟘ 7·8·10·11·12·18·22·29·33·34·36] ➔ >:+21 =:0 <:-21) \n",
" <:[3·4·6·9·14·21·30·32·33·38 ⟘ 1·2·5·10·11·13·17·22·24·36] ➔ >:-17 =:+16 <:-14)) \n",
" <:[2·8·10·15·17·24·26·27·28·33·34·35 ⟘ 4·5·6·11·12·18·22·23·29·30·31·39] ➔ \n",
" >:[6·7·13·15·17·19·23·25·26 ⟘ 1·11·12·14·16·22·32·36·38] ➔ >:+15 =:-18 <:-23) \n",
" =:[4·7·11·13·14·15·17·18·27·36·37·38·39 ⟘ 2·6·10·16·19·20·22·23·24·31·32·33·35] ➔ >:-19 =:+25 <:+20) \n",
" <:[8·12·20·25·26·27·28·32·34 ⟘ 3·4·5·13·19·21·24·31·38] ➔ >:-24 =:+22 <:-26))) \n",
" <:[2·8·11·15·19·21·22·25·29·31·34·39 ⟘ 1·5·7·12·13·23·30·33·35·36·37·38] ➔ \n",
" >:[2·3·6·8·13·33·36·39 ⟘ 1·7·9·10·25·27·31·34] ➔ \n",
" >:[4·5·6·8·27·28·29·33 ⟘ 7·10·15·20·21·30·32·39] ➔ >:-7 =:-1 <:+39) \n",
" =:[7·13·14·15·22·28·32·35·36·39 ⟘ 5·6·8·19·21·27·29·34·37·38] ➔ >:-5 =:-12 <:+29) \n",
" <:[3·8·13·24·31·35·36·37 ⟘ 5·7·11·15·19·21·22·29] ➔ >:+31 =:+34 <:-13)) \n",
" =:[1·3·10·11·13·17·19·20·28·36·38 ⟘ 2·5·6·12·14·15·16·29·32·37·39] ➔ \n",
" >:[1·9·15·16·18·19·23·28·30·32·34·37 ⟘ 3·7·10·12·13·14·20·22·24·29·33·35] ➔ >:+28 =:-6 <:0) \n",
" =:[5·6·11·18·20·22·23·25·26·30·32·34·36 ⟘ 1·2·8·9·10·13·14·17·19·27·29·31·39] ➔ >:-9 =:-4 <:+27) \n",
" <:[2·8·9·11·13·21·22·24·28·31·33·35·39 ⟘ 3·7·12·15·16·19·23·27·29·30·32·34·38] ➔ >:-3 =:-10 <:+32)) \n",
" <:[1·8·11·12·24·28·30·33·35·39 ⟘ 3·4·10·14·15·16·19·23·27·37] ➔ \n",
" >:[1·3·5·7·9·11·14·30·31·32·34·37·38 ⟘ 4·8·12·13·15·17·19·23·26·28·29·33·36] ➔ >:+30 =:+35 <:+33) \n",
" =:[10·11·20·23·24·26·28·29·31·39 ⟘ 2·3·4·5·9·16·27·33·34·38] ➔ >:-2 =:+36 <:+38) \n",
" <:[8·9·12·14·19·27·33·34·37 ⟘ 1·3·6·10·17·18·24·38·39] ➔ >:+37 =:-11 <:-8))))\n"
]
}
],
"source": [
"do(Puzzle(12, 3, {-1, 0, +1}))"
"do(Puzzle(39, 4, {-1, 0, +1}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can do **26** balls in 3 weighings if we know that no ball can be heavier (and one or none is lighter):"
"We can do **27** balls in 3 weighings if we know that the odd ball can only be lighter, not heavier. And we can do 26 balls under the condition that either an odd ball is lighter or all the balls weigh the same. In both cases there are 27 possibilities, so that's the most we can possibly do."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1·2·3·4·5·6·7·8·9 ⟘ 19·20·21·22·23·24·25·26·27] ➔ \n",
" >:[6·7·12·15·17·19·20·25 ⟘ 2·5·10·13·16·24·26·27] ➔ \n",
" >:[1·2·5·6·10·14·20·25·26 ⟘ 3·9·13·15·16·18·19·23·27] ➔ >:-27 =:-24 <:-26) \n",
" =:[6·11·14·16·22 ⟘ 1·15·21·25·27] ➔ >:-21 =:-23 <:-22) \n",
" <:[1·3·6·11·13·20·22·26 ⟘ 4·7·10·12·14·15·19·24] ➔ >:-19 =:-25 <:-20)) \n",
" =:[2·4·12·15·18·25 ⟘ 1·9·13·14·17·26] ➔ \n",
" >:[11·13·15·16·21·23 ⟘ 7·9·12·14·24·27] ➔ >:-14 =:-17 <:-13) \n",
" =:[5·7·12·16·17·22·23·25 ⟘ 6·8·9·10·13·19·24·26] ➔ >:-10 =:-11 <:-16) \n",
" <:[2·6·9·15·21 ⟘ 4·8·11·12·23] ➔ >:-12 =:-18 <:-15)) \n",
" <:[2·4·6·10·11·12·14·18·19 ⟘ 1·3·9·13·22·24·25·26·27] ➔ \n",
" >:[3·8·13·16·20·24·27 ⟘ 4·6·7·9·12·14·15] ➔ >:-9 =:-1 <:-3) \n",
" =:[2·3·8·9·12·15·19·22·23 ⟘ 1·5·6·10·14·17·25·26·27] ➔ >:-5 =:-7 <:-8) \n",
" <:[4·5·7·8·12·13·16·22 ⟘ 3·6·9·11·17·18·24·25] ➔ >:-6 =:-2 <:-4)))\n"
]
}
],
"source": [
"do(Puzzle(27, 3, {-1}))"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1·2·3·4·5·6·7·8·9 ⟘ 18·19·20·21·22·23·24·25·26] ➔ \n",
" >:[3·7·12·14·15·18·21·25 ⟘ 1·6·8·10·17·20·24·26] ➔ \n",
" >:[3·6·9·10·13·14·17·26 ⟘ 5·7·8·15·21·23·24·25] ➔ >:-24 =:-20 <:-26) \n",
" =:[5·6·7·9·12·13·14·15·23 ⟘ 1·2·3·4·8·16·18·19·25] ➔ >:-19 =:-22 <:-23) \n",
" <:[1·2·7·9·10·12·14·19·25 ⟘ 3·4·5·8·13·15·16·21·22] ➔ >:-21 =:-18 <:-25)) \n",
" =:[10·11·16·19·20·21·22·23·24 ⟘ 1·2·3·4·7·13·14·17·26] ➔ \n",
" >:[2·5·7·14·15·16 ⟘ 4·9·11·17·19·21] ➔ >:-17 =:-13 <:-14) \n",
" =:[5·8·10·13·14·15·23·26 ⟘ 4·7·9·12·18·19·24·25] ➔ >:-12 =:0 <:-15) \n",
" <:[6·10·13·15·19·21·22·23·25 ⟘ 3·4·7·8·9·11·12·17·24] ➔ >:-11 =:-16 <:-10)) \n",
" <:[3·4·8·11·12·16·20 ⟘ 1·2·5·10·13·21·26] ➔ \n",
" >:[1·9·16·18·20·22 ⟘ 4·5·8·13·14·19] ➔ >:-5 =:-2 <:-1) \n",
" =:[1·4·7·13·16·17 ⟘ 5·9·10·12·21·25] ➔ >:-9 =:-6 <:-7) \n",
" <:[4·6·12·14·18 ⟘ 8·11·13·19·20] ➔ >:-8 =:-3 <:-4)))\n"
" >:[1·5·7·8·9·12·20·21·26 ⟘ 2·3·10·13·14·17·19·23·25] ➔ \n",
" >:[4·6·9·11·17·19·22·26 ⟘ 2·5·10·16·20·21·24·25] ➔ >:-25 =:-23 <:-19) \n",
" =:[1·15·22·25·26 ⟘ 7·14·18·19·20] ➔ >:-18 =:-24 <:-22) \n",
" <:[5·8·12·16·21·22·23·24 ⟘ 2·3·6·11·14·19·20·25] ➔ >:-20 =:-26 <:-21)) \n",
" =:[6·7·10·15·16·20·21·23 ⟘ 8·9·11·13·14·19·24·26] ➔ \n",
" >:[4·10·12·14·19·20 ⟘ 8·9·11·15·22·26] ➔ >:-11 =:-13 <:-14) \n",
" =:[6·11·12·14·21·22·23·25 ⟘ 7·8·9·13·15·16·17·20] ➔ >:-17 =:0 <:-12) \n",
" <:[5·11·12·16·19·24·25·26 ⟘ 2·6·8·10·17·18·20·23] ➔ >:-10 =:-15 <:-16)) \n",
" <:[5·8·9·12·13·14·19·23·25 ⟘ 1·2·6·16·17·18·21·22·24] ➔ \n",
" >:[2·7·8·9·10·13·20·21·22 ⟘ 5·6·11·15·16·17·19·25·26] ➔ >:-6 =:-1 <:-2) \n",
" =:[4·13·14·23·25 ⟘ 1·3·12·18·22] ➔ >:-3 =:-7 <:-4) \n",
" <:[2·3·8·13·14·15·22 ⟘ 1·6·7·9·18·20·26] ➔ >:-9 =:-5 <:-8)))\n"
]
}
],
@ -733,14 +767,12 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Three weighings can theoretically handle at most 27 possibilities, and we used all 27 in the problem above. \n",
"\n",
"Can we solve the 13-ball, heavier-or-lighter problem, which has 26 possibilities? "
]
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": 23,
"metadata": {},
"outputs": [
{
@ -764,7 +796,7 @@
},
{
"cell_type": "code",
"execution_count": 23,
"execution_count": 24,
"metadata": {},
"outputs": [
{
@ -775,7 +807,7 @@
" 'lt': {-4, -3, -2, -1, 10, 11, 12, 13}}"
]
},
"execution_count": 23,
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
@ -786,7 +818,7 @@
},
{
"cell_type": "code",
"execution_count": 24,
"execution_count": 25,
"metadata": {},
"outputs": [
{
@ -797,7 +829,7 @@
" 'lt': {-5, -4, -3, -2, -1, 9, 10, 11, 12, 13}}"
]
},
"execution_count": 24,
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
@ -815,7 +847,7 @@
},
{
"cell_type": "code",
"execution_count": 25,
"execution_count": 26,
"metadata": {},
"outputs": [
{
@ -823,18 +855,18 @@
"output_type": "stream",
"text": [
"[1·2·3·4·5·6·7·8·9 ⟘ 17·18·19·20·21·22·23·24·25] ➔ \n",
" >:[1·5·8·9·11·13·18·22 ⟘ 2·6·7·14·16·17·19·25] ➔ \n",
" >:[2·4·9·10·11·13·16·21·22 ⟘ 3·5·8·12·18·19·20·23·24] ➔ >:+9 =:+1 <:+5) \n",
" =:[3·6·8·11·14·21·24 ⟘ 5·7·10·16·18·19·25] ➔ >:+3 =:-20 <:-24) \n",
" <:[1·2·5·10·12·14·19·21·23 ⟘ 6·7·9·11·13·18·20·24·25] ➔ >:-18 =:-22 <:+7)) \n",
" =:[7·9·10·15·17·20·25 ⟘ 2·3·11·12·16·18·23] ➔ \n",
" >:[4·6·11·12·13·19·22·23 ⟘ 9·16·17·18·20·21·24·25] ➔ >:-16 =:+15 <:-12) \n",
" =:[1·2·6·9·15·17·21·23·25 ⟘ 3·8·10·11·12·13·14·19·22] ➔ >:-14 =:0 <:+13) \n",
" <:[11·18·19 ⟘ 3·13·20] ➔ >:+11 =:-10 <:0)) \n",
" <:[2·9·12·13·17·20·23 ⟘ 4·5·15·18·21·22·25] ➔ \n",
" >:[1·2·10·14·16·17·21·25 ⟘ 5·7·15·18·19·22·23·24] ➔ >:+17 =:-4 <:+23) \n",
" =:[1·4·8·11·13·21 ⟘ 2·5·6·18·23·24] ➔ >:-6 =:+19 <:-8) \n",
" <:[6·9·14·17·21·23 ⟘ 5·12·15·18·20·25] ➔ >:+21 =:-2 <:+25)))\n"
" >:[1·6·7·9·10·16·18·19·24 ⟘ 2·3·4·8·11·15·17·23·25] ➔ \n",
" >:[8·9·13·15·20 ⟘ 2·7·12·14·21] ➔ >:+9 =:+1 <:+7) \n",
" =:[5·11·14·16·18·21·22·24 ⟘ 1·2·6·7·9·10·17·19] ➔ >:+5 =:-20 <:-22) \n",
" <:[8·13·16·17·19·23·25 ⟘ 2·3·4·9·11·15·24] ➔ >:-24 =:-18 <:+3)) \n",
" =:[1·7·11·16·19 ⟘ 4·12·13·15·23] ➔ \n",
" >:[1·3·6·8·16·22·25 ⟘ 2·5·12·17·20·21·24] ➔ >:-12 =:+11 <:0) \n",
" =:[12·14·19 ⟘ 3·10·21] ➔ >:-10 =:0 <:-14) \n",
" <:[3·4·5·7·8·10·15·24 ⟘ 9·13·17·18·19·20·22·25] ➔ >:+15 =:-16 <:+13)) \n",
" <:[2·7·14·16·17·21·22 ⟘ 3·6·15·18·19·24·25] ➔ \n",
" >:[7·9·11·22·23·25 ⟘ 6·8·10·12·18·21] ➔ >:-6 =:+17 <:+21) \n",
" =:[8·11·13·16·19·24 ⟘ 2·3·4·5·15·25] ➔ >:-4 =:+23 <:-8) \n",
" <:[2·8·9·10·11·12·13·25 ⟘ 4·6·14·15·17·20·23·24] ➔ >:+25 =:+19 <:-2)))\n"
]
}
],
@ -845,6 +877,23 @@
"do(p)"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, -24, -22, -20, -18, -16, -14, -12, -10, -8, -6, -4, -2}\n"
]
}
],
"source": [
"print(p.oddballs)"
]
},
{
"cell_type": "markdown",
"metadata": {},