Add files via upload
This commit is contained in:
parent
b684db7aff
commit
4255a36062
933
ipynb/war.ipynb
Normal file
933
ipynb/war.ipynb
Normal file
@ -0,0 +1,933 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div align=\"right\" style=\"text-align: right\"><i>Peter Norvig<br>Aug 2020</i></div>\n",
|
||||
"\n",
|
||||
"# WAR: [What is it Good For?](https://www.youtube.com/watch?v=bX7V6FAoTLc)\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",
|
||||
"\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:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"import collections\n",
|
||||
"from statistics import mean\n",
|
||||
"from itertools import permutations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In my analysis I considered four different approaches to the problem, which I will describe in the order I considered them (although I actually implemented the final one first, and then came back to fill in the implementation of the earlier approaches).\n",
|
||||
"\n",
|
||||
"# Approach 1: Simple Arithmetic?\n",
|
||||
"\n",
|
||||
"A naive approach reasons as follows: there are 13 ranks, so perhaps the outcome probabilities for the first turn are:\n",
|
||||
"\n",
|
||||
" A wins=6/13; B wins=6/13; tie=1/13\n",
|
||||
" \n",
|
||||
"And thus the probability that **A** wins 26 turns in a row would be:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1.8595392516568175e-09"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(6/13) ** 26"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"or about 2 in a billion. Unfortunately, that reasoning is **wrong**, even for one turn. The probability of a tie (that is, both players flipping cards of the same rank) on the first turn is actually 3/51 (not 4/52) because after player **A** flips a card, there are 51 equiprobable cards remaining for player **B**, and 3 have the same rank. So we actually have for the first turn:\n",
|
||||
"\n",
|
||||
" A wins=24/51; B wins=24/51; tie=3/51\n",
|
||||
"\n",
|
||||
"The probabilities on subsequent turns would be slightly different, depending on the cards picked in the previous turns. So simple arithmetic doesn't give us the answer.\n",
|
||||
"\n",
|
||||
"# Approach 2: Brute Force Enumeration?\n",
|
||||
"\n",
|
||||
"Brute force enumeration means:\n",
|
||||
"- Consider every possible permutation of the deck of cards.\n",
|
||||
"- For each permutation, deal out the cards and see whether or not **A** sweeps.\n",
|
||||
"- The probability that **A** sweeps is the number of sweeps divided by the number of permutations.\n",
|
||||
"\n",
|
||||
"Easy-peasy; here's the code. First the types:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Probability = float # Type: Probability is a number between 0.0 and 1.0\n",
|
||||
"Deck = list # Type: Deck is a list of card ranks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now the functions:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def make_deck(ranks=13, suits=4) -> Deck: \n",
|
||||
" \"\"\"Make a list of ranks, each rank repeated `suits` times.\"\"\"\n",
|
||||
" return list(range(ranks)) * suits\n",
|
||||
"\n",
|
||||
"def A_sweeps(deck) -> bool: \n",
|
||||
" \"\"\"Upon dealing this deck, does player A win every turn?\"\"\"\n",
|
||||
" return all(deck[i] > deck[i + 1] for i in range(0, len(deck), 2))\n",
|
||||
"\n",
|
||||
"def brute_p_sweep(deck) -> Probability:\n",
|
||||
" \"\"\"The probability that A sweeps, considering every permutation of deck.\"\"\"\n",
|
||||
" return mean(A_sweeps(d) for d in permutations(deck))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"(Note that in Python the `bool` value `False` is equivalent to `0` and `True` is equivalent to `1`, so the `mean` of an iterable of `bool` values is the same as the proportion or probability of `True` values.)\n",
|
||||
"\n",
|
||||
"Here we run the code on a tiny deck of just 8 cards, all with different ranks:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[0, 1, 2, 3, 4, 5, 6, 7]"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"make_deck(8, 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.0625"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"brute_p_sweep(make_deck(8, 1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"There can be no ties in this deck, so this is just four turns where on each turn **A** and **B** each have an equal chance of winning, so the probability of **A** winning all 4 turns is: "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.0625"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(1/2) ** 4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"If the 8-card deck had 4 ranks and 2 suits, then the probability of **A** sweeping would be less, because there could be ties:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[0, 1, 2, 3, 0, 1, 2, 3]"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"make_deck(4, 2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.03571428571428571"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"brute_p_sweep(make_deck(4, 2))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"What about the real deck, with 52 cards? Unfortunately, there are 52! permutations (more than $10^{67}$), and even if we were clever about the duplicated ranks and the ordering of the 26 turns, and\n",
|
||||
"even if we could process a billion deals a second, it would still take [millions of years](https://www.google.com/search?q=%2852%21+%2F+4%21%5E13+%2F+26%21%29+nanoseconds+in+years&oq=%2852%21+%2F+4%21%5E13+%2F+26%21%29+nanoseconds+in+years) to complete the brute force enumeration. And 538 wanted the answer by Monday.\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Approach 3: Simulation?\n",
|
||||
"\n",
|
||||
"It is simple to code up a simulation that **randomly samples** the space of possible permutations rather than processing **all** the permutations. Just repeatedly shuffle the cards and compute how often **A** sweeps:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def deals(deck, N) -> Deck: \n",
|
||||
" \"\"\"Yield N randomly shuffled deals of deck.\"\"\"\n",
|
||||
" for _ in range(N):\n",
|
||||
" random.shuffle(deck)\n",
|
||||
" yield deck\n",
|
||||
"\n",
|
||||
"def simulate(deck, N=10000) -> Probability:\n",
|
||||
" \"\"\"Simulate N games of war, and return the estimated probability of A sweeping.\"\"\"\n",
|
||||
" return mean(A_sweeps(d) for d in deals(deck, N))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"simulate(make_deck())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Examining 10,000 deals with the full 52-card deck we got zero sweeps. Since the probability of a sweep is probably somewhere around one in a billion, we would need to look at trillions of deals to get a reliable estimate. That would require over a day of run time: much better than millions of years, but still not good enough."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Approach 4: Abstract Incremental Enumeration!\n",
|
||||
"\n",
|
||||
"The first three approaches didn't pan out. What next? \n",
|
||||
"\n",
|
||||
"It is feasible to consider every possible deal if we are careful. As discussed in my [How To Count Things](How%20To%20Count%20Things.ipynb) notebook, the idea is to represent a deck not as a concrete permutation of 52 ranks but rather with a representation that is:\n",
|
||||
"- **Abstract**: What really matters is not the exact identity of every card in the deck, but rather whether **A**'s next card is higher, lower, or the same as **B**'s next card. For example, if there are only two cards remaining and we know they have different ranks, then the probability of **A** winning is 1/2; it doesn't matter whether the cards are [10, 8] or [2, 5] or any other combination of two distinct ranks.\n",
|
||||
"- **Incremental**: First we'll consider the possibilities for the two cards in the first turn, and only if **A** wins will we then move on to consider possible cards for the next turn. If **A** loses or ties the first turn, there is no need to consider the 50! permutations of the remaining cards.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"The key is to focus on how many cards in the deck have other cards of the same rank. Thus, we can represent any set of remaining cards with four numbers:\n",
|
||||
"- The number of ranks that have no other card of the same rank.\n",
|
||||
"- The number of ranks that have exactly 2 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",
|
||||
"\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",
|
||||
"\n",
|
||||
"Consider these sample abstract decks:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ADeck = tuple # Type: Abstract Deck, e.g. (0, 0, 0, 0, 13)\n",
|
||||
"\n",
|
||||
"deck = (0, 0, 0, 0, 13)\n",
|
||||
"tie1 = (0, 0, 1, 0, 12)\n",
|
||||
"dif1 = (0, 0, 0, 2, 11)\n",
|
||||
"end4 = (0, 4, 0, 0, 0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"- `deck` is the normal 52 card full deck with 13 ranks, each with 4 cards of that rank, so `deck[4]` is 13. (This abstract deck represents 52! concrete decks.)\n",
|
||||
"- `tie1` is the deck that results when both players flip a card of the same rank on the first turn. There are 12 ranks with 4 cards each and 1 rank with 2 cards.\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",
|
||||
"\n",
|
||||
"We can compute the number of cards in abstract decks as follows:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def ncards(deck: ADeck) -> int:\n",
|
||||
" \"\"\"Number of cards remaining in an abstract deck.\"\"\"\n",
|
||||
" return sum(i * deck[i] for i in range(len(deck)))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[52, 50, 50, 4]"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"[ncards(d) for d in (deck, tie1, dif1, end4)]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Abstract Incremental Enumeration Strategy\n",
|
||||
"\n",
|
||||
"Now we're ready to outline a strategy to exactly and efficiently compute `p_sweep(deck)`, the probability that **A** sweeps a game of war:\n",
|
||||
"\n",
|
||||
"- Start with `dist` being a **probability distribution** of deck outcomes after 0 turns: `{deck: 1.0}`.\n",
|
||||
"- **for** each of the 26 turns (or in general ncards(deck) / 2 turns):\n",
|
||||
" - **for** each of the entries in `dist`:\n",
|
||||
" - See what possible outcomes can arise from the deck\n",
|
||||
" - update `dist` to include outcomes where **A** might sweep, with appropriate probabilities \n",
|
||||
"- Sum the probabilities in `dist`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class PDist(collections.Counter): \n",
|
||||
" \"\"\"A probability distribution of {deck: probability}.\"\"\"\n",
|
||||
"\n",
|
||||
"def p_sweep(deck: ADeck) -> Probability:\n",
|
||||
" \"\"\"The probability that player A sweeps a game of war.\"\"\"\n",
|
||||
" dist = PDist({deck: 1.0})\n",
|
||||
" for turn in range(ncards(deck) // 2):\n",
|
||||
" dist = play_turn(dist)\n",
|
||||
" return sum(dist.values())\n",
|
||||
"\n",
|
||||
"def play_turn(dist) -> PDist:\n",
|
||||
" \"\"\"Play one turn with all possible card choices for players A and B; return\n",
|
||||
" the probability distribution of outcomes where A still might sweep.\"\"\"\n",
|
||||
" P = PDist()\n",
|
||||
" for deck in dist:\n",
|
||||
" for deck2, p_Awin in select2(deck):\n",
|
||||
" P[deck2] += dist[deck] * p_Awin\n",
|
||||
" return P"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now the key is figuring out:\n",
|
||||
"- All the possible ways to select two cards from the deck.\n",
|
||||
"- The probability of that selection.\n",
|
||||
"- Who won (or was it a tie) with that selection.\n",
|
||||
"figuring out what the probab ility of that selection is, figuring out"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"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",
|
||||
" \"\"\"All ways to pick one card from deck, returning a list of:\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, i)\n",
|
||||
" for i in range(len(deck)) if deck[i]]\n",
|
||||
"\n",
|
||||
"def remove(i, deck):\n",
|
||||
" \"\"\"Remove one card from deck[i].\"\"\"\n",
|
||||
" deck1 = list(deck)\n",
|
||||
" deck1[i] -= 1\n",
|
||||
" if i - 1 != 0:\n",
|
||||
" deck1[i - 1] += 1\n",
|
||||
" return tuple(deck1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# The Answer!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"3.132436174322294e-09"
|
||||
]
|
||||
},
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"p_sweep(deck)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"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.) "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Gaining Confidence in the Answer\n",
|
||||
"\n",
|
||||
"Above we computed that the probability of **A** winning the first turn is exactly 24/51. If every other turn were the same, the probability of a sweep would be:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"3.0808297965386556e-09"
|
||||
]
|
||||
},
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(24/51) ** 26"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This is within 2% of the answer we got, giving some credence to our answer.\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:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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))}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{0.049999999999999996, 0.05}"
|
||||
]
|
||||
},
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 6 cards (2 ranks and 3 suits)\n",
|
||||
"{brute_p_sweep(make_deck(2, 3)), p_sweep((0, 0, 0, 2))}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"metadata": {},
|
||||
"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",
|
||||
"\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",
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"all(p_sweep((0, 2 * N)) == (1/2) ** N\n",
|
||||
" for N in range(200))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Working through the algorithm\n",
|
||||
"\n",
|
||||
"Let's work through how `p_sweep`, `play_turn`, `select1` and `select2` work. We'll start by reminding ourself what the starting `deck` is, and then `select1` card from it:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0, 0, 0, 0, 13)"
|
||||
]
|
||||
},
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"deck"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[((0, 0, 0, 1, 12), 1.0, 4)]"
|
||||
]
|
||||
},
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"select1(deck)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This is saying with probability 1.0 the result of selecting one card is a deck where there are 12 ranks with four-of-a-kind and 1 rank with three-of-a-kind. The selected card came from index 4 in the deck.\n",
|
||||
"\n",
|
||||
"Now we'll try selecting two cards:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[((0, 0, 0, 2, 11), 0.47058823529411764)]"
|
||||
]
|
||||
},
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"list(select2(deck))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This says that the only result of the first turn in which **A** wins has 11 ranks with four-of-a-kind and 2 ranks with 3-of-a-kind. The probability of this outcome is 0.47058823529411764, which, as we computed earlier, is 24/51. The rest of the probability goes to an equiprobable result in which **B** wins, and to `(0, 0, 1, 0, 12)`, which indicates a tie on the first turn. Since these outcomes don't result in **A** winning, they do not appeear in the result from `select2`.\n",
|
||||
"\n",
|
||||
"Now let's work through how `p_sweep` repeatedly calls `play_turn`. We start with a probability distribution where we have the initial deck with probability 1.0:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"PDist({(0, 0, 0, 0, 13): 1.0})"
|
||||
]
|
||||
},
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"dist = PDist({deck: 1.0})\n",
|
||||
"dist"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's the outcome of playing the first turn:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 29,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"PDist({(0, 0, 0, 2, 11): 0.47058823529411764})"
|
||||
]
|
||||
},
|
||||
"execution_count": 29,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"dist = play_turn(dist)\n",
|
||||
"dist"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now for the second turn:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 30,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"PDist({(0, 0, 2, 0, 11): 0.0017286914765906362,\n",
|
||||
" (0, 0, 1, 2, 10): 0.05070828331332534,\n",
|
||||
" (0, 0, 0, 4, 9): 0.16902761104441777})"
|
||||
]
|
||||
},
|
||||
"execution_count": 30,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"dist = play_turn(dist)\n",
|
||||
"dist"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And the third turn:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 31,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"PDist({(0, 2, 0, 0, 11): 3.0650558095578654e-06,\n",
|
||||
" (0, 1, 1, 1, 10): 0.00040458736686163827,\n",
|
||||
" (0, 0, 2, 2, 9): 0.010114684171540957,\n",
|
||||
" (0, 1, 0, 3, 9): 0.0017981660749406148,\n",
|
||||
" (0, 0, 3, 0, 10): 0.00020229368343081916,\n",
|
||||
" (0, 0, 1, 4, 8): 0.048550484023396595,\n",
|
||||
" (0, 0, 0, 6, 7): 0.04315598579857475})"
|
||||
]
|
||||
},
|
||||
"execution_count": 31,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"dist = play_turn(dist)\n",
|
||||
"dist"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We'll leave it as an exercise for the reader to work through these, but one thing you can clearly see is that the total probability of winning is becoming smaller with every turn:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 32,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.10422926617455494"
|
||||
]
|
||||
},
|
||||
"execution_count": 32,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"sum(dist.values())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"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 Duane’s friend’s granddaughter's claim that she won 26 in a row."
|
||||
]
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
Loading…
Reference in New Issue
Block a user