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",
"metadata": {},
"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",
"# `xkcd` Name Dominoes\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",
"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",
"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",
"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",
"- **`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",
"border = '--'\n",
"\n",
"Tile = tuple\n",
"\n",
"class Board(list):\n",
" \"A board is a 2d array of values.\"\n",
" \n",
" def __init__(self, n): \n",
" \"Initialize an [n × n] array of `empty`, surrounded by `off`\"\n",
" top = bot = [border] * (n + 2)\n",
" line = [border] + ([empty] * n) + [border]\n",
" self.extend([top] + [list(line) for _ in range(n)] + [bot])\n",
" def __init__(self, width=16, height=24): \n",
" \"Initialize a [width × height] array of `empty`, surrounded by `border`\"\n",
" def rows (r, val): \n",
" return [[border] + width * [val] + [border] for _ in range(r)]\n",
" self[:] = rows(1, border) + rows(height, empty) + rows(1, border)\n",
" \n",
" def get(self, loc): return self[loc[1]][loc[0]]\n",
" \n",
" def put(self, loc, value): self[loc[1]][loc[0]] = value\n",
" \n",
"tiles1 = [('Bo', 'Ja'), ('Ja', 'Po'), ('Ja', 'Ry'), ('Ry', 'Ke'), ('Gr', 'Ke'), ('Gr', 'Ho')]"
" def put(self, loc, value): self[loc[1]][loc[0]] = value"
]
},
{
@@ -54,12 +50,11 @@
{
"data": {
"text/plain": [
"[['--', '--', '--', '--', '--', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', ' ', ' ', '--'],\n",
" ['--', '--', '--', '--', '--', '--']]"
"[['--', '--', '--', '--'],\n",
" ['--', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', '--'],\n",
" ['--', '--', '--', '--']]"
]
},
"execution_count": 2,
@@ -68,23 +63,22 @@
}
],
"source": [
"Board(4)"
"Board(2, 3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"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",
"- **`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",
"- **`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_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",
"- **`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": {
"collapsed": false
},
"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"
}
],
"outputs": [],
"source": [
"import random\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",
" tiles = shuffle(list(tiles))\n",
" board = Board(n)\n",
" board = Board(width, height)\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",
" while tiles and frontier:\n",
" place1(tiles, board, frontier)\n",
" try_one(tiles, board, frontier)\n",
" return board\n",
" \n",
"def place1(tiles, board, frontier):\n",
" \"Randomly place one tile on board, on some frontier location and an adjacent square.\"\n",
" for loc0 in shuffle(frontier):\n",
" frontier.discard(loc0)\n",
" for tile in shuffle(tiles):\n",
" for (v, w) in [tile, tile[::-1]]:\n",
" if legal(v, loc0, board):\n",
" for loc1 in shuffle(neighbors(loc0)):\n",
" if legal(w, loc1, board):\n",
" put(board, loc0, loc1, [v, w], frontier)\n",
" tiles.remove(tile)\n",
" return \n",
"def try_one(tiles, board, frontier):\n",
" \"Pop a frontier location, and try to place a random tile on that location and an adjacent square.\"\n",
" loc0 = frontier.pop()\n",
" for tile in shuffle(tiles):\n",
" for (v, w) in [tile, tile[::-1]]:\n",
" if legal(v, loc0, board):\n",
" for loc1 in shuffle(neighbors(loc0)):\n",
" if legal(w, loc1, board):\n",
" put(board, loc0, loc1, [v, w], frontier)\n",
" tiles.remove(tile)\n",
" return \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)) \n",
" all(board.get(nbr) in (empty, border, value)\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",
"def neighbors(loc):\n",
" \"Neighbors of this location.\"\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",
"def put(board, loc0, loc1, tile, 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",
" if board.get(loc) is empty}\n",
" \n",
"def shuffle(seq):\n",
" \"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."
"def shuffle(items): random.shuffle(items); return items"
]
},
{
@@ -188,13 +142,57 @@
"metadata": {
"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": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"\n",
"def plot_board(board, figsize=6):\n",
" plt.figure(figsize=(figsize, figsize))\n",
"def plot_board(board, figsize=(16, 24)):\n",
" plt.figure(figsize=figsize)\n",
" plt.axis('off') \n",
" plt.axis('equal')\n",
" for (x0, y0, x1, y1) in board.boxes:\n",
@@ -209,11 +207,11 @@
"class Board(list):\n",
" \"A board is a 2d array of values.\"\n",
" \n",
" def __init__(self, n): \n",
" \"Initialize an [n × n] array of `empty`, surrounded by `off`\"\n",
" top = bot = [border] * (n + 2)\n",
" line = [border] + ([empty] * n) + [border]\n",
" self.extend([top] + [list(line) for _ in range(n)] + [bot])\n",
" def __init__(self, width=20, height=36): \n",
" \"Initialize a [width × height] array of `empty`, surrounded by `border`\"\n",
" def rows (r, val): \n",
" return [[border] + width * [val] + [border] for _ in range(r)]\n",
" self[:] = rows(1, border) + rows(height, empty) + rows(1, border)\n",
" self.boxes = []\n",
" \n",
" def get(self, loc): return self[loc[1]][loc[0]]\n",
@@ -234,16 +232,16 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "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\nRU5ErkJggg==\n",
"image/png": "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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x10eb19b70>"
"<matplotlib.figure.Figure at 0x10eeaca20>"
]
},
"metadata": {},
@@ -251,7 +249,7 @@
}
],
"source": [
"plot_board(dominoes(tiles1))"
"plot_board(dominoes(tiles1, 6, 6), (6, 6))"
]
},
{
@@ -261,30 +259,19 @@
"# All the Names\n",
"\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",
"execution_count": 6,
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"270"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"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",
" for name in text.strip().splitlines()]\n",
" \n",
@@ -324,7 +311,7 @@
"Francis Bacon\n",
"Francis Drake\n",
"Lyndon Johnson\n",
"Oscar the Grouch\n",
"Oscar The Grouch\n",
"Oscar Isaac\n",
"Isaac Hayes\n",
"Isaac Newton\n",
@@ -529,7 +516,7 @@
"Cameron Crow\n",
"Long John Silver\n",
"Olivia Newton John\n",
"Huey long\n",
"Huey Long\n",
"John Edwards\n",
"Candy Crowley\n",
"Aleister Crowley\n",
@@ -559,78 +546,7 @@
"John Oliver\n",
"Ryan 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": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"110 / 270 tiles placed\n"
]
},
"data": {
"text/plain": [
"270"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(tiles1970)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"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": {},
@@ -659,16 +714,7 @@
}
],
"source": [
"def best_dominoes(tiles, n=20, figsize=16, plot=True, repeat=30):\n",
" 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)"
"plot_board(restart_dominoes(tiles1970))"
]
}
],