Add files via upload

This commit is contained in:
Peter Norvig 2020-08-31 16:52:15 -07:00 committed by GitHub
parent 4255a36062
commit 318cc8e0ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -8,7 +8,7 @@
"\n", "\n",
"# WAR: [What is it Good For?](https://www.youtube.com/watch?v=bX7V6FAoTLc)\n", "# WAR: [What is it Good For?](https://www.youtube.com/watch?v=bX7V6FAoTLc)\n",
"\n", "\n",
"The [538 Riddler Classic for 28 August 2020](https://fivethirtyeight.com/features/can-you-cover-the-globe/) asks about the probability of winning the children's [card game **war**](https://en.wikipedia.org/wiki/War_%28card_game%29) in a **sweep**: player **A** wins 26 turns in a row against player **B**. (In **war**, players are dealt 26 cards each and on each turn they flip the top card in their respective hands; higher rank card wins. Other rules are not relevant to this problem.)\n", "The [538 Riddler Classic for 28 August 2020](https://fivethirtyeight.com/features/can-you-cover-the-globe/) asks about the probability of winning the children's [card game **war**](https://en.wikipedia.org/wiki/War_%28card_game%29) in a **sweep**: a game where player **A** wins 26 turns in a row against player **B**. (In **war**, players are dealt 26 cards each and on each turn they flip the top card in their respective hands; higher rank card wins. Other rules are not relevant to this problem.)\n",
"\n", "\n",
"We'll analyze this problem and come up with a program to solve it; first let's get the imports out of the way:" "We'll analyze this problem and come up with a program to solve it; first let's get the imports out of the way:"
] ]
@ -20,9 +20,9 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"import random\n", "import random\n",
"import itertools\n",
"import collections\n", "import collections\n",
"from statistics import mean\n", "from statistics import mean"
"from itertools import permutations"
] ]
}, },
{ {
@ -44,18 +44,7 @@
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 2,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [],
{
"data": {
"text/plain": [
"1.8595392516568175e-09"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"(6/13) ** 26" "(6/13) ** 26"
] ]
@ -113,7 +102,7 @@
"\n", "\n",
"def brute_p_sweep(deck) -> Probability:\n", "def brute_p_sweep(deck) -> Probability:\n",
" \"\"\"The probability that A sweeps, considering every permutation of deck.\"\"\"\n", " \"\"\"The probability that A sweeps, considering every permutation of deck.\"\"\"\n",
" return mean(A_sweeps(d) for d in permutations(deck))" " return mean(A_sweeps(d) for d in itertools.permutations(deck))"
] ]
}, },
{ {
@ -129,18 +118,7 @@
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 5,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [],
{
"data": {
"text/plain": [
"[0, 1, 2, 3, 4, 5, 6, 7]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"make_deck(8, 1)" "make_deck(8, 1)"
] ]
@ -320,7 +298,7 @@
"- The number of ranks that have exactly 3 cards of the same rank.\n", "- The number of ranks that have exactly 3 cards of the same rank.\n",
"- The number of ranks that have exactly 4 cards of the same rank. \n", "- The number of ranks that have exactly 4 cards of the same rank. \n",
"\n", "\n",
"An abstract deck (or `ADeck`) is a tuple where `deck[i]` gives the number of different ranks that have `i` cards in the deck. (This representation is a bit wasteful, because `deck[0]` is always a redundant `0`, but the alternative would be to sprinkle the code with a bunch of `[i - 1]` indexes, risking an off-by-one error).\n", "An abstract deck (or `AbDeck`) is a tuple where `deck[i]` gives the number of different ranks that have `i` cards in the deck. (This representation is a bit wasteful, because `deck[0]` is always a redundant `0`, but the alternative would be to sprinkle the code with a bunch of `[i - 1]` indexes, risking an off-by-one error).\n",
"\n", "\n",
"Consider these sample abstract decks:" "Consider these sample abstract decks:"
] ]
@ -331,7 +309,7 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"ADeck = tuple # Type: Abstract Deck, e.g. (0, 0, 0, 0, 13)\n", "AbDeck = tuple # Type: Abstract Deck, e.g. (0, 0, 0, 0, 13)\n",
"\n", "\n",
"deck = (0, 0, 0, 0, 13)\n", "deck = (0, 0, 0, 0, 13)\n",
"tie1 = (0, 0, 1, 0, 12)\n", "tie1 = (0, 0, 1, 0, 12)\n",
@ -348,7 +326,7 @@
"- `dif1` is the deck that results when the players flip cards of different ranks on the first turn. There are 11 ranks each with 4 cards and 2 ranks with 3 cards. `tie1` and `dif1` are the only two possible results for turn 1.\n", "- `dif1` is the deck that results when the players flip cards of different ranks on the first turn. There are 11 ranks each with 4 cards and 2 ranks with 3 cards. `tie1` and `dif1` are the only two possible results for turn 1.\n",
"- `end4` is a deck you would see near the end of a game, with just 4 remaining cards, each of a different rank. (This represents 52 × 48 × 44 × 40 = 4,392,960 concrete decks.)\n", "- `end4` is a deck you would see near the end of a game, with just 4 remaining cards, each of a different rank. (This represents 52 × 48 × 44 × 40 = 4,392,960 concrete decks.)\n",
"\n", "\n",
"We can compute the number of cards in abstract decks as follows:" "We can create abstract decks and compute the number of cards in abstract decks as follows:"
] ]
}, },
{ {
@ -357,7 +335,11 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"def ncards(deck: ADeck) -> int:\n", "def make_abdeck(ranks=13, suits=4) -> AbDeck: \n",
" \"\"\"Make an abstract deck.\"\"\"\n",
" return (0,) * suits + (ranks,)\n",
" \n",
"def ncards(deck: AbDeck) -> int:\n",
" \"\"\"Number of cards remaining in an abstract deck.\"\"\"\n", " \"\"\"Number of cards remaining in an abstract deck.\"\"\"\n",
" return sum(i * deck[i] for i in range(len(deck)))" " return sum(i * deck[i] for i in range(len(deck)))"
] ]
@ -407,7 +389,7 @@
"class PDist(collections.Counter): \n", "class PDist(collections.Counter): \n",
" \"\"\"A probability distribution of {deck: probability}.\"\"\"\n", " \"\"\"A probability distribution of {deck: probability}.\"\"\"\n",
"\n", "\n",
"def p_sweep(deck: ADeck) -> Probability:\n", "def p_sweep(deck: AbDeck) -> Probability:\n",
" \"\"\"The probability that player A sweeps a game of war.\"\"\"\n", " \"\"\"The probability that player A sweeps a game of war.\"\"\"\n",
" dist = PDist({deck: 1.0})\n", " dist = PDist({deck: 1.0})\n",
" for turn in range(ncards(deck) // 2):\n", " for turn in range(ncards(deck) // 2):\n",
@ -441,23 +423,25 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"def select2(deck):\n",
" \"\"\"All ways to select two cards from deck on a turn, yielding tuples:\n",
" (remaining deck, probability that deck occurs and player A wins).\"\"\"\n",
" for deck1, p_i, i in select1(deck):\n",
" for deck2, p_j, j in select1(deck1):\n",
" p_tie = 0 if j != i - 1 else 1 / deck1[j]\n",
" p_Awin = (1 - p_tie) / 2\n",
" if p_Awin > 0:\n",
" yield deck2, p_i * p_j * p_Awin\n",
" \n",
"def select1(deck):\n", "def select1(deck):\n",
" \"\"\"All ways to pick one card from deck, returning a list of:\n", " \"\"\"All ways to pick one card from deck, returning a list of:\n",
" (remaining deck, probability of pick, index of pick in deck)\"\"\"\n", " (remaining deck, probability of pick, index of pick in deck)\"\"\"\n",
" Ncards = sum(i * deck[i] for i in range(len(deck)))\n", " return [(remove(i, deck), i * deck[i] / ncards(deck), i)\n",
" return [(remove(i, deck), i * deck[i] / Ncards, i)\n",
" for i in range(len(deck)) if deck[i]]\n", " for i in range(len(deck)) if deck[i]]\n",
"\n", "\n",
"def select2(deck):\n",
" \"\"\"All ways to select two cards from deck on a turn, yielding tuples:\n",
" (remaining deck, probability that deck occurs and player A wins).\"\"\"\n",
" return [(deck2, p_i * p_j * p_Awin(deck1, i, j))\n",
" for deck1, p_i, i in select1(deck)\n",
" for deck2, p_j, j in select1(deck1)\n",
" if p_Awin(deck1, i, j) > 0]\n",
" \n",
"def p_Awin(deck1, i, j) -> Probability:\n",
" \"\"\"Probability that A wins turn when A selects i giving deck1 and B selects j.\"\"\"\n",
" p_tie = 0 if j != i - 1 else 1 / deck1[j]\n",
" return (1 - p_tie) / 2\n",
"\n",
"def remove(i, deck):\n", "def remove(i, deck):\n",
" \"\"\"Remove one card from deck[i].\"\"\"\n", " \"\"\"Remove one card from deck[i].\"\"\"\n",
" deck1 = list(deck)\n", " deck1 = list(deck)\n",
@ -471,7 +455,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# The Answer!" "# The Answer!\n",
"\n",
"What's the probability that player **A** will win in a sweep?"
] ]
}, },
{ {
@ -498,7 +484,36 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The probability that **A** sweeps a game of war is a little over 3 in a billion. (The computation took well under a second.) " "The probability that **A** sweeps a game of war is a little over 3 in a billion. I have to say, I'm begining to doubt Duanes friends granddaughter's claim that she won 26 in a row.(By the way, this computation took less than 1/10 second, a big improvement over millions of years.) \n",
"\n",
"538 Riddler actually posed the question somewhat differently: \"*How many games of War would you expect to play until you had a game that lasted just 26 turns with no Wars, like Duanes friends granddaughter?*\" That is, they want to know the inverse of the probability, and they are allowing for either **A** or **B** to sweep. So that would be:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"159620171.7049113"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"1 / (2 * p_sweep(deck))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" We would expect a sweep about once every 160 million games."
] ]
}, },
{ {
@ -512,7 +527,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 18, "execution_count": 19,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -521,7 +536,7 @@
"3.0808297965386556e-09" "3.0808297965386556e-09"
] ]
}, },
"execution_count": 18, "execution_count": 19,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -534,30 +549,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"This is within 2% of the answer we got, giving some credence to our answer.\n", "This is within 2% of the answer we got, lending some credence to our answer.\n",
"\n", "\n",
"The brute force algorithm can't handle a big deck, but we can use it to verify that `p_sweep` gets the same answers as `brute_p_sweep` for small decks:" "The brute force algorithm can't handle a big deck, but we can use it to verify that `p_sweep` gets the same answers as `brute_p_sweep` for small decks; in the printout below every case has a `diff` of zero):"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{0.06666666666666667, 0.06666666666666668}"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 6 cards (3 ranks and 2 suits)\n",
"{brute_p_sweep(make_deck(3, 2)), p_sweep((0, 0, 3))}"
] ]
}, },
{ {
@ -566,96 +560,39 @@
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "name": "stdout",
"text/plain": [ "output_type": "stream",
"{0.049999999999999996, 0.05}" "text": [
"ranks,suits: 2,1; p: 0.50000; diff: 0.000000000\n",
"ranks,suits: 4,1; p: 0.25000; diff: 0.000000000\n",
"ranks,suits: 6,1; p: 0.12500; diff: 0.000000000\n",
"ranks,suits: 8,1; p: 0.06250; diff: 0.000000000\n",
"ranks,suits: 2,2; p: 0.16667; diff: 0.000000000\n",
"ranks,suits: 2,3; p: 0.05000; diff: 0.000000000\n",
"ranks,suits: 3,2; p: 0.06667; diff: 0.000000000\n",
"ranks,suits: 2,4; p: 0.01429; diff: 0.000000000\n",
"ranks,suits: 4,2; p: 0.03571; diff: 0.000000000\n"
] ]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
} }
], ],
"source": [ "source": [
"# 6 cards (2 ranks and 3 suits)\n", "for r, s in [(2, 1), (4, 1), (6, 1), (8, 1),\n",
"{brute_p_sweep(make_deck(2, 3)), p_sweep((0, 0, 0, 2))}" " (2, 2), (2, 3), (3, 2), (2, 4), (4, 2)]:\n",
] " p = brute_p_sweep(make_deck(r, s))\n",
}, " diff = abs(p - p_sweep(make_abdeck(r, s)))\n",
{ " print(f'ranks,suits: {r},{s}; p: {p:.5f}; diff: {diff:.9f}')"
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{0.0625}"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 8 cards (8 different ranks)\n",
"{brute_p_sweep(make_deck(8, 1)), p_sweep((0, 8))}"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{0.03571428571428571}"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 8 cards (4 ranks and 2 suits)\n",
"{brute_p_sweep(make_deck(4, 2)), p_sweep((0, 0, 4))}"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{0.014285714285714284, 0.014285714285714285}"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 8 cards (2 ranks and 4 suits)\n",
"{brute_p_sweep(make_deck(2, 4)), p_sweep((0, 0, 0, 0, 2))}"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We see that in every case the resulting set either has one member (indicating that the two computations got exactly the same result) or two members that differ only in the final digit (indicating floating-point round-off error).\n", "And we can do some unit tests on components (we should do more):"
"\n",
"We can also test `p_sweep` on larger decks that we know the answer for. If there are `2N` cards in a deck, all with different ranks, then the probability that **A** sweeps is `(1/2) ** N`. We can test that:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 24, "execution_count": 21,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -664,14 +601,29 @@
"True" "True"
] ]
}, },
"execution_count": 24, "execution_count": 21,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"all(p_sweep((0, 2 * N)) == (1/2) ** N\n", "def test():\n",
" for N in range(200))" " # If there are `2N` cards in a deck, all with different ranks, \n",
" # then the probability that A sweeps is `(1/2) ** N`.\n",
" for N in range(200):\n",
" assert p_sweep((0, 2 * N)) == (1/2) ** N\n",
" # Remove a single card from abstract deck\n",
" assert remove(4, (0, 0, 0, 0, 13)) == (0, 0, 0, 1, 12)\n",
" assert remove(4, (0, 0, 0, 1, 12)) == (0, 0, 0, 2, 11)\n",
" assert remove(3, (0, 0, 0, 1, 12)) == (0, 0, 1, 0, 12)\n",
" # Count cards in abstract deck\n",
" assert [ncards(d) for d in (deck, tie1, dif1, end4)] == [52, 50, 50, 4]\n",
" # Select cards\n",
" assert select1((0, 0, 0, 0, 13)) == [((0, 0, 0, 1, 12), 1.0, 4)]\n",
" assert select2((0, 0, 0, 0, 13)) == [((0, 0, 0, 2, 11), 0.47058823529411764)]\n",
" return True\n",
" \n",
"test()"
] ]
}, },
{ {
@ -685,7 +637,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 25, "execution_count": 22,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -694,7 +646,7 @@
"(0, 0, 0, 0, 13)" "(0, 0, 0, 0, 13)"
] ]
}, },
"execution_count": 25, "execution_count": 22,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -705,7 +657,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 26, "execution_count": 23,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -714,7 +666,7 @@
"[((0, 0, 0, 1, 12), 1.0, 4)]" "[((0, 0, 0, 1, 12), 1.0, 4)]"
] ]
}, },
"execution_count": 26, "execution_count": 23,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -734,7 +686,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 27, "execution_count": 24,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -743,13 +695,13 @@
"[((0, 0, 0, 2, 11), 0.47058823529411764)]" "[((0, 0, 0, 2, 11), 0.47058823529411764)]"
] ]
}, },
"execution_count": 27, "execution_count": 24,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"list(select2(deck))" "select2(deck)"
] ]
}, },
{ {
@ -763,7 +715,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 28, "execution_count": 25,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -772,7 +724,7 @@
"PDist({(0, 0, 0, 0, 13): 1.0})" "PDist({(0, 0, 0, 0, 13): 1.0})"
] ]
}, },
"execution_count": 28, "execution_count": 25,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -791,7 +743,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 29, "execution_count": 26,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -800,7 +752,7 @@
"PDist({(0, 0, 0, 2, 11): 0.47058823529411764})" "PDist({(0, 0, 0, 2, 11): 0.47058823529411764})"
] ]
}, },
"execution_count": 29, "execution_count": 26,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -819,7 +771,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 30, "execution_count": 27,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -830,7 +782,7 @@
" (0, 0, 0, 4, 9): 0.16902761104441777})" " (0, 0, 0, 4, 9): 0.16902761104441777})"
] ]
}, },
"execution_count": 30, "execution_count": 27,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -849,7 +801,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 31, "execution_count": 28,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -864,7 +816,7 @@
" (0, 0, 0, 6, 7): 0.04315598579857475})" " (0, 0, 0, 6, 7): 0.04315598579857475})"
] ]
}, },
"execution_count": 31, "execution_count": 28,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -883,7 +835,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 32, "execution_count": 29,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -892,7 +844,7 @@
"0.10422926617455494" "0.10422926617455494"
] ]
}, },
"execution_count": 32, "execution_count": 29,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -905,7 +857,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"You have only a 10% chance of winning three turns in a row. This will steadily fall to 3-in-a-billion after 26 turns. I have to say, I'm begining to doubt Duanes friends granddaughter's claim that she won 26 in a row." "You have only a 10% chance of winning three turns in a row. This will steadily fall to 3-in-a-billion after 26 turns. "
] ]
} }
], ],