Upload "Orderable Cards.ipynb"
This commit is contained in:
parent
dbe56cea9c
commit
8489cb1bd2
@ -12,16 +12,18 @@
|
||||
"\n",
|
||||
"> *Suppose you’re playing pitch, in which a hand has six cards. What are the odds that you can accomplish your obsessive goal? What about for another game, where a hand has N cards, somewhere between 1 and 13?*\n",
|
||||
"\n",
|
||||
"The first thing to decide is how many `N`-card hands are there? That will tell if I can just use brute force, looking at every possible hand. The answer is (52 choose `N`), so we have:\n",
|
||||
"# Brute Force Versus Cleverness\n",
|
||||
"\n",
|
||||
"The first thing to decide is how many `N`-card hands are there? If there are only a few, I could just enumerate them all and count. If there are a lot, I'll have to be more clever. The answer is (52 choose `N`), so we have:\n",
|
||||
"\n",
|
||||
"- 6 cards: 20,358,520 hands\n",
|
||||
"- 13 cards: 635,013,559,600 hands \n",
|
||||
"\n",
|
||||
"That's too many hands. \n",
|
||||
"That's a lot. Time for a small dose of cleverness. \n",
|
||||
"\n",
|
||||
"# Abstract Hands\n",
|
||||
"# Abstract Hands: Suits\n",
|
||||
"\n",
|
||||
"I notice that the problem states *\"Numbers don’t matter,\"* so I can abstract away from *cards* to *suits*: instead of saying that the first card in this hand is the seven of spades, I can just say it is a spade. Then there are only 4<sup>N</sup> abstract hands (for N ≤ 13), so we have:\n",
|
||||
"I notice that the problem says *\"Numbers don’t matter,\"* so I can abstract away from *cards* to *suits*: instead of saying that the first card in this hand is the seven of spades, I can just say it is a spade. Then there are only 4<sup>N</sup> abstract hands (for N ≤ 13), so we have:\n",
|
||||
"\n",
|
||||
"- 6 cards: 4,096 abstract hands\n",
|
||||
"- 13 cards: 67,108,864 abstract hands\n",
|
||||
@ -45,7 +47,6 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import re\n",
|
||||
"from fractions import Fraction\n",
|
||||
"from functools import lru_cache\n",
|
||||
"\n",
|
||||
@ -106,41 +107,22 @@
|
||||
"source": [
|
||||
"Is that right? Yes it is. The probability of `'BB'` is 1/17, beause the probability of the first `'B'` is 13/52 or 1/4, and when we deal the second card, one `'B'` is gone, so the probability is 12/51, so that simplifies to 1/4 × 12/51 = 3/51 = 1/17. The probability of `'BR'` is 13/204, because the probability of the `'R'` is 13/51, and 1/4 × 13/51 = 13/204.\n",
|
||||
"\n",
|
||||
"# Collapsing Hands\n",
|
||||
"# More Abstraction: Collapsed Runs\n",
|
||||
"\n",
|
||||
"I'll introduce the idea of *collapsing* an abstract hand by replacing a run of cards of the same suit with a single card, so that: \n",
|
||||
"\n",
|
||||
" collapse('BBBBBrrrrBBBB') == 'BrB'\n",
|
||||
" \n",
|
||||
"From now on I'll just say *hand* rather than *abstract hand* for `'BBBBBrrrrBBBB'`, and I'll use the term *sequence* (or the abbreviation *seq*) for the collapsed version, `'BrB'`.\n",
|
||||
"\n",
|
||||
"# Properly Ordered Hands\n",
|
||||
"\n",
|
||||
"A hand is considered properly `ordered` if *\"the cards of a given suit are grouped together and, if possible, such that no suited groups of the same color are adjacent.\"* I was initially confused about the meaning of *\"if possible\";* Matt Ginsberg confirmed it means *\"if it is possible to separate the colors in any number of moves\"*, and thus that the hand `'BBBbbb'` is properly ordered, because it is not possible to separate the two black suits, while `'BBBbbR'` is not properly ordered, because the red card could have been inserted between the two black runs.\n",
|
||||
"\n",
|
||||
"So a hand is properly ordered if, considering its collapsed sequence, each suit appears only once, and either all the colors are the same, or suits of the same color don't appear adjacent to each other."
|
||||
"Now for a second abstraction: the idea that an abstract hand can be *collapsed* by replacing a run of cards of the same suit with a single card, so that `'BBBBBrrrrBBBB'` collapses to `'BrB'`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def ordered(hand):\n",
|
||||
" \"Properly ordered if each suit run appears only once, and same color suits not adjacent.\"\n",
|
||||
" seq = collapse(hand)\n",
|
||||
" return once_each(seq) and (len(colors(seq)) == 1 or not adjacent_colors(seq))\n",
|
||||
" \n",
|
||||
"def collapse(hand):\n",
|
||||
" \"Collapse identical adjacent characters to one character.\"\n",
|
||||
" return ''.join(hand[i] for i in range(len(hand)) if i == 0 or hand[i] != hand[i-1])\n",
|
||||
"\n",
|
||||
"def once_each(seq): return len(seq) == len(set(seq))\n",
|
||||
"def colors(seq): return set(seq.casefold())\n",
|
||||
"adjacent_colors = re.compile('rR|Rr|Bb|bB').search"
|
||||
" \"Collapse runs of adjacent identical suits with a single suit.\"\n",
|
||||
" return ''.join(hand[i] for i in range(len(hand)) if i == 0 or hand[i] != hand[i-1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -165,12 +147,70 @@
|
||||
"collapse('BBBBBrrrrBBBB')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"From now on I'll just say *hand* rather than *abstract hand* for `'BBBBBrrrrBBBB'`, and I'll use the term *sequence* (or the abbreviation *seq*) for the collapsed version, `'BrB'`.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Properly Ordered Hands\n",
|
||||
"\n",
|
||||
"A hand is considered properly `ordered` if *\"the cards of a given suit are grouped together and, if possible, such that no suited groups of the same color are adjacent.\"* I was initially confused about the meaning of *\"if possible\";* Matt Ginsberg confirmed it means *\"if it is possible to separate the colors in any number of moves\"*, and thus that the hand `'BBBbbb'` is properly ordered, because it is not possible to separate the two black suits, while `'BBBbbR'` is not properly ordered, because the red card could have been inserted between the two black runs.\n",
|
||||
"\n",
|
||||
"A hand is properly ordered if and only if its collapsed sequence is properly ordered, and a sequence is properly ordered if each suit appears only once, and either all the colors are the same, or suits of the same color don't appear adjacent to each other."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def ordered(hand):\n",
|
||||
" \"Properly ordered if each suit run appears only once, and same color suits not adjacent.\"\n",
|
||||
" seq = collapse(hand)\n",
|
||||
" return once_each(seq) and (len(colors(seq)) == 1 or not adjacent_colors(seq))\n",
|
||||
" \n",
|
||||
"def adjacent_colors(seq): \n",
|
||||
" \"Do two suits of the same color appear next to each other in seq?\"\n",
|
||||
" return any(pair in seq for pair in ('rR', 'Rr', 'Bb', 'bB'))\n",
|
||||
"\n",
|
||||
"def once_each(seq): return len(seq) == len(set(seq))\n",
|
||||
"\n",
|
||||
"def colors(seq): return set(seq.casefold())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ordered('BBBRbb')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
@ -178,7 +218,7 @@
|
||||
"False"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@ -199,7 +239,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 8,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
@ -242,7 +282,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 9,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -268,7 +308,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": 10,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -297,27 +337,70 @@
|
||||
"report(range(7))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's make sure that each `deals(N)` covers everything: that I got all 4<sup>N</sup> hands, and that the probabilities sum to 1:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for N in range(7):\n",
|
||||
" assert len(deals(N)) == 4 ** N\n",
|
||||
" assert sum(deals(N).values()) == 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Getting to `N` = 13\n",
|
||||
"\n",
|
||||
"That looks good, but if we want to get to 13-card hands, we would have to handle 4<sup>13</sup> = 67,108,864 `deals`, which would take a while. I can speed things up by taking advantage of two key properties of orderable sequences:\n",
|
||||
"So far so good, but if we want to get to 13-card hands, we would have to handle 4<sup>13</sup> = 67,108,864 `deals`, which would take a while. I can speed things up by taking advantage of two key properties of orderable sequences:\n",
|
||||
"\n",
|
||||
"1. **An orderable sequence can have at most 7 runs.** We know that a properly ordered hand can have at most 4 runs. But a move can reduce the number of runs by only 3 at the most: one run can be reduced when we remove the block (if the cards on either side of the block are the same), and two more can be reduced when we re-insert the block (if the left and right ends of the block match the suits to the left and right of the new position). Here's an example of moving a block [bracketed] to reduce the number of runs from 6 to 3:\n",
|
||||
"\n",
|
||||
" bRB[bR]B => b[bR]RBB = bRB\n",
|
||||
" \n",
|
||||
"2. **Adding a suit to the end of an unorderable sequence can't make it orderable.** Even after we move a block, you can't make an unordered hand ordered by inserting one suit.\n",
|
||||
"2. **Adding a suit to the end of an unorderable sequence can't make it orderable.** To show that, take an unordered sequence, and see what happens if you take the extra suit and insert it anywhere in the sequence. If the sequence was unordered because it repeats a suit, adding a suit can't fix that. If it was unordered because suits of the same color are adjacent, then adding a suit of the other color *could* fix that: `'bBR'` could be fixed by inserting a `'r'` to get `'brBR'`. But here's the catch: `'bBR'` is not unorderable. And if we are going to insert a new suit between two others, that means that the original sequence must have had at most three suits (because when we add one, we can't get more than four suits in an ordered sequence), and *every* three-suit sequence is orderable. And if that argument doesn't convince you, below I collect all the unorderable sequences up to length 7 (which we know is the longest length we need to look at) and show that no matter what suit you add, the result is unorderable.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"I'll redefine `deals(N)` to hold only orderable hands, redefine `orderable(seq)` to immediately reject sequences longer than 7, and redefine `orderable_probability(N)` to just add up the probabilities in `deals(N)`, since everything there is orderable."
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": 12,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"seqs = {collapse(hand) for N in range(1, 8) for hand in deals(N)}\n",
|
||||
"unorderable = {seq for seq in seqs if not orderable(seq)}\n",
|
||||
"\n",
|
||||
"for seq in unorderable:\n",
|
||||
" for suit in suits:\n",
|
||||
" assert not orderable(seq + suit)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"I'll redefine `deals(N)` to hold only orderable hands, redefine `orderable(seq)` to immediately reject sequences longer than 7, and redefine `orderable_probability(N)` to just add up the probabilities in `deals(N)`, since everything in there is now orderable."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -355,7 +438,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": 14,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -378,8 +461,8 @@
|
||||
"11: 1.9% = 22673450197/1219690678500\n",
|
||||
"12: 0.7% = 1751664923/238130084850\n",
|
||||
"13: 0.3% = 30785713171/11112737293000\n",
|
||||
"CPU times: user 15 s, sys: 245 ms, total: 15.2 s\n",
|
||||
"Wall time: 16.3 s\n"
|
||||
"CPU times: user 16.8 s, sys: 402 ms, total: 17.2 s\n",
|
||||
"Wall time: 19.5 s\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -398,7 +481,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"execution_count": 15,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -409,7 +492,7 @@
|
||||
"CacheInfo(hits=1438512, misses=1540, maxsize=None, currsize=1540)"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@ -433,7 +516,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"execution_count": 16,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -444,7 +527,7 @@
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user