xkcd-Name-Dominoes.ipynb

This commit is contained in:
Peter Norvig authored and GitHub committed 2018-03-22 23:19:08 -07:00
1 parent d5b3f6128e
commit 9703198373
1 file changed
+248 -202
+248 -202
View File
@@ -4,15 +4,15 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"<div style=\"text-align:right\">Peter Norvig<br>21 March 2018</div>\n", "<span style=\"text-align:right\">Peter Norvig<br>21 March 2018</span>\n",
"\n", "\n",
"# `xkcd` Name Dominoes\n", "# `xkcd` Name Dominoes\n",
"\n", "\n",
"The March 21, 2018 `xkcd` comic number 1970 was [Name Dominoes](https://xkcd.com/1970/): domino tiles laid out in a legal array,\n", "The March 21, 2018 `xkcd` comic number 1970 was [Name Dominoes](https://xkcd.com/1970/): domino tiles laid out as if in a game, but with the tiles containing names of famous poeple rather than numbers. \n",
"but with each tile having names of famous poeple rather than numbers. In regular [dominoes](https://en.wikipedia.org/wiki/Dominoes), each tile has two halves, and a tile can be legally placed only if one half is adjacent to another tile with the same number/name, and neither half is adjacent to any tile with a different number/name. (The very first tile in a game has no adjacent tiles, so it can be placed anywhere.) I will write a function to lay out dominoes in a random, legal array. I'll start with two key data structures:\n", "In [dominoes](https://en.wikipedia.org/wiki/Dominoes) each tile has two halves, and a tile can be legally placed only if one half is adjacent to another tile with the same number/name, and neither half is adjacent to any tile with a different number/name. (The very first tile in a game has no adjacent tiles, so it can be placed anywhere.) I will write a function to lay out dominoes in a random, legal array. I'll start with two key data structures:\n",
"\n", "\n",
"- **`Tile`**: a tile is a 2-element tuple, like `('Tim', 'Cook')`, indicating the two halves.\n", "- **`Tile`**: a tile is a 2-element tuple, like `('Tim', 'Cook')`, indicating the two halves.\n",
"- **`Board(n)`**: Creates an [n × n] 2-dimensional array of locations; each location holds as a value one half of a tile, or it can be `empty`, or it can be a `border`, meaning nothing can be placed there.\n" "- **`Board(w, h)`**: a [w × h] 2-dimensional array of locations; each location holds as a value one half of a tile, or it can be `empty`, or it can be a `border`, meaning nothing can be placed there."
] ]
}, },
{ {
@@ -26,22 +26,18 @@
"empty = ' '\n", "empty = ' '\n",
"border = '--'\n", "border = '--'\n",
"\n", "\n",
"Tile = tuple\n",
"\n",
"class Board(list):\n", "class Board(list):\n",
" \"A board is a 2d array of values.\"\n", " \"A board is a 2d array of values.\"\n",
" \n", " \n",
" def __init__(self, n): \n", " def __init__(self, width=16, height=24): \n",
" \"Initialize an [n × n] array of `empty`, surrounded by `off`\"\n", " \"Initialize a [width × height] array of `empty`, surrounded by `border`\"\n",
" top = bot = [border] * (n + 2)\n", " def rows (r, val): \n",
" line = [border] + ([empty] * n) + [border]\n", " return [[border] + width * [val] + [border] for _ in range(r)]\n",
" self.extend([top] + [list(line) for _ in range(n)] + [bot])\n", " self[:] = rows(1, border) + rows(height, empty) + rows(1, border)\n",
" \n", " \n",
" def get(self, loc): return self[loc[1]][loc[0]]\n", " def get(self, loc): return self[loc[1]][loc[0]]\n",
" \n", " \n",
" def put(self, loc, value): self[loc[1]][loc[0]] = value\n", " def put(self, loc, value): self[loc[1]][loc[0]] = value"
" \n",
"tiles1 = [('Bo', 'Ja'), ('Ja', 'Po'), ('Ja', 'Ry'), ('Ry', 'Ke'), ('Gr', 'Ke'), ('Gr', 'Ho')]"
] ]
}, },
{ {
@@ -54,12 +50,11 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"[['--', '--', '--', '--', '--', '--'],\n", "[['--', '--', '--', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', '--'],\n", " ['--', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', '--'],\n", " ['--', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', '--'],\n", " ['--', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', '--'],\n", " ['--', '--', '--', '--']]"
" ['--', '--', '--', '--', '--', '--']]"
] ]
}, },
"execution_count": 2, "execution_count": 2,
@@ -68,23 +63,22 @@
} }
], ],
"source": [ "source": [
"Board(4)" "Board(2, 3)"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Now I need a strategy to fill the board with tiles, legally. I will randomly place dominoes one at a time, and I will *not* consider removing a tile from the board and backtracking; this is a greedy search. Some more concepts:\n", "Now I need a strategy to fill the board with tiles. I will randomly place dominoes one at a time, and I will *not* consider removing a tile from the board and backtracking. Some more concepts:\n",
"\n", "\n",
"- **`frontier`**: I'll maintain a *frontier*, a set of locations that are adjacent to tiles on the board, and thus are candidates for placing new tiles.\n", "- **`frontier`**: I'll maintain a *frontier*, a set of locations that are adjacent to tiles on the board, and thus are candidates for placing new tiles.\n",
"- **`dominoes(tiles)`**: places a random tile for the first tile, then repeatedly calls `place1` to legally place an additional tile, stopping when either there is no `frontier` left (meaning no place to put a tile) or no `tiles` left to place.\n", "- **`dominoes(tiles)`**: places a random tile for the first tile, then repeatedly calls `place1` to legally place an additional tile, stopping when either there is no `frontier` left (meaning no place to put a tile) or no `tiles` left to place.\n",
"- **`place1(tiles, board, frontier)`**: find a location in the frontier, such that some tile can legally put one of its halves there, and the other half on an adjacent location; when found, `put` the tile there, and remove it from `tiles`.\n", "- **`try_one(tiles, board, frontier)`**: take a location in the frontier, and try to find some tile that can legally put one of its halves there, and the other half on an adjacent location; when found, `put` the tile there, and remove it from `tiles`.\n",
"- **`legal(value, loc, board)`**: a value can be placed if the location is empty, and the value matches all the neighboring location values.\n", "- **`legal(value, loc, board)`**: a value can be placed if the location is empty, and the value matches all the neighboring location values.\n",
"- **`legal_match(value, nbrval)`**: a value matches if the neighbor is empty or a border, or is equal to the value.\n",
"- **`neighbors(loc)`**: returns the four neighbors of a location.\n", "- **`neighbors(loc)`**: returns the four neighbors of a location.\n",
"- **`put(board, loc0, loc1, tile, frontier)`**: places a tile on the board, it accomplishes this by making two calls to the board's `put` method, one for each half of the tile. The `put` function also updates the `frontier` to say that the just-covered locations are no longer in the frontier, but the empty neighbors of the tile are.\n", "- **`put(board, loc0, loc1, tile, frontier)`**: places a tile on the board, it accomplishes this by making two calls to the board's `put` method, one for each half of the tile. The `put` function also updates the `frontier` to say that the just-covered locations are no longer in the frontier, but the empty neighbors of the tile are.\n",
"- **`shuffle(sequence)`**: used to randomize lists; calls `random.shuffle` and returns the result." "- **`shuffle(items)`**: used to randomize lists; calls `random.shuffle` and returns the result."
] ]
}, },
{ {
@@ -93,65 +87,43 @@
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
"outputs": [ "outputs": [],
{
"data": {
"text/plain": [
"[['--', '--', '--', '--', '--', '--', '--'],\n",
" ['--', 'Ho', ' ', 'Po', 'Ja', ' ', '--'],\n",
" ['--', 'Gr', 'Gr', ' ', 'Ja', 'Bo', '--'],\n",
" ['--', ' ', 'Ke', ' ', 'Ja', ' ', '--'],\n",
" ['--', ' ', 'Ke', 'Ry', 'Ry', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', ' ', '--'],\n",
" ['--', '--', '--', '--', '--', '--', '--']]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"import random\n", "import random\n",
"\n", "\n",
"def dominoes(tiles, n=24):\n", "def dominoes(tiles, width=16, height=24):\n",
" \"Place as many tiles on board as possible, legally and randomly.\"\n", " \"Place as many tiles on board as possible, legally and randomly.\"\n",
" tiles = shuffle(list(tiles))\n", " tiles = shuffle(list(tiles))\n",
" board = Board(n)\n", " board = Board(width, height)\n",
" frontier = set()\n", " frontier = set()\n",
" m = n // 2\n", " m = min(width, height) // 2\n",
" put(board, (m, m), (m, m + 1), tiles.pop(), frontier) # Place first tile\n", " put(board, (m, m), (m, m + 1), tiles.pop(), frontier) # Place first tile\n",
" while tiles and frontier:\n", " while tiles and frontier:\n",
" place1(tiles, board, frontier)\n", " try_one(tiles, board, frontier)\n",
" return board\n", " return board\n",
" \n", " \n",
"def place1(tiles, board, frontier):\n", "def try_one(tiles, board, frontier):\n",
" \"Randomly place one tile on board, on some frontier location and an adjacent square.\"\n", " \"Pop a frontier location, and try to place a random tile on that location and an adjacent square.\"\n",
" for loc0 in shuffle(frontier):\n", " loc0 = frontier.pop()\n",
" frontier.discard(loc0)\n", " for tile in shuffle(tiles):\n",
" for tile in shuffle(tiles):\n", " for (v, w) in [tile, tile[::-1]]:\n",
" for (v, w) in [tile, tile[::-1]]:\n", " if legal(v, loc0, board):\n",
" if legal(v, loc0, board):\n", " for loc1 in shuffle(neighbors(loc0)):\n",
" for loc1 in shuffle(neighbors(loc0)):\n", " if legal(w, loc1, board):\n",
" if legal(w, loc1, board):\n", " put(board, loc0, loc1, [v, w], frontier)\n",
" put(board, loc0, loc1, [v, w], frontier)\n", " tiles.remove(tile)\n",
" tiles.remove(tile)\n", " return \n",
" return \n",
" \n", " \n",
"def legal(value, loc, board):\n", "def legal(value, loc, board):\n",
" \"Is it legal to place this value on this location on board?\"\n", " \"Is it legal to place this value on this location on board?\"\n",
" return (board.get(loc) is empty and\n", " return (board.get(loc) is empty and\n",
" all(legal_match(value, board.get(nbr)) \n", " all(board.get(nbr) in (empty, border, value)\n",
" for nbr in neighbors(loc)))\n", " for nbr in neighbors(loc)))\n",
"\n",
"def legal_match(value, nbrval):\n",
" \"Does this value match the neighbor's value?\"\n",
" return nbrval in (empty, border, value)\n",
" \n", " \n",
"def neighbors(loc):\n", "def neighbors(loc):\n",
" \"Neighbors of this location.\"\n", " \"Neighbors of this location.\"\n",
" x, y = loc\n", " x, y = loc\n",
" return ((x, y+1), (x, y-1), (x+1, y), (x-1, y)) \n", " return [(x, y+1), (x, y-1), (x+1, y), (x-1, y)]\n",
"\n", "\n",
"def put(board, loc0, loc1, tile, frontier): \n", "def put(board, loc0, loc1, tile, frontier): \n",
" \"Place the tile across the two locations, and update frontier.\"\n", " \"Place the tile across the two locations, and update frontier.\"\n",
@@ -161,25 +133,7 @@
" frontier |= {loc for loc in neighbors(loc0) + neighbors(loc1)\n", " frontier |= {loc for loc in neighbors(loc0) + neighbors(loc1)\n",
" if board.get(loc) is empty}\n", " if board.get(loc) is empty}\n",
" \n", " \n",
"def shuffle(seq):\n", "def shuffle(items): random.shuffle(items); return items"
" \"Return seq as a shuffled list.\"\n",
" if not isinstance(seq, list): seq = list(seq)\n",
" random.shuffle(seq)\n",
" return seq\n",
" \n",
"dominoes(tiles1, 5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Pretty Output\n",
"\n",
"There are two problems with this output. One, it is ugly. Two, I can't easily tell where each domino is: when three names come together, which of the outside ones go with the middle one? To fix those two problems I will:\n",
"\n",
"- Use `matplotlib` to plot a less-ugly display, by defining `plot_board(board)`.\n",
"- Modify the `Board` class and the `put` function so that the board maintains a list of `boxes` that surround each tile once it is placed on the board."
] ]
}, },
{ {
@@ -188,13 +142,57 @@
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
"outputs": [
{
"data": {
"text/plain": [
"[['--', '--', '--', '--', '--', '--', '--', '--'],\n",
" ['--', 'Ry', 'Ke', ' ', ' ', 'Ja', 'Ry', '--'],\n",
" ['--', ' ', 'Ke', 'Gr', ' ', 'Ja', ' ', '--'],\n",
" ['--', ' ', ' ', 'Gr', ' ', 'Ke', ' ', '--'],\n",
" ['--', ' ', ' ', 'Jo', 'Jo', 'Ke', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', ' ', ' ', '--'],\n",
" ['--', '--', '--', '--', '--', '--', '--', '--']]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tiles1 = [('Bo', 'Ja'), ('Ja', 'Po'), ('Ja', 'Ry'), ('Ry', 'Ke'), \n",
" ('Gr', 'Ke'), ('Gr', 'Jo'), ('Ja', 'Ke'), ('Ke', 'Jo')]\n",
"\n",
"dominoes(tiles1, 6, 6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Pretty Output\n",
"\n",
"There are two problems with this output. One, it is ugly. Two, I can't easily tell where each domino is: when three names come together, which of the outside names goes with the middle name? To fix those two problems I will:\n",
"\n",
"- Use `matplotlib` to plot a less-ugly display, by defining `plot_board(board)`.\n",
"- Modify the `Board` class and the `put` function so that the board maintains a list of `boxes` that surround each two-location rectangle that a tile occupies."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [], "outputs": [],
"source": [ "source": [
"%matplotlib inline\n", "%matplotlib inline\n",
"import matplotlib.pyplot as plt\n", "import matplotlib.pyplot as plt\n",
"\n", "\n",
"def plot_board(board, figsize=6):\n", "def plot_board(board, figsize=(16, 24)):\n",
" plt.figure(figsize=(figsize, figsize))\n", " plt.figure(figsize=figsize)\n",
" plt.axis('off') \n", " plt.axis('off') \n",
" plt.axis('equal')\n", " plt.axis('equal')\n",
" for (x0, y0, x1, y1) in board.boxes:\n", " for (x0, y0, x1, y1) in board.boxes:\n",
@@ -209,11 +207,11 @@
"class Board(list):\n", "class Board(list):\n",
" \"A board is a 2d array of values.\"\n", " \"A board is a 2d array of values.\"\n",
" \n", " \n",
" def __init__(self, n): \n", " def __init__(self, width=20, height=36): \n",
" \"Initialize an [n × n] array of `empty`, surrounded by `off`\"\n", " \"Initialize a [width × height] array of `empty`, surrounded by `border`\"\n",
" top = bot = [border] * (n + 2)\n", " def rows (r, val): \n",
" line = [border] + ([empty] * n) + [border]\n", " return [[border] + width * [val] + [border] for _ in range(r)]\n",
" self.extend([top] + [list(line) for _ in range(n)] + [bot])\n", " self[:] = rows(1, border) + rows(height, empty) + rows(1, border)\n",
" self.boxes = []\n", " self.boxes = []\n",
" \n", " \n",
" def get(self, loc): return self[loc[1]][loc[0]]\n", " def get(self, loc): return self[loc[1]][loc[0]]\n",
@@ -234,16 +232,16 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 6,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
"outputs": [ "outputs": [
{ {
"data": { "data": {
"image/png": "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\nRU5ErkJggg==\n", "image/png": "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\n",
"text/plain": [ "text/plain": [
"<matplotlib.figure.Figure at 0x10eb19b70>" "<matplotlib.figure.Figure at 0x10eeaca20>"
] ]
}, },
"metadata": {}, "metadata": {},
@@ -251,7 +249,7 @@
} }
], ],
"source": [ "source": [
"plot_board(dominoes(tiles1))" "plot_board(dominoes(tiles1, 6, 6), (6, 6))"
] ]
}, },
{ {
@@ -261,30 +259,19 @@
"# All the Names\n", "# All the Names\n",
"\n", "\n",
"Now let's try **all** the names from `xkcd 1970`, as reported \n", "Now let's try **all** the names from `xkcd 1970`, as reported \n",
"by [explainxkcd](http://www.explainxkcd.com/wiki/index.php/1970:_Name_Dominoes), with a few typo corrections:" "by [explainxkcd](http://www.explainxkcd.com/wiki/index.php/1970:_Name_Dominoes), with a few typos corrected:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 7,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
"outputs": [ "outputs": [],
{
"data": {
"text/plain": [
"270"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"def split_names(text):\n", "def split_names(text):\n",
" \"For each line of text, create a Tile of ('First Name(s)', 'Lastname').\"\n", " \"For each line of text, create a tile of ('First Name(s)', 'Lastname').\"\n",
" return [name.rpartition(' ')[0::2]\n", " return [name.rpartition(' ')[0::2]\n",
" for name in text.strip().splitlines()]\n", " for name in text.strip().splitlines()]\n",
" \n", " \n",
@@ -324,7 +311,7 @@
"Francis Bacon\n", "Francis Bacon\n",
"Francis Drake\n", "Francis Drake\n",
"Lyndon Johnson\n", "Lyndon Johnson\n",
"Oscar the Grouch\n", "Oscar The Grouch\n",
"Oscar Isaac\n", "Oscar Isaac\n",
"Isaac Hayes\n", "Isaac Hayes\n",
"Isaac Newton\n", "Isaac Newton\n",
@@ -529,7 +516,7 @@
"Cameron Crow\n", "Cameron Crow\n",
"Long John Silver\n", "Long John Silver\n",
"Olivia Newton John\n", "Olivia Newton John\n",
"Huey long\n", "Huey Long\n",
"John Edwards\n", "John Edwards\n",
"Candy Crowley\n", "Candy Crowley\n",
"Aleister Crowley\n", "Aleister Crowley\n",
@@ -559,78 +546,7 @@
"John Oliver\n", "John Oliver\n",
"Ryan Reynolds\n", "Ryan Reynolds\n",
"Alastair Reynolds\n", "Alastair Reynolds\n",
"\"\"\")\n", "\"\"\")"
"\n",
"len(tiles1970)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Approximate and Partial Matches\n",
"\n",
"The halves of two tiles match if they are an exact match, like \"Amy\" and \"Amy\", but also if they are an **approximate match**, like \"Amy\" and \"Aimee\". You can manually allow for this with:\n",
"\n",
" synsets = synonyms(\"Amy=Aimee, ...\")\n",
"\n",
"Another issue is a **partial match**: \"John Smith\" is allowed to match \"Long John Silver\"\n",
"because the first name \"John\" is a piece of the first name \"Long John\". allowed to match. If you pass in a list of tiles as a second argument to `synsets`, then all the pieces of a first name (if it has more than one piece) will be (individually) made synonyms of the whole first name.\n",
"\n",
"I also update `legal_match` to consult the `synsets` global variable for an approximate or partial match, and to handle a special case: a single names like \"Prince\" gets represented as the tile `('', 'Prince')`, but we don't want the empty first name of this tile to match the empty first name of \"Drake\", so we disallow an empty-to-empty match."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import collections\n",
"\n",
"Synset = set\n",
"\n",
"def synonyms(text, tiles=()): \n",
" \"synonyms('a=A, b=B') => dict(a={'a', 'A'}, A={'a', 'A'}, b={'b', 'B'}, B={'b', 'B'}\"\n",
" synsets = collections.defaultdict(set)\n",
" # Process `text`\n",
" for text1 in text.split(','):\n",
" synset = Synset(text1.strip().split('='))\n",
" for s in synset:\n",
" synsets[s] |= synset\n",
" # Process `tiles`\n",
" for (first, last) in tiles:\n",
" if ' ' in first:\n",
" for piece in first.split():\n",
" synsets[piece].add(first)\n",
" synsets[first].add(piece)\n",
" return synsets\n",
"\n",
"synsets = synonyms(\"\"\"\n",
" Amy=Aimee, Cook=Cooke=Cookie=Cokie, Alastair=Alistair, Columbo=Columbus, \n",
" Safire=Sapphire=Garnet, Garnet=Ruby, Charlie=Charles, Sean=Shawn, \n",
" Jimmy=James, Man=Mann, Jack=John, Tom=Tommy, Will=William=Williams, \n",
" Robert=Roberts=Bob, Cam=Cameron, Oliver=Olivia, Edward=Edwards, \n",
" Rich=Richard, Oscar=Oscar, Frank=Frank, Chris=Christopher=Topher,\n",
" Fat=Fats=Fatty\n",
" \"\"\", tiles1970)\n",
"\n",
"def legal_match(value, nbrval):\n",
" \"Does this value match the neighbor's value?\"\n",
" if value == '' == nbrval: return False\n",
" return (nbrval == value or nbrval is empty or nbrval is border or\n",
" nbrval in synsets[value])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Random Restart\n",
"\n",
"With many tiles, the program can get stuck before it has placed enough tiles. I could modify the program to *back up* in this case, but it will be easier to just *give up* and start again with an empty board. I can repeat this multiple times, and take the board on which the most tiles were placed. The function `best_dominoes` does this, and optionally plots the result. (I could fit more tiles on a larger square (with smaller tiles), but then the text would overflow the tiles even more than now.)"
] ]
}, },
{ {
@@ -641,17 +557,156 @@
}, },
"outputs": [ "outputs": [
{ {
"name": "stdout", "data": {
"output_type": "stream", "text/plain": [
"text": [ "270"
"110 / 270 tiles placed\n" ]
] },
}, "execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(tiles1970)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [
{ {
"data": { "data": {
"image/png": "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 truncated
"text/plain": [ "text/plain": [
"<matplotlib.figure.Figure at 0x1158ec7b8>" "[('Bill', 'Clinton'),\n",
" ('Kristen', 'Stewart'),\n",
" ('James Earl', 'Ray'),\n",
" ('Barry', 'White'),\n",
" ('Kevin', 'Costner'),\n",
" ('Grace', 'Lee'),\n",
" ('Carly', 'Hughes'),\n",
" ('Ma', 'Bell'),\n",
" ('Chris', 'Cooper')]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"random.sample(tiles1970, 9)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Approximate and Partial Matches\n",
"\n",
"The halves of two tiles match if they are an exact match, like \"Amy\" and \"Amy\", but also if they are an **approximate match**, like \"Amy\" and \"Aimee\". You can manually define allowable approximate matches like this:\n",
"\n",
" synsets = synonyms(\"Amy=Aimee, ...\")\n",
"\n",
"The idea is that this creates *synonym sets*, or `synsets`.\n",
"Another issue is a **partial match**: \"John Smith\" is allowed to match \"Long John Silver\"\n",
"because the first name \"John\" is a piece of the first name \"Long John\". In the 1970 comic, this was drawn with one domino touching the middle of another; I won't do that, but I will allow, for first names that have multiple parts, for another tile to match any one of the parts. The second argument to `synsets`, is a list of tiles; `synonyms` will go through these and add synset entries for paets of first names. \n",
"\n",
"I also update `legal_match` to consult the `synsets` global variable for an approximate or partial match, and to handle a special case: a single names like \"Prince\" gets represented as the tile `('', 'Prince')`, but we don't want the empty first name of this tile to match the empty first name of \"Drake\", so we disallow an empty-to-empty match."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import collections\n",
"\n",
"def synonyms(text, tiles=()): \n",
" \"synonyms('a=A, b=B') => dict(a={'a', 'A'}, A={'a', 'A'}, b={'b', 'B'}, B={'b', 'B'}\"\n",
" synsets = collections.defaultdict(set)\n",
" # Process `text`\n",
" for text1 in text.split(','):\n",
" synset = set(text1.strip().split('='))\n",
" for s in synset:\n",
" synsets[s] |= synset\n",
" # Process `tiles`\n",
" for (first, last) in tiles:\n",
" if ' ' in first:\n",
" for piece in first.split():\n",
" synsets[piece].add(first)\n",
" synsets[first].add(piece)\n",
" return synsets\n",
"\n",
"def legal(value, loc, board):\n",
" \"Is it legal to place this value on this location on board?\"\n",
" return (board.get(loc) is empty and\n",
" all(legal_match(value, board.get(nbr)) for nbr in neighbors(loc)))\n",
"\n",
"def legal_match(value, nbrval):\n",
" \"Does this value match the neighbor's value?\"\n",
" return (nbrval in (empty, border) or \n",
" nbrval == value != '' or\n",
" nbrval in synsets[value])"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"synsets = synonyms(\"\"\"\n",
" Amy=Aimee, Cook=Cooke=Cookie=Cokie, Alastair=Alistair, Columbo=Columbus, \n",
" Safire=Sapphire=Garnet, Garnet=Ruby, Charlie=Charles, Sean=Shawn, \n",
" Jimmy=James, Man=Mann, Jack=John, Tom=Tommy, Will=William=Williams, \n",
" Robert=Roberts=Bob, Cam=Cameron, Oliver=Olivia, Edward=Edwards, \n",
" Rich=Richard, Chris=Christopher=Topher, Fat=Fats=Fatty\n",
" \"\"\", tiles1970)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Random Restart\n",
"\n",
"The program sometimes gets stuck before it has placed enough tiles. I could modify the program to *back up* in this case, but it will be easier to just *give up* and restart with an empty board. I can restart multiple times, and take the board on which the most tiles were placed. The function `restart_dominoes` does this."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def restart_dominoes(tiles, width=16, height=24, repeat=50):\n",
" return max((dominoes(tiles, width, height) for _ in range(repeat)), \n",
" key=lambda board: len(board.boxes))"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA6IAAAU7CAYAAAAzQRg2AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xu4JFV97//3Ry5GMwEEgaAGx8tzEvUIg4gabwwXDYi3\n4AXlomMkGDXGnGMMQQwSNfJTYzRRQdFEdBCDCXrEwEGEYbgZRYxjNOSgEoEEEDhJUPFyQPj+/qi1\nmWaz91zYvav3rv1+PU8/u2ZVdfd3dVWtWt+1qntSVUiSJEmS1Jf7TDoASZIkSdLSYiIqSZIkSeqV\niagkSZIkqVcmopIkSZKkXpmISpIkSZJ6ZSIqSZIkSeqViagkSZIkqVcmopIkSZKkXpmISpIkSZJ6\nZSIqSZIkSeqViagkSZIkqVcmopIkSZKkXpmISpIkSZJ6ZSIqSZIkSeqViagkSZIkqVcmopIkSZKk\nXpmISpIkSZJ6ZSIqSZIkSeqViagkSZIkqVcmopIkSZKkXpmISpIkSZJ6ZSIqSZIkSeqViagkSZIk\nqVcmopIkSZKkXpmISpIkSZJ6ZSIqSZIkSeqViagkSZIkqVcmopIkSZKkXpmISpIkSZJ6ZSIqSZIk\nSeqViagkSZIkqVcmopIkSZKkXpmISpIkSZJ6ZSIqSZIkSeqViagkSZIkqVcmopIkSZKkXpmISpIk\nSZJ6ZSIqSZIkSeqViagkSZIkqVcmopIkSZKkXpmISpIkSZJ6ZSIqSZIkSeqViagkSZIkqVcmopIk\nSZKkXpmISpIkSZJ6ZSIqSZIkSeqViagkSZIkqVcmopIkSZKkXpmISpIkSZJ6ZSIqSZIkSeqViagk\nSZIkqVcmopIkSZKkXm056QCkDUlyFHDopOPQPZxWVSdPOoi58vhasHZpf2+YaBQa5T5ZuAbRHkta\nepwR1UJ3KLBi0kHoblYwnOTN42thekR7aOFwnyxMQ2qPJS0xzohqMVhXVSsnHYQ6SdZOOoYx8/ha\nYJLcAuB+WTjcJwvTANtjSUuIM6KSJEmSpF6ZiEqSJEmSemUiKkmSJEnqlYmoJEmSJKlXJqKSJEmS\npF6ZiEqSJEmSemUiKkmSJEnqlYmoJEmSJKlXJqKSJEmSpF6ZiEqSJEmSemUiKkmSJEnqlYmoJEmS\nJKlXJqKSJEmSpF6ZiEqSJEmSemUiKkmSJEnqlYmoJEmSJKlXJqKSJEmSpF6ZiEqSJEmSemUiKkmS\n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\nIipJkiRJ6pWJqCRJkiSpVyaikiRJkqRemYhKkiRJknplIipJkiRJ6pWJqCRJkiSpVyaikiRJkqRe\nbTnpABaSJEcBh046Dt3N3gBJ1k44jnHYBdh50kGMwTLg1oHsE4+vhWlbGMx+GQr3ycK0Alg36SDm\nyv7XgrVL+3vDRKPQdKdV1cmTDmIcnBG9u0PpGnVpPuxMl8QtdrcCN046CN3DUI4vSZtuHXDapIMY\nA/tfC9Mj2kMLxwoGNGjjjOg9rauqlZMOQp2p0fch7JMh1WUohrRPrIukRc7+1wKT5BawLV5IhnZX\nijOikiRJkqRemYhKkiRJknplIipJkiRJ6pWJqCRJkiSpVyaikiRJkqRemYhKkiRJknplIipJkiRJ\n6pWJqCRJkiSpVyaikiRJkqRemYhKkiRJknplIipJkiRJ6pWJqCRJkiSpVyaikiRJkqRemYhKkiRJ\nknplIipJkiRJ6pWJqCRJkiSpVyaikiRJkqRemYhKkiRJknplIipJkiRJ6pWJqCRJkiSpVyaikiRJ\nkqRemYhKkiRJknplIipJkiRJ6pWJqCRJkiSpVyaikiRJkqRemYhKkiRJknplIipJkiRJ6pWJqCRJ\nkiSpVyaiY5JkvyRrk1yU5LNJdtjE552S5JHzHd+mSvKdJC9py2uTbJlkVZI9Z9h2uyQHb+T13j9f\nsS5VSVYm+XmSndq/90pSSZZPNrKNS/LxJI9uyycmeWtb3jfJfyQ5ctr2ByQ5aFOONW26dgxdk+SC\nJF+crb2aagM287WXJ9l3PJFK0uySbJPkrNZWfTnJU5McO+m45mpz+5StTX97X/FtjsVYl+kxbE5f\nfSHEv5hsVgdDM0uyI3Ac8Oyq+lGS/wZsPeGwNluS3YFLgOcAfzNVXlWnzPKU7YCDgc/M9ppV9bpp\n73GfqrpzzsFqHfA84CPAbwKXTzacTfZVYC/gCmAb4P6tfC/gXdM3rqpzoEtu2Mixps22uqrenORw\n4KXAB+b6gknuAywH9gXWzPX1JGkjXgZ8pqr+qg2a3a+qLpl0UHOxuX3K1u4uSEOqi+aHO3w8nkXX\nqfsRQFV9G9gpyaVthO5w6BK96WVTkuyT5JNJtkry7DZy9KUkB/RYj4OBE4H7J7nvSGzHJ9k/yZOT\nfKXNorwSOAp4Rhvp2jHJp5NcmOTcJNu0517S/q5N8i7gEz3WZ8jWAPu15ccA/wzcLLine truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x1156f4e80>"
] ]
}, },
"metadata": {}, "metadata": {},
@@ -659,16 +714,7 @@
} }
], ],
"source": [ "source": [
"def best_dominoes(tiles, n=20, figsize=16, plot=True, repeat=30):\n", "plot_board(restart_dominoes(tiles1970))"
" board = max((dominoes(tiles, n) for _ in range(repeat)), \n",
" key=lambda board: len(board.boxes))\n",
" if plot:\n",
" print(len(board.boxes), '/', len(tiles), 'tiles placed')\n",
" plot_board(board, figsize)\n",
" else:\n",
" return board\n",
"\n",
"best_dominoes(tiles1970)"
] ]
} }
], ],