Add subdirectories

Add /ipynb/ and /py/ subdirectories to keep the home page neater.
This commit is contained in:
Peter Norvig committed 2017-10-23 10:32:23 -07:00
1 parent ff96ec21ba
commit 88819c4cd0
61 files changed
+48 -46

No files matched your search

File diff suppressed because it is too large. Load diff
+1966
View File
File diff suppressed because it is too large. Load diff
+1070
View File
File diff suppressed because it is too large. Load diff
+184
View File
@@ -0,0 +1,184 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Bike Speed versus Grade\n",
"\n",
"Like most people, I bike slower when I'm going up a steep hill than on a flat road. But how much slower?\n",
"To answer that, I downloaded data on my past rides \n",
"from [Strava](https://www.strava.com/athletes/575579) and manipulated the `data` to create two lists: \n",
"\n",
"- `X`: the *grade* of each ride, in feet of ascent per mile. \n",
"- `Y`: the *speed* of each (corresponding) ride, in miles per hour.\n",
"\n",
"I omit rides shorter than 30 miles, because on some short rides I was sprinting at an unsustainable speed, and on others I was slowed by city traffic. Here is the code to collect and plot the X:Y data:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x1028a2be0>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import csv\n",
"\n",
"def hours(sec): return float(sec) / 60 / 60\n",
"def feet(meters): return float(meters) * 100 / 2.54 / 12\n",
"def miles(meters): return feet(meters) / 5280\n",
"\n",
"# Read data file with three fields: distance (m), climb (m), elapsed time (sec)\n",
"X, Y = [], []\n",
"for (dist, climb, time) in csv.reader(open('dist-climb-time.csv')):\n",
" if miles(dist) > 30:\n",
" X.append(feet(climb) / miles(dist))\n",
" Y.append(miles(dist) / hours(time))\n",
"\n",
"plt.plot(X, Y, 'o');"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As expected, the speeds get slower as the grade gets steeper. The data has a lot of variance, and I can say that it \n",
"looks more like a curve than a straight line, so I'll fit a cubic (degree two) polynomial to the data,\n",
"and make the plot prettier:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAzgAAAIcCAYAAAA6xgsWAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XtgU1W6NvAnTe2FClpa2lLaBhQKXgbGcdTx8zoKM+KI\nRxG0hUodaFGwIlULKJQClTsKQgHl4ghSQfGgM+qo53gbUUaPjgg4jlBEE1oKpRcupk1D03x/lISm\n2Ul30p19y/P7S9Mka+29krDfvdb7LsOJEyecICIiIiIi0oEIpTtAREREREQkFQY4RERERESkGwxw\niIiIiIhINxjgEBERERGRbjDAISIiIiIi3WCAQ0REREREusEAh4iIiIiIdEPxAGfXrl3Izs7GpZde\nivj4eGzdutXj71arFUVFRbjsssvQu3dvXHXVVVizZo1CvSUiIiIiIjWLVLoDVqsVl112GbKzszFp\n0iSvvz/11FP49NNPsW7dOmRkZGDXrl2YMmUKEhMTce+99yrQYyIiIiIiUivFZ3CGDRuGWbNm4c47\n74TBYPD6+1dffYX77rsP1113HdLT03Hffffht7/9Lb7++msFektERERERGqmeIDTmd/97nd47733\nUFVVBQD48ssv8d1332HYsGEK94yIiIiIiNRG8SVqnVm8eDGmTp2Kyy+/HJGRkTAYDFiyZAkDHCIi\nIiIi8qL6AOf555/HV199hVdffRVpaWnYtWsXZs2ahYyMDNxyyy1Kd4+IiIiIiFRE1QGOzWZDaWkp\nNm/ejD/84Q8AgEsvvRR79+7FqlWrGOAQEREREZEHVefgnDlzBmfOnEFEhGc3jUYjWltbFeoVERER\nERGpleIBjtVqxb59+7B37160traisrIS+/btQ2VlJbp3747rrrsOc+bMwWeffQaz2Yzy8nJs27YN\nI0aMULrrsNlsumtLzmOqqKiQrS09jpXcbelxvDhWXafXcyhXWxwrbbXF8dJGOwDHSmttSU3xAGf3\n7t248cYbcfPNN8Nms2HhwoW46aabsHDhQgDAX/7yF/zmN7/Bgw8+iGuvvRYrV67ErFmzkJeXp3DP\niYiIiIhIbRTPwbn++uvR0NDg8++9evVCWVmZjD0iIiIiIiKtUnwGh4iIiIiISCoMcIiIiIiISDcY\n4BARERERkW4wwCEiIiIiIt1ggENERERERLrBAIeIiIiIiHSDAQ4REREREekGAxwiIiIiItINBjhE\n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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x108dd82e8>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def poly(X, Y, n):\n",
" \"Best-fit degree-n polynomial for X, Y data.\"\n",
" C = np.polyfit(X, Y, n)[::-1] # Array of coefficients, reversed\n",
" return lambda x: sum(C[i] * x ** i for i in range(n + 1)) \n",
"\n",
"F = poly(X, Y, 2) # defines y = F(x); x in ft/mile, y in mph\n",
"\n",
"def show(X, Y, F=F): \n",
" plt.rcParams[\"figure.figsize\"] = (12, 8)\n",
" plt.style.use('fivethirtyeight')\n",
" plt.plot(X, Y, 'o')\n",
" X1 = list(range(int(max(X))))\n",
" plt.plot(X1, [F(x) for x in X1], 'o')\n",
" plt.ylabel('Speed (mph)')\n",
" plt.xlabel('Grade (feet/mile)')\n",
" plt.minorticks_on()\n",
" plt.grid(True, which='major')\n",
" plt.grid(True, which='minor', alpha=0.2)\n",
" \n",
"show(X, Y) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So, I average about 14 mph when the road is fairly flat, with a lot of variability from 12 to 16 mph, depending more on my level of effort than on the grade of the road. But from 60 ft/mile and up, speed falls off quickly at 1 mph for every 20 ft/mile, and by 140 ft/mile, I'm down around 8 mph. Note that 140 ft/mile is only 2.7% grade, but if you figure a typical route is 1/3 up, 1/3 down, and 1/3 flatish, then that's 8% grade on the up part.\n",
"\n",
"I can use the polynomial `F` to estimate the duration of a route:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def duration(dist, climb, F=F):\n",
" \"Given a distance in miles and total climb in feet, return estimated time in minutes.\"\n",
" return dist / F(climb / dist) * 60"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For example, to get to Pescadero from La Honda, I could take the flatter [coast route](https://www.google.com/maps/dir/La+Honda,+California/Pescadero,+California/@37.2905834,-122.3896683,12z/data=!4m19!4m18!1m10!1m1!1s0x808faed4dc6265bd:0x51a109d3306a7219!2m2!1d-122.274227!2d37.3190255!3m4!1m2!1d-122.4039496!2d37.3116594!3s0x808f062b7d7585e7:0x942480c22f110b74!1m5!1m1!1s0x808f00b4b613c4c1:0x43c609077878b77!2m2!1d-122.3830152!2d37.2551636!3e1), or the shorter [creek route](https://www.google.com/maps/dir/La+Honda,+California/Pescadero,+California/@37.2905834,-122.3896683,12z/data=!4m19!4m18!1m10!1m1!1s0x808faed4dc6265bd:0x51a109d3306a7219!2m2!1d-122.274227!2d37.3190255!3m4!1m2!1d-122.3658887!2d37.2538867!3s0x808f00acf265bd43:0xb7e2a0c9ee355c3a!1m5!1m1!1s0x808f00b4b613c4c1:0x43c609077878b77!2m2!1d-122.3830152!2d37.2551636!3e1):"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6.7314081227852185"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"coast = duration(15.7, 344)\n",
"creek = duration(13.5, 735)\n",
"\n",
"coast - creek"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This says the coast route takes 6.7 minutes longer. Good to know, but other factors are probably more important in making the choice."
]
}
],
"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.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
File diff suppressed because it is too large. Load diff
+504
View File
@@ -0,0 +1,504 @@
{
"metadata": {
"name": "",
"signature": "sha256:b9e2e6c3f7e82432833f1416dd0552c13753f683e1888682faf2910ef7d9f8ff"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When is Cheryl's Birthday?\n",
"===\n",
"\n",
"Peter Norvig, April 2015\n",
"\n",
"This logic puzzle has been [making the rounds](https://www.google.com/webhp?#q=cheryl%27s+birthday):\n",
"\n",
"\n",
"1. Albert and Bernard just became friends with Cheryl, and they want to know when her birtxhday is. Cheryl gave them a list of 10 possible dates:\n",
"<pre>\n",
" May 15 May 16 May 19\n",
" June 17 June 18\n",
" July 14 July 16\n",
" August 14 August 15 August 17\n",
"</pre>\n",
" \n",
"2. Cheryl then tells Albert and Bernard separately the month and the day of the birthday respectively.\n",
" \n",
"3. **Albert**: I don't know when Cheryl's birthday is, but I know that Bernard does not know too.\n",
" \n",
"4. **Bernard**: At first I don't know when Cheryl's birthday is, but I know now.\n",
" \n",
"5. **Albert**: Then I also know when Cheryl's birthday is.\n",
" \n",
"6. So when is Cheryl's birthday?\n",
"\n",
"Problem-Solving Tools\n",
"---\n",
"\n",
"Cheryl's puzzle was designed to be solved with a pencil, the greatest problem-solving tool in the history of mathematics (although some prefer a pen, chalk, marker, or a [stick for drawing in the sand](http://www.hellenicaworld.com/Greece/Science/en/Archimedes.html)). But I will show how to solve it with another tool: **computer code**. I choose this tool for four reasons: \n",
"- It is a more direct way to find the solution. All I have to do is faithfully describe the problem with code that says: *\"for each of the 10 possible dates, tell Albert the month and Bernard the day and check if statements 3 through 5 are true.\"* The intended [pencil and paper solution](https://scontent-iad.xx.fbcdn.net/hphotos-xpa1/v/t1.0-9/s720x720/11111802_983395601695416_3208022346737572922_n.jpg?oh=15fcb7edc4689dd9c71385b613446465&oe=55DD4488) requires not just understanding the *problem*, but also creatively discovering the steps of the *solution*&mdash;a harder task.\n",
"- With tested, debugged code, you're less likely to make a mistake that leads you to a [wrong answer](http://www.theguardian.com/science/alexs-adventures-in-numberland/2015/apr/15/why-the-cheryl-birthday-problem-turned-into-the-maths-version-of-thatdress).\n",
"- You'll learn how to solve problems that are similar, but can't be solved with pencil and paper because they have millions of possibilities rather than just 10.\n",
"- Solving puzzles is fun; programming is fun; solving puzzles with programs is double fun.\n",
"\n",
"We will translate each of the 6 statements in the puzzle into Python code:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"1. Cheryl gave them a list of 10 possible dates:\n",
"---\n"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"DATES = ['May 15', 'May 16', 'May 19',\n",
" 'June 17', 'June 18',\n",
" 'July 14', 'July 16',\n",
" 'August 14', 'August 15', 'August 17']"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We'll also define accessor functions for the month and day of a date:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def Month(date): return date.split()[0]\n",
"\n",
"def Day(date): return date.split()[1]"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 2
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"Month('May 15')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 3,
"text": [
"'May'"
]
}
],
"prompt_number": 3
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"Day('May 15')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 4,
"text": [
"'15'"
]
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"2. Cheryl then tells Albert and Bernard separately the month and the day of the birthday respectively.\n",
"---\n",
"\n",
"We can define the idea of **telling**, and while we're at it, the idea of **knowing** a birthdate:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def tell(part, possible_dates=DATES):\n",
" \"Cheryl tells a part of her birthdate to someone; return a new list of possible dates that match the part.\"\n",
" return [date for date in possible_dates if part in date]\n",
"\n",
"def know(possible_dates):\n",
" \"A person knows the birthdate if they have exactly one possible date.\"\n",
" return len(possible_dates) == 1"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 5
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that we use a *list of dates* to represent someone's knowledge of the possible birthdates, and that someone *knows* the birthdate when they get down to only one possibility. For example: If Cheryl tells Albert that her birthday is in May, he would have a list of three possible birthdates:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"tell('May')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 6,
"text": [
"['May 15', 'May 16', 'May 19']"
]
}
],
"prompt_number": 6
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And if she tells Bernard that her birthday is on the 15th, he would end up with two possibilities:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"tell('15')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 7,
"text": [
"['May 15', 'August 15']"
]
}
],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With two possibilities, Bernard does not know the birthdate:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"know(tell('15'))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 8,
"text": [
"False"
]
}
],
"prompt_number": 8
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Overall Strategy\n",
"---\n",
"\n",
"When Cheryl tells Albert `'May'` then *he* knows there are three possibilities, but *we* (the puzzle solvers) don't, because we don't know what Cheryl said. So what can we do? We will consider *all* of the possible dates, one at a time. For example, first consider `'May 15'`. Cheryl tells Albert `'May'` and Bernard `'15'`, giving them the lists of possible birthdates shown above. We can then check whether statements 3 through 5 are true in this scenario. If they are, then `'May 15'` is a solution to the puzzle. Repeat the process for each of the possible dates. If all goes well, there should be exactly one solution. \n",
"\n",
"Here is the main function, `cheryls_birthday`:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def cheryls_birthday(possible_dates=DATES):\n",
" \"Return a list of the possible dates for which statements 3 to 5 are true.\"\n",
" return filter(statements3to5, possible_dates)\n",
"\n",
"def statements3to5(date): return statement3(date) and statement4(date) and statement5(date)\n",
"\n",
"## TO DO: define statement3, statement4, statement5"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 9
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" (*Python note:* `filter(predicate, items)` returns a list of all items for which `predicate(item)` is true.)\n",
" \n",
" 3. Albert: I don't know when Cheryl's birthday is, but I know that Bernard does not know too.\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The function `statement3` takes as input a possible birthdate and returns true if Albert's statement is true for that birthdate. How do we go from Albert's English statement to a Python function? Let's paraphrase in a form that is closer to Python code:\n",
"\n",
"> **Albert**: After Cheryl told me the month of her birthdate, I didn't know her birthday. I don't know which day Cheryl told Bernard, but I know that for all of the possible dates, if Bernard is told that day, he wouldn't know the birthdate.\n",
"\n",
"That I can translate directly into code:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def statement3(date):\n",
" \"Albert: I don't know when Cheryl's birthday is, but I know that Bernard does not know too.\"\n",
" possible_dates = tell(Month(date))\n",
" return (not know(possible_dates) \n",
" and all(not know(tell(Day(d)))\n",
" for d in possible_dates))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 10
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can try the function on a date:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"statement3('May 15')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 11,
"text": [
"False"
]
}
],
"prompt_number": 11
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In fact, we can see all the dates that satisfy statement 3:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"filter(statement3, DATES)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 12,
"text": [
"['July 14', 'July 16', 'August 14', 'August 15', 'August 17']"
]
}
],
"prompt_number": 12
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"4. Bernard: At first I don't know when Cheryl's birthday is, but I know now.\n",
"---\n",
"\n",
"Again, a paraphrase:\n",
"\n",
"> **Bernard:** At first Cheryl told me the day, and I didn't know. Then I considered just the dates for which Albert's statement 3 is true, and now I know."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def statement4(date):\n",
" \"Bernard: At first I don't know when Cheryl's birthday is, but I know now.\"\n",
" at_first = tell(Day(date))\n",
" return (not know(at_first)\n",
" and know(filter(statement3, at_first)))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 13
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's see which dates satisfy both statement 3 and statement 4:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"filter(statement4, filter(statement3, DATES))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 14,
"text": [
"['July 16', 'August 15', 'August 17']"
]
}
],
"prompt_number": 14
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wait a minute&mdash;I thought that Bernard **knew**?! Why are there three possible dates remaining? Bernard does indeed know the birthdate,\n",
"because he knows something we don't know: the day. We won't know the birthdate until after statement 5.\n",
"\n",
"5. Albert: Then I also know when Cheryl's birthday is.\n",
"---\n",
"\n",
"Albert is saying that after hearing the month and Bernard's statement 4, he now knows Cheryl's birthday:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def statement5(date):\n",
" \"Albert: Then I also know when Cheryl's birthday is.\"\n",
" return know(filter(statement4, tell(Month(date))))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 15
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"6. So when is Cheryl's birthday?\n",
"---\n",
"\n",
"Let's see:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"cheryls_birthday()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 16,
"text": [
"['July 16']"
]
}
],
"prompt_number": 16
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Success!** We have deduced that Cheryl's birthday is **July 16**. It is now `True` that we know Cheryl's birthday:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"know(cheryls_birthday())"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 17,
"text": [
"True"
]
}
],
"prompt_number": 17
}
],
"metadata": {}
}
]
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+1231
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+836
View File
@@ -0,0 +1,836 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# John Conway's Game of Life\n",
"\n",
"The cellular automata game *Life*, invented by the mathematician [John Conway](https://en.wikipedia.org/wiki/John_Horton_Conway), makes a fun programming exercise. Let's review the [rules](http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life):\n",
"\n",
"The *world* of the Game of Life is an infinite two-dimensional orthogonal grid of *cells*, each of which is in one of two possible states, *live* or *empty*. Each cell has eight *neighbors*, the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following rules are applied to create the next *generation*:\n",
"\n",
"+ Any live cell with two or three live neighbors lives on to the next generation.\n",
"+ Any empty cell with exactly three live neighbors becomes a live cell in the next generation.\n",
"+ All other cells are empty in the next generation.\n",
"\n",
"For example, in the diagram below, \"`@`\" cells are live. In the transition from Generation 0 to 1, the cell marked \"`,`\" becomes empty (dies off) because it has zero live neighbors. In the next transition, a fourth `@` becomes live, because it has 3 live neighbors. All other cells stay the same. \n",
"\n",
" . . . . . . . . . . . . . . .\n",
" . . . @ . . . . , . . . . . .\n",
" . @ . . . . @ . . . . @ @ . .\n",
" . @ @ . . . @ @ . . . @ @ . .\n",
" . . . . . . . . . . . . . . .\n",
" Gen 0 Gen 1 Gen 2\n",
" \n",
"\n",
"\n",
"The world continues to evolve by these rules for as long as you care to observe. \n",
"\n",
"# Developing a Life Program\n",
"\n",
"\n",
"To create a program to play Life, start with the vocabulary of concepts:\n",
"\n",
"+ **World**\n",
"+ **Cell**\n",
"+ **Live/Empty**\n",
"+ **Neighbors**\n",
"+ **Next Generation**\n",
"+ **Display**\n",
"+ **Live Neighbor Counts**\n",
"\n",
"and consider how to implement them:\n",
"\n",
"+ **World**: The state of the world must represent which cells are empty and which are live. The tricky part is that the number of cells is infinite, and we can't store an infinite array in a finite computer. I can think of three ways to deal with this problem:\n",
" 1. **Change the rules**; make the world finite instead of infinite. (Cells at the edge of the world have fewer neighbors, or perhaps they wrap around to the other side of the world.)\n",
" 2. Use a **finite rectangular window** that covers all the live cells in the infinite grid. As the world\n",
" evolves, this window may have to grow or shift.\n",
"<br>Example: `world = [[0, 0, 0, 0, 0], [0, 0, 0, 1, 0], [0, 1, 0, 0, 0], [0, 1, 1, 0, 0], [0, 0, 0, 0, 0]]` \n",
"\n",
" 3. Represent a world as a **set of live cells.** This set will grow and shrink in size from one generation to the next, but we don't have to worry about overflowing the edges of an array.\n",
"<br>Example: `world = {(3, 1), (1, 2), (1, 3), (2, 3)}` \n",
"<br>I will go with this choice.\n",
"\n",
"\n",
"+ **Cell**: Each cell will be represented as an (x, y) pair of integer coordinates. <br>Example: `cell = (1, 2)`.\n",
"+ **Live/Empty**: \n",
"A cell is live if it is a member of the set of live cells. \n",
"<br>Example: \"`cell in world`\" is True, given the definition of `cell` and `world` above, so `cell` is live.\n",
"+ **Neighbors**: The cell `(x, y)` has eight neighbors, formed by adding or subtracting 1 from `x` or `y` or both. We can define\n",
"a function `neighbors(cell)` to return this set.\n",
"<br>Example: `neighbors((8, 8)) == [(7, 7), (8, 7), (9, 7), (7, 8), (9, 8), (7, 9), (8, 9), (9, 9)]`\n",
"\n",
"+ **Display**: We will need some way to display the state of the world. Let's defer that for now.\n",
"\n",
"+ **Next Generation**: We can define a function, `next_generation(world)`, that takes a world as input and returns\n",
"a new world with the new set of live cells according to the rules.\n",
"Example: `next_generation({(3, 1), (1, 2), (1, 3), (2, 3)}) == {(1, 2), (1, 3), (2, 3)}`\n",
"\n",
"+ **Live Neighbor Counts**: I need to know how many live neighbors each cell has. A good way to represent this is a dict of `{(x, y): count}`. But which cells need to be the keys of this dict? We can start with the live cells, and also add any cells neighboring the live cells. An easy way to generate this dict is to create a `Counter` and pass it every neighbor of every live cell. This may feel like we're doing the counting \"backwards.\" Instead of asking \"for each cell, how many live neighbors does it have?\" we are saying \"for each live cell, increment the count of each of its neighbors.\" The two amount to the same thing because *neighbor* is symmetric&mdash;if P is a neighbor of Q, then Q is a neighbor of P. Below we see the neighbor counts for each of the three generations; in each generation the top diagram gives the neighbor counts for the empty cells, and the bottom diagram gives the counts for the live cells. This is just to make the diagram easier to read; in the code these are combined into one `Counter`. Here are the counts:\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" . . 1 1 1 . . . . . . . . . .\n",
" 1 1 2 @ 1 1 1 1 , . 1 2 2 1 .\n",
" 2 @ 4 2 1 2 @ 3 1 . 2 @ @ 2 .\n",
" 2 @ @ 1 . 2 @ @ 1 . 2 @ @ 2 .\n",
" 1 2 2 1 . 1 2 2 1 . 1 2 2 1 .\n",
" Gen 0 Gen 1 Gen 2\n",
" . . . . . . . . . . . . . . .\n",
" . . . 0 . . . . , . . . . . .\n",
" . 2 . . . . 2 . . . . 3 3 . .\n",
" . 2 2 . . . 2 2 . . . 3 3 . .\n",
" . . . . . . . . . . . . . . .\n",
" \n",
"\n",
"Here is the implementation. Note that in `next_generation` the `neighbor_counts` is used two ways so I decided to use two different names for clarity: `possible_cells` is used to iterate over all cells that might be live, and `counts` is used to check if a cell has the right number of neighbors."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from collections import Counter\n",
"\n",
"def next_generation(world):\n",
" \"The set of live cells in the next generation.\"\n",
" possible_cells = counts = neighbor_counts(world)\n",
" return {cell for cell in possible_cells\n",
" if (counts[cell] == 3) \n",
" or (counts[cell] == 2 and cell in world)}\n",
"\n",
"def neighbor_counts(world):\n",
" \"A {cell: int} counter of the number of live neighbors for each cell that has neighbors.\"\n",
" return Counter(nb for cell in world \n",
" for nb in neighbors(cell))\n",
"\n",
"def neighbors(cell):\n",
" \"All 8 adjacent neighbors of cell.\"\n",
" (x, y) = cell\n",
" return [(x-1, y-1), (x, y-1), (x+1, y-1), \n",
" (x-1, y), (x+1, y), \n",
" (x-1, y+1), (x, y+1), (x+1, y+1)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see how this works:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(1, 2), (1, 3), (2, 3)}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"world = {(3, 1), (1, 2), (1, 3), (2, 3)}\n",
"next_generation(world)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(1, 2), (1, 3), (2, 2), (2, 3)}"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next_generation(next_generation(world))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[(1, 3), (2, 3), (3, 3), (1, 4), (3, 4), (1, 5), (2, 5), (3, 5)]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"neighbors((2, 4))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Counter({(0, 1): 1,\n",
" (0, 2): 2,\n",
" (0, 3): 2,\n",
" (0, 4): 1,\n",
" (1, 1): 1,\n",
" (1, 2): 2,\n",
" (1, 3): 2,\n",
" (1, 4): 2,\n",
" (2, 0): 1,\n",
" (2, 1): 2,\n",
" (2, 2): 4,\n",
" (2, 3): 2,\n",
" (2, 4): 2,\n",
" (3, 0): 1,\n",
" (3, 2): 2,\n",
" (3, 3): 1,\n",
" (3, 4): 1,\n",
" (4, 0): 1,\n",
" (4, 1): 1,\n",
" (4, 2): 1})"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"neighbor_counts(world)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`run` is a function to play n generations of Life:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def run(world, n):\n",
" \"Run the world for n generations. No display; just return the nth generation.\"\n",
" for g in range(n):\n",
" world = next_generation(world)\n",
" return world"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(1, 2), (1, 3), (2, 2), (2, 3)}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"run(world, 100)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Display\n",
"\n",
"Now let's see how to display worlds. We'll consider a rectangular window on the infinite plane, specified as ranges of `Xs` and `Ys` coordinates. The function `picture` turns a world into a string showing what the world looks like:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"from IPython.display import clear_output, display_html\n",
"\n",
"LIVE = '@'\n",
"EMPTY = '.'\n",
"PAD = ' '\n",
" \n",
"def picture(world, Xs, Ys):\n",
" \"Return a picture: a grid of characters representing the cells in this window.\"\n",
" def row(y): return PAD.join(LIVE if (x, y) in world else EMPTY for x in Xs)\n",
" return '\\n'.join(row(y) for y in Ys)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
". . . . .\n",
". . . @ .\n",
". @ . . .\n",
". @ @ . .\n",
". . . . .\n"
]
}
],
"source": [
"print(picture(world, range(5), range(5)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The function `display_run` runs the world for `n` steps, displaying the picture at each step:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"def display_run(world, n=10, Xs=range(10), Ys=range(10), pause=0.2):\n",
" \"Step and display the world for the given number of generations.\"\n",
" for g in range(n + 1):\n",
" html = ('Generation {}, Population {}\\n{}'\n",
" .format(g, len(world), pre(picture(world, Xs, Ys))))\n",
" clear_output()\n",
" display_html(html, raw=True)\n",
" time.sleep(pause)\n",
" world = next_generation(world)\n",
" \n",
"def pre(text): return '<pre>' + text + '</pre>'"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Generation 5, Population 4\n",
"<pre>. . . . .\n",
". . . . .\n",
". @ @ . .\n",
". @ @ . .\n",
". . . . .</pre>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display_run(world, 5, range(5), range(5))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Interesting Worlds\n",
"\n",
"Now let's take a look at some initial worlds that *Life* enthusiasts have discovered. It would be tedious to enumerate these with an explicit set of `(x, y)` coordinates, so we will define the function `shape` that takes a picture as input and returns a world; `shape` and `picture` are more-or-less inverses. "
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def shape(picture, offset=(3, 3)):\n",
" \"Convert a graphical picture (e.g. '@ @ .\\n. @ @') into a world (set of cells).\"\n",
" cells = {(x, y) \n",
" for (y, row) in enumerate(picture.splitlines())\n",
" for (x, c) in enumerate(row.replace(PAD, ''))\n",
" if c == LIVE}\n",
" return move(cells, offset)\n",
"\n",
"def move(cells, offset):\n",
" \"Move/Translate/slide a set of cells by a (dx, dy) displacement/offset.\"\n",
" (dx, dy) = offset\n",
" return {(x+dx, y+dy) for (x, y) in cells}\n",
"\n",
"blinker = shape(\"@@@\")\n",
"block = shape(\"@@\\n@@\")\n",
"beacon = block | move(block, (2, 2))\n",
"toad = shape(\".@@@\\n@@@.\")\n",
"glider = shape(\".@.\\n..@\\n@@@\")\n",
"rpentomino = shape(\".@@\\n@@.\\n.@.\", (36, 20))\n",
"line = shape(\".@@@@@@@@.@@@@@...@@@......@@@@@@@.@@@@@\", (10, 10))\n",
"growth = shape(\"@@@.@\\n@\\n...@@\\n.@@.@\\n@.@.@\", (10, 10))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is how `shape` and `move` work:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(3, 3), (4, 3), (4, 4), (5, 4)}"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"shape(\"\"\"@ @ .\n",
" . @ @\"\"\")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(3, 3), (3, 4), (4, 3), (4, 4)}"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"block"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(103, 203), (103, 204), (104, 203), (104, 204)}"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"move(block, (100, 200))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's run some examples. If you are viewing a static notebook, you will only see the last generation; rerun each cell to see all the generations."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Generation 10, Population 3\n",
"<pre>. . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . @ @ @ . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .</pre>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display_run(blinker)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Generation 10, Population 8\n",
"<pre>. . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . @ @ . . . . .\n",
". . . @ @ . . . . .\n",
". . . . . @ @ . . .\n",
". . . . . @ @ . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .</pre>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display_run(beacon)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Generation 10, Population 6\n",
"<pre>. . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . @ @ @ . . .\n",
". . . @ @ @ . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .</pre>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display_run(toad)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Generation 15, Population 5\n",
"<pre>. . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . . . .\n",
". . . . . . . @ . .\n",
". . . . . . . . @ @\n",
". . . . . . . @ @ .</pre>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display_run(glider, 15)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Generation 130, Population 178\n",
"<pre>. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . @\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . @ . . . . . . . . . . . . . @\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @\n",
". . . . . . . . . . . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . @ @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @\n",
". . . . . @ . @ @ @ @ . @ @ . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . @ . @\n",
". . . @ @ . . @ @ . . @ @ @ . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . @ . @\n",
". . . . . @ @ @ . . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ .\n",
". . . . . . . . . . . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . @ . @ . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . .\n",
". . . . . . . . @ @ . . . . . . . . . . . . . . . . . @ . @ . . . @ @ . . . . . . . . . . . . .\n",
". . . . @ @ @ . . @ @ . . . . . . . . . . . . . . . . @ . @ . . . . . . . . . . . . . . . . @ @\n",
". . . . @ @ . . . @ @ . . . . . . . . . . . . . . . . @ . . . . . . . . . . . . . . . . . @ @ @\n",
". . . . @ . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . . . @ . . . . . . . @ . .\n",
". . . . @ @ . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . @ . . . . . . . @ .\n",
". . . . . @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . @ . . . . . . . .\n",
". . . . @ . . @ . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . @ . . . @ @ . . . .\n",
". . . . . @ . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . @ @ . . . @ . . @ . . .\n",
"@ @ . @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . @ . . @ . . . . . . . @ . . @ . . .\n",
". . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . @ @ @ . . . . . . @ @ . . . .\n",
". . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . @ . . . . . . . . . . .\n",
"@ @ . @ @ . . . . . . . . . . @ @ . . . . . . . . . . . . . . . @ . . . . . . . . . . . . . . .\n",
"@ . . @ . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . .\n",
"@ @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . @ . @ . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . @ . @ . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . @ . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . @ . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .</pre>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display_run(rpentomino, 130, range(48), range(40))"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Generation 160, Population 111\n",
"<pre>. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . @\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . @ . @ . . @ .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . @ . . @ . @ @ . @ .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . @ @ . . @ @ . . . @\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . @ . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ @ . @ @ @ @ @ . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . @ @ @ . . . @ . . . @ @ . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . @ . @ . @ . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . @ . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . @ @ . . @ . . . . . . . @\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . @ . . . . . . @ @ .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . @ . . . . . . . . @ @ .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ . . . . . . . . @ @ .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . @\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . @ @ @ . . . . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . @ . . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .</pre>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"zoo = (move(blinker, (5, 25)) | move(glider, (8, 13)) | move(blinker, (20, 25)) |\n",
" move(beacon, (24, 25)) | move(toad, (30, 25)) | move(block, (13, 25)) | move(block, (17, 33)))\n",
"\n",
"display_run(zoo, 160, range(48), range(40))"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Generation 100, Population 72\n",
"<pre>. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . @ . . . @ @ . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . @ . @ . . @ . @ . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . @ . . . @ . . @ . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . @ @ . . . . @ . @ . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . @ @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . @ @ @ . @ @ . @ @ . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . @ @ . . . @ . @ @ @ . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . @ @ @ . . . . . @ . . @ . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . @ @ @ @ @ . @ @ @ @ @ @ @ . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . @ @ @ . @ @ @ @ @ @ @ @ . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . @ @ . @ @ @ @ @ . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .</pre>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display_run(growth, 100, range(40), range(40))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Outside of IPython\n",
"\n",
"If you want to run this code in your terminal, outside of an Ipython/Jupyter notebook, you can remove the line:\n",
"\n",
" from IPython.display import clear_output, display_html\n",
" \n",
"and add these lines:\n",
"\n",
" def clear_output(): print(\"\\033[;H\\033[2J\") # ANSI terminal home and clear\n",
" \n",
" def display_html(text, raw=False): print(text)\n",
" \n",
" def pre(text): return text\n",
" \n",
"# Coding Kata\n",
"\n",
"I once attended a [code kata](https://en.wikipedia.org/wiki/Kata_(programming%29) in which one of the exercises was to write the Game of Life without using any conditional (e.g. `if`) statements. I did it by using roughly the program shown here, but changing the lone `if` to a `filter` in `next_generation`:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [],
"source": [
"def next_generation(world):\n",
" \"The set of live cells in the next generation.\"\n",
" possible_cells = counts = neighbor_counts(world)\n",
" def good(cell): return counts[cell] == 3 or (counts[cell] == 2 and cell in world)\n",
" return set(filter(good, possible_cells))"
]
}
],
"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.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
File diff suppressed because it is too large. Load diff
+534
View File
@@ -0,0 +1,534 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Panama Palindrome\n",
"\n",
"## Utilities"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import random, re, bisect, time\n",
"\n",
"def is_palindrome(s):\n",
" \"Test if a string is a palindrome (only considering letters a-z).\"\n",
" s1 = canonical(s)\n",
" return s1 == reversestr(s1)\n",
"\n",
"def is_unique_palindrome(s):\n",
" \"Test if string s is a palindrome where each comma-separated phrase is unique.\"\n",
" return is_palindrome(s) and is_unique(phrases(s))\n",
"\n",
"def canonical(word, sub=re.compile('[^a-z]').sub):\n",
" \"The canonical form for comparing: only lowercase a-z.\"\n",
" return sub('', word.lower())\n",
"\n",
"def phrases(s):\n",
" \"Break a string s into comma-separated phrases.\"\n",
" return [phrase.strip() for phrase in s.split(',')]\n",
"\n",
"def reversestr(s):\n",
" \"Reverse a string.\"\n",
" return s[::-1]\n",
"\n",
"def is_unique(collection):\n",
" \"Return true if collection has no duplicate elements.\"\n",
" return len(collection) == len(set(collection))"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"'test_utils passes'"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def test_utils():\n",
" assert is_unique_palindrome('A man, a plan, a canal, Panama!')\n",
" assert is_unique_palindrome('''A (man), a PLAN... a ``canal?'' -- Panama!''')\n",
" assert not is_unique_palindrome('A man, a plan, a radar, a canal, Panama.')\n",
" \n",
" assert is_palindrome('A man, a plan, a canal, Panama.')\n",
" assert is_palindrome('Radar. Radar? Radar!')\n",
" assert not is_palindrome('radars')\n",
"\n",
" assert phrases('A man, a plan, Panama') == ['A man', 'a plan', 'Panama']\n",
" assert canonical('A man, a plan, a canal, Panama') == 'amanaplanacanalpanama'\n",
" assert reversestr('foo') == 'oof'\n",
" assert is_unique([1, 2, 3])\n",
" assert not is_unique([1, 2, 2])\n",
" return 'test_utils passes'\n",
"\n",
"test_utils()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The Dictionary"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"! [ -e npdict.txt ] || curl -O http://norvig.com/npdict.txt"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 126144 204928 1383045 npdict.txt\r\n"
]
}
],
"source": [
"! wc npdict.txt"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"################ Reading in a dictionary\n",
"\n",
"class PalDict:\n",
" \"\"\"A dictionary with the following fields:\n",
" words: a sorted list of words: ['ant', 'bee', 'sea']\n",
" rwords: a sorted list of reversed words: ['aes', 'eeb', 'tna']\n",
" truename: a dict of {canonical:true} pairs, e.g. {'anelk': 'an elk', 'anneelk': 'Anne Elk'}\n",
" k:\n",
" and the followng methods:\n",
" \"\"\"\n",
" \n",
" def __init__(self, k=100, filename='npdict.txt'):\n",
" words, rwords, truename = [], [], {'': '', 'panama': 'Panama!'}\n",
" for tword in open(filename, 'r', encoding='ascii', errors='ignore').read().splitlines():\n",
" word = canonical(tword)\n",
" words.append(word)\n",
" rwords.append(reversestr(word))\n",
" truename[word] = tword\n",
" words.sort()\n",
" rwords.sort()\n",
" self.k = k\n",
" self.words = words\n",
" self.rwords = rwords\n",
" self.truename = truename\n",
" self.rangek = range(k)\n",
" self.tryharder = False\n",
"\n",
" def startswith(self, prefix):\n",
" \"\"\"Return up to k canonical words that start with prefix.\n",
" If there are more than k, choose from them at random.\"\"\"\n",
" return self._k_startingwith(self.words, prefix)\n",
"\n",
" def endswith(self, rsuffix):\n",
" \"\"\"Return up to k canonical words that end with the reversed suffix.\n",
" If you want words ending in 'ing', ask for d.endswith('gni').\n",
" If there are more than k, choose from them at random.\"\"\"\n",
" return [reversestr(s) for s in self._k_startingwith(self.rwords, rsuffix)]\n",
"\n",
" def __contains__(self, word):\n",
" return word in self.truename\n",
"\n",
" def _k_startingwith(self, words, prefix):\n",
" start = bisect.bisect_left(words, prefix)\n",
" end = bisect.bisect(words, prefix + 'zzzz')\n",
" n = end - start\n",
" if self.k >= n: # get all the words that start with prefix\n",
" results = words[start:end]\n",
" else: # sample from words starting with prefix \n",
" indexes = random.sample(range(start, end), self.k)\n",
" results = [words[i] for i in indexes]\n",
" random.shuffle(results)\n",
" ## Consider words that are prefixes of the prefix.\n",
" ## This is very slow, so don't use it until late in the game.\n",
" if self.tryharder:\n",
" for i in range(3, len(prefix)):\n",
" w = prefix[0:i]\n",
" if ((words == self.words and w in self.truename) or\n",
" (words == self.rwords and reversestr(w) in self.truename)):\n",
" results.append(w)\n",
" return results\n",
"\n",
"paldict = PalDict() \n",
"\n",
"def anpdictshort():\n",
" \"Find the words that are valid when every phrase must start with 'a'\"\n",
" def segment(word): return [s for s in word.split('a') if s]\n",
" def valid(word): return all(reversestr(s) in segments for s in segment(word))\n",
" words = [canonical(w) for w in open('anpdict.txt')]\n",
" segments = set(s for w in words for s in segment(w))\n",
" valid_words = [paldict.truename[w] for w in words if valid(w)]\n",
" file('anpdict-short2.txt', 'w').write('\\n'.join(valid_words))\n",
"\n",
"################ Search for a palindrome\n",
"\n",
"class Panama:\n",
" def __init__(self, L='A man, a plan', R='a canal, Panama', dict=paldict):\n",
" ## .left and .right hold lists of canonical words\n",
" ## .diff holds the number of characters that are not matched,\n",
" ## positive for words on left, negative for right.\n",
" ## .stack holds (action, side, arg) tuples\n",
" self.left = []\n",
" self.right = []\n",
" self.best = 0\n",
" self.seen = {}\n",
" self.diff = 0\n",
" self.stack = []\n",
" self.starttime = time.clock()\n",
" self.dict = dict\n",
" self.steps = 0\n",
" for word in L.split(','):\n",
" self.add('left', canonical(word))\n",
" for rword in reversestr(R).split(','):\n",
" self.add('right', canonical(reversestr(rword)))\n",
" self.consider_candidates()\n",
" \n",
" def search(self, steps=10*1000*1000):\n",
" \"Search for palindromes.\"\n",
" for self.steps in range(steps):\n",
" if not self.stack:\n",
" return 'done'\n",
" action, dir, substr, arg = self.stack[-1]\n",
" if action == 'added': # undo the last word added\n",
" self.remove(dir, arg)\n",
" elif action == 'trying' and arg: # try the next word if there is one\n",
" self.add(dir, arg.pop()) and self.consider_candidates()\n",
" elif action == 'trying' and not arg: # otherwise backtrack\n",
" self.stack.pop()\n",
" else:\n",
" raise ValueError(action)\n",
" self.report()\n",
" return self\n",
"\n",
" def add(self, dir, word):\n",
" \"add a word\"\n",
" if word in self.seen:\n",
" return False\n",
" else:\n",
" getattr(self, dir).append(word)\n",
" self.diff += factor[dir] * len(word)\n",
" self.seen[word] = True\n",
" self.stack.append(('added', dir, '?', word))\n",
" return True\n",
"\n",
" def remove(self, dir, word):\n",
" \"remove a word\"\n",
" oldword = getattr(self, dir).pop()\n",
" assert word == oldword\n",
" self.diff -= factor[dir] * len(word)\n",
" del self.seen[word]\n",
" self.stack.pop()\n",
" \n",
" def consider_candidates(self):\n",
" \"\"\"Push a new state with a set of candidate words onto stack.\"\"\"\n",
" if self.diff > 0: # Left is longer, consider adding on right\n",
" dir = 'right'\n",
" substr = self.left[-1][-self.diff:]\n",
" candidates = self.dict.endswith(substr)\n",
" elif self.diff < 0: # Right is longer, consider adding on left\n",
" dir = 'left'\n",
" substr = reversestr(self.right[-1][0:-self.diff])\n",
" candidates = self.dict.startswith(substr)\n",
" else: # Both sides are same size\n",
" dir = 'left'\n",
" substr = ''\n",
" candidates = self.dict.startswith('')\n",
" if substr == reversestr(substr):\n",
" self.report()\n",
" self.stack.append(('trying', dir, substr, candidates))\n",
" \n",
" def report(self):\n",
" \"Report a new palindrome to log file (if it is sufficiently big).\"\n",
" N = len(self)\n",
" if N > 13333:\n",
" self.dict.tryharder = True\n",
" if N > self.best and (N > 13000 or N > self.best+1000):\n",
" self.best = len(self)\n",
" self.bestphrase = str(self)\n",
" print('%5d phrases (%5d words) in %3d seconds (%6d steps)' % (\n",
" self.best, self.bestphrase.count(' ')+1, time.clock() - self.starttime,\n",
" self.steps))\n",
" assert is_unique_palindrome(self.bestphrase)\n",
"\n",
" def __len__(self):\n",
" return len(self.left) + len(self.right)\n",
"\n",
" def __str__(self):\n",
" truename = self.dict.truename\n",
" lefts = [truename[w] for w in self.left]\n",
" rights = [truename[w] for w in self.right]\n",
" return ', '.join(lefts + rights[::-1])\n",
" \n",
" def __repr__(self):\n",
" return '<Panama with {} phrases>'.format(len(self))\n",
"\n",
"factor = {'left': +1, 'right': -1}\n",
"\n",
"# Note that we only allow one truename per canonical name. Occasionally\n",
"# this means we miss a good word (as in \"a node\" vs. \"an ode\"), but there\n",
"# are only 665 of these truename collisions, and most of them are of the\n",
"# form \"a mark-up\" vs. \"a markup\" so it seemed better to disallow them.\n"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"126144"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(paldict.words)"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"all tests pass\n",
" 1005 phrases ( 1239 words) in 0 seconds ( 18582 steps)\n",
" 2012 phrases ( 2478 words) in 0 seconds ( 41886 steps)\n",
" 3017 phrases ( 3710 words) in 0 seconds ( 64444 steps)\n",
" 4020 phrases ( 4957 words) in 0 seconds ( 92989 steps)\n",
" 5022 phrases ( 6184 words) in 1 seconds (128986 steps)\n",
" 6024 phrases ( 7408 words) in 1 seconds (162634 steps)\n",
" 7027 phrases ( 8607 words) in 1 seconds (204639 steps)\n",
" 8036 phrases ( 9846 words) in 2 seconds (254992 steps)\n",
" 9037 phrases (11050 words) in 2 seconds (320001 steps)\n",
"10039 phrases (12257 words) in 2 seconds (417723 steps)\n",
"11040 phrases (13481 words) in 3 seconds (565050 steps)\n",
"12043 phrases (14711 words) in 4 seconds (887405 steps)\n"
]
},
{
"data": {
"text/plain": [
"<Panama with 12764 phrases>"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"################ Unit Tests\n",
" \n",
"def test2(p=PalDict()):\n",
" d = p.dict\n",
" def sameset(a, b): return set(a) == set(b)\n",
" assert 'panama' in d\n",
" assert d.words[0] in d\n",
" assert d.words[-1] in d\n",
" assert sameset(d.startswith('aword'), ['awording', 'awordbreak',\n",
" 'awordiness', 'awordage', 'awordplay', 'awordlore', 'awordbook',\n",
" 'awordlessness', 'aword', 'awordsmith'])\n",
" assert sameset(d.endswith('ytisob'), ['aglobosity', 'averbosity',\n",
" 'asubglobosity', 'anonverbosity', 'agibbosity'])\n",
" d.tryharder = True\n",
" assert sameset(d.startswith('oklahoma'), ['oklahoma', 'okla'])\n",
" d.tryharder = False\n",
" assert d.startswith('oklahoma') == ['oklahoma']\n",
" assert d.startswith('fsfdsfdsfds') == []\n",
" print('all tests pass')\n",
" return p\n",
"\n",
"p = Panama()\n",
"test2(p)\n",
"p.search().report()\n",
"p"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" % Total % Received % Xferd Average Speed Time Time Time Current\n",
" Dload Upload Total Spent Left Speed\n",
"100 847k 100 847k 0 0 1037k 0 --:--:-- --:--:-- --:--:-- 1037k\n"
]
}
],
"source": [
"! [ -e anpdict.txt ] || curl -O http://norvig.com/anpdict.txt"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"anpdictshort()"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 4527 9055 39467 anpdict-short.txt\r\n",
" 4527 9055 39467 anpdict-short2.txt\r\n",
" 69241 138489 867706 anpdict.txt\r\n",
" 126144 204928 1383045 npdict.txt\r\n",
" 204439 361527 2329685 total\r\n"
]
}
],
"source": [
"! wc *npd*"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# Letter-By-Letter Approach\n",
"\n",
"Can we go letter-by-letter instead of word-by-word? Advantages: \n",
"\n",
"* We can (if need be) be exhaustive at each decision point, trying all 26 possibilities.\n",
"* We can try the most likely letters first.\n",
"\n",
"Process\n",
"\n",
"* Keep left- nad right- partial phrase lists; and the current state:\n",
"\n",
" {left: ['aman', 'aplan'], right: ['acanal', panama'],\n",
" left_word: True, right_word: True, extra_chars: +3, palindrome: True}\n",
" \n",
"* Now consider all ways of extending:\n",
"\n",
" - Add the letter `'a'` to the left, either as a new word or a continuation of the old word (perhaps going for `'a planaria'`).\n",
" - Add a letter, any letter, to the right, either as a new word or a continuation of \n",
" \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from collections import namedtuple\n",
"\n",
"\n",
"def do(state, action, side, L): action(state, side, L)\n",
"def add(state, side, L): getattr(state, side)[-1] += L\n",
"def new(state, side, L): getattr(state, side).append(L)\n",
"def undo(action, letter):\n",
" if action == add:\n",
" elif action == new:\n",
" else:\n",
" raise ValueError()"
]
}
],
"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.5.1"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+483
View File
@@ -0,0 +1,483 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Translating English Sentences into Propositional Logic Statements\n",
"\n",
"In a Logic course, one exercise is to turn an English sentence like this:\n",
"\n",
"> *Sieglinde will survive, and either her son will gain the Ring and Wotan’s plan will be fulfilled or else Valhalla will be destroyed.*\n",
"\n",
"Into a formal Propositional Logic statement: \n",
"\n",
" P ⋀ ((Q ⋀ R) ∨ S)\n",
" \n",
"along with definitions of the propositions:\n",
"\n",
" P: Sieglinde will survive\n",
" Q: Sieglinde’s son will gain the Ring\n",
" R: Wotan’s plan will be fulfilled\n",
" S: Valhalla will be destroyed\n",
"\n",
"For some sentences, it takes detailed knowledge to get a good translation. The following two sentences are ambiguous, with different prefered interpretations, and translating them correctly requires knowledge of eating habits:\n",
"\n",
" I will eat salad or I will eat bread and I will eat butter. P ∨ (Q ⋀ R)\n",
" I will eat salad or I will eat soup and I will eat ice cream. (P ∨ Q) ⋀ R\n",
"\n",
"But for many sentences, the translation process is automatic, with no special knowledge required. I will develop a program to handle these easy sentences. The program is based on the idea of a series of translation rules of the form:\n",
"\n",
" Rule('{P} ⇒ {Q}', 'if {P} then {Q}', 'if {P}, {Q}')\n",
" \n",
"which means that the logic translation will have the form `'P ⇒ Q'`, whenever the English sentence has either the form `'if P then Q'` or `'if P, Q'`, where `P` and `Q` can match any non-empty subsequence of characters. Whatever matches `P` and `Q` will be recursively processed by the rules. The rules are in order&mdash;top to bottom, left to right, and the first rule that matches in that order will be accepted, no matter what, so be sure you order your rules carefully. One guideline I have adhered to is to put all the rules that start with a keyword (like `'if'` or `'neither'`) before the rules that start with a variable (like `'{P}'`); that way you avoid accidently having a keyword swallowed up inside a `'{P}'`.\n",
"\n",
"Consider the example sentence `\"If loving you is wrong, I don't want to be right.\"` This should match the pattern \n",
"`'if {P}, {Q}'` with the variable `P` equal to `\"loving you is wrong\"`. But I don't want the variable `Q` to be \n",
"`\"I don't want to be right\"`, rather, I want to have `~Q` equal to `\"I do want to be right\"`. So in addition to having a set of `Rule`s to handle the `'if {P}, {Q}'` patterns, I will also have a list of `negations` to handle `\"don't\"` and the like.\n",
"\n",
"Here is the code to process `Rule` definitions (using [regular expressions](https://docs.python.org/3.5/library/re.html), which can sometimes be confusing.)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import re\n",
"\n",
"def Rule(output, *patterns):\n",
" \"A rule that produces `output` if the entire input matches any one of the `patterns`.\" \n",
" return (output, [name_group(pat) + '$' for pat in patterns])\n",
"\n",
"def name_group(pat):\n",
" \"Replace '{Q}' with '(?P<Q>.+?)', which means 'match 1 or more characters, and call it Q'\"\n",
" return re.sub('{(.)}', r'(?P<\\1>.+?)', pat)\n",
" \n",
"def word(w):\n",
" \"Return a regex that matches w as a complete word (not letters inside a word).\"\n",
" return r'\\b' + w + r'\\b' # '\\b' matches at word boundary"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's see what a rule looks like:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(('{P} ⇒ {Q}',\n",
" ['if (?P<P>.+?) then (?P<Q>.+?)$', 'if (?P<P>.+?), (?P<Q>.+?)$']),)"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Rule('{P} ⇒ {Q}', 'if {P} then {Q}', 'if {P}, {Q}'),"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And now the actual rules. If your sentence is not translated correctly, you can attempt to augment these rules to handle your sentence."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"rules = [\n",
" Rule('{P} ⇒ {Q}', 'if {P} then {Q}', 'if {P}, {Q}'),\n",
" Rule('{P} ⋁ {Q}', 'either {P} or else {Q}', 'either {P} or {Q}'),\n",
" Rule('{P} ⋀ {Q}', 'both {P} and {Q}'),\n",
" Rule('~{P} ⋀ ~{Q}', 'neither {P} nor {Q}'),\n",
" Rule('~{A}{P} ⋀ ~{A}{Q}', '{A} neither {P} nor {Q}'), # The Kaiser neither ...\n",
" Rule('~{Q} ⇒ {P}', '{P} unless {Q}'),\n",
" Rule('{P} ⇒ {Q}', '{Q} provided that {P}', '{Q} whenever {P}', \n",
" '{P} implies {Q}', '{P} therefore {Q}', \n",
" '{Q}, if {P}', '{Q} if {P}', '{P} only if {Q}'),\n",
" Rule('{P} ⋀ {Q}', '{P} and {Q}', '{P} but {Q}'),\n",
" Rule('{P} ⋁ {Q}', '{P} or else {Q}', '{P} or {Q}'),\n",
" ]\n",
"\n",
"negations = [\n",
" (word(\"not\"), \"\"),\n",
" (word(\"cannot\"), \"can\"),\n",
" (word(\"can't\"), \"can\"),\n",
" (word(\"won't\"), \"will\"),\n",
" (word(\"ain't\"), \"is\"),\n",
" (\"n't\", \"\"), # matches as part of a word: didn't, couldn't, etc.\n",
" ]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now the mechanism to process these rules. The key function is `match_rule`, which matches an English sentence against a rule. The function returns two values, a string representing the translation of the Eng;lish sentence into logic, and `defs`, a dictionary of `{Variable: \"value\"}` pairs. If `match_rule` finds that the rule matches, it recursively calls `match_rules` to match each of the subgroups of the regular expression (the `P` and `Q` in `if {P}, then {Q}`).\n",
"The function `match_literal` handles negations, and is where the `defs` dictionary actually gets updated."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def match_rules(sentence, rules, defs):\n",
" \"\"\"Match sentence against all the rules, accepting the first match; or else make it an atom.\n",
" Return two values: the Logic translation and a dict of {P: 'english'} definitions.\"\"\"\n",
" sentence = clean(sentence)\n",
" for rule in rules:\n",
" result = match_rule(sentence, rule, defs)\n",
" if result: \n",
" return result\n",
" return match_literal(sentence, negations, defs)\n",
" \n",
"def match_rule(sentence, rule, defs):\n",
" \"Match rule, returning the logic translation and the dict of definitions if the match succeeds.\"\n",
" output, patterns = rule\n",
" for pat in patterns:\n",
" match = re.match(pat, sentence, flags=re.I)\n",
" if match:\n",
" groups = match.groupdict()\n",
" for P in sorted(groups): # Recursively apply rules to each of the matching groups\n",
" groups[P] = match_rules(groups[P], rules, defs)[0]\n",
" return '(' + output.format(**groups) + ')', defs\n",
" \n",
"def match_literal(sentence, negations, defs):\n",
" \"No rule matched; sentence is an atom. Add new proposition to defs. Handle negation.\"\n",
" polarity = ''\n",
" for (neg, pos) in negations:\n",
" (sentence, n) = re.subn(neg, pos, sentence, flags=re.I)\n",
" polarity += n * '~'\n",
" sentence = clean(sentence)\n",
" P = proposition_name(sentence, defs)\n",
" defs[P] = sentence\n",
" return polarity + P, defs\n",
" \n",
"def proposition_name(sentence, defs, names='PQRSTUVWXYZBCDEFGHJKLMN'):\n",
" \"Return the old name for this sentence, if used before, or a new, unused name.\"\n",
" inverted = {defs[P]: P for P in defs}\n",
" if sentence in inverted:\n",
" return inverted[sentence] # Find previously-used name\n",
" else:\n",
" return next(P for P in names if P not in defs) # Use a new unused name\n",
" \n",
"def clean(text): \n",
" \"Remove redundant whitespace; handle curly apostrophe and trailing comma/period.\"\n",
" return ' '.join(text.split()).replace(\"’\", \"'\").rstrip('.').rstrip(',')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For example:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"('(P ⇒ ~Q)', {'P': 'loving you is wrong', 'Q': 'I do want to be right'})"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"match_rule(\"If loving you is wrong, I don't want to be right\",\n",
" Rule('{P} ⇒ {Q}', 'if {P}, {Q}'),\n",
" {})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here are some more test sentences and a top-level function to handle them:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"English: Polkadots and Moonbeams. \n",
"\n",
"Logic: (P ⋀ Q)\n",
"P: Polkadots\n",
"Q: Moonbeams\n",
"\n",
"English: If you liked it then you shoulda put a ring on it. \n",
"\n",
"Logic: (P ⇒ Q)\n",
"P: you liked it\n",
"Q: you shoulda put a ring on it\n",
"\n",
"English: If you build it, he will come. \n",
"\n",
"Logic: (P ⇒ Q)\n",
"P: you build it\n",
"Q: he will come\n",
"\n",
"English: It don't mean a thing, if it ain't got that swing. \n",
"\n",
"Logic: (~P ⇒ ~Q)\n",
"P: it is got that swing\n",
"Q: It do mean a thing\n",
"\n",
"English: If loving you is wrong, I don't want to be right. \n",
"\n",
"Logic: (P ⇒ ~Q)\n",
"P: loving you is wrong\n",
"Q: I do want to be right\n",
"\n",
"English: Should I stay or should I go. \n",
"\n",
"Logic: (P ⋁ Q)\n",
"P: Should I stay\n",
"Q: should I go\n",
"\n",
"English: I shouldn't go and I shouldn't not go. \n",
"\n",
"Logic: (~P ⋀ ~~P)\n",
"P: I should go\n",
"\n",
"English: If I fell in love with you, would you promise to be true and help me\n",
"understand. \n",
"\n",
"Logic: (P ⇒ (Q ⋀ R))\n",
"P: I fell in love with you\n",
"Q: would you promise to be true\n",
"R: help me understand\n",
"\n",
"English: I could while away the hours conferrin' with the flowers, consulting\n",
"with the rain and my head I'd be a scratchin' while my thoughts are busy\n",
"hatchin' if I only had a brain. \n",
"\n",
"Logic: (P ⇒ (Q ⋀ R))\n",
"P: I only had a brain\n",
"Q: I could while away the hours conferrin' with the flowers, consulting with the rain\n",
"R: my head I'd be a scratchin' while my thoughts are busy hatchin'\n",
"\n",
"English: There's a federal tax, and a state tax, and a city tax, and a street\n",
"tax, and a sewer tax. \n",
"\n",
"Logic: (P ⋀ (Q ⋀ (R ⋀ (S ⋀ T))))\n",
"P: There's a federal tax\n",
"Q: a state tax\n",
"R: a city tax\n",
"S: a street tax\n",
"T: a sewer tax\n",
"\n",
"English: A ham sandwich is better than nothing and nothing is better than\n",
"eternal happiness therefore a ham sandwich is better than eternal happiness. \n",
"\n",
"Logic: ((P ⋀ Q) ⇒ R)\n",
"P: A ham sandwich is better than nothing\n",
"Q: nothing is better than eternal happiness\n",
"R: a ham sandwich is better than eternal happiness\n",
"\n",
"English: If I were a carpenter and you were a lady, would you marry me anyway?\n",
"and would you have my baby. \n",
"\n",
"Logic: ((P ⋀ Q) ⇒ (R ⋀ S))\n",
"P: I were a carpenter\n",
"Q: you were a lady\n",
"R: would you marry me anyway?\n",
"S: would you have my baby\n",
"\n",
"English: Either Danny didn't come to the party or Virgil didn't come to the\n",
"party. \n",
"\n",
"Logic: (~P ⋁ ~Q)\n",
"P: Danny did come to the party\n",
"Q: Virgil did come to the party\n",
"\n",
"English: Either Wotan will triumph and Valhalla will be saved or else he won't\n",
"and Alberic will have the final word. \n",
"\n",
"Logic: ((P ⋀ Q) ⋁ (~R ⋀ S))\n",
"P: Wotan will triumph\n",
"Q: Valhalla will be saved\n",
"R: he will\n",
"S: Alberic will have the final word\n",
"\n",
"English: Sieglinde will survive, and either her son will gain the Ring and\n",
"Wotan's plan will be fulfilled or else Valhalla will be destroyed. \n",
"\n",
"Logic: (P ⋀ ((Q ⋀ R) ⋁ S))\n",
"P: Sieglinde will survive\n",
"Q: her son will gain the Ring\n",
"R: Wotan's plan will be fulfilled\n",
"S: Valhalla will be destroyed\n",
"\n",
"English: Wotan will intervene and cause Siegmund's death unless either Fricka\n",
"relents or Brunnhilde has her way. \n",
"\n",
"Logic: (~(R ⋁ S) ⇒ (P ⋀ Q))\n",
"P: Wotan will intervene\n",
"Q: cause Siegmund's death\n",
"R: Fricka relents\n",
"S: Brunnhilde has her way\n",
"\n",
"English: Figaro and Susanna will wed provided that either Antonio or Figaro pays\n",
"and Bartolo is satisfied or else Marcellina's contract is voided and the\n",
"Countess does not act rashly. \n",
"\n",
"Logic: ((((P ⋁ Q) ⋀ R) ⋁ (S ⋀ ~T)) ⇒ (U ⋀ V))\n",
"P: Antonio\n",
"Q: Figaro pays\n",
"R: Bartolo is satisfied\n",
"S: Marcellina's contract is voided\n",
"T: the Countess does act rashly\n",
"U: Figaro\n",
"V: Susanna will wed\n",
"\n",
"English: If the Kaiser neither prevents Bismarck from resigning nor supports the\n",
"Liberals, then the military will be in control and either Moltke's plan will be\n",
"executed or else the people will revolt and the Reich will not survive. \n",
"\n",
"Logic: ((~PQ ⋀ ~PR) ⇒ (S ⋀ (T ⋁ (U ⋀ ~V))))\n",
"P: the Kaiser\n",
"Q: prevents Bismarck from resigning\n",
"R: supports the Liberals\n",
"S: the military will be in control\n",
"T: Moltke's plan will be executed\n",
"U: the people will revolt\n",
"V: the Reich will survive\n"
]
}
],
"source": [
"sentences = '''\n",
"Polkadots and Moonbeams.\n",
"If you liked it then you shoulda put a ring on it.\n",
"If you build it, he will come.\n",
"It don't mean a thing, if it ain't got that swing.\n",
"If loving you is wrong, I don't want to be right.\n",
"Should I stay or should I go.\n",
"I shouldn't go and I shouldn't not go.\n",
"If I fell in love with you,\n",
" would you promise to be true\n",
" and help me understand.\n",
"I could while away the hours\n",
" conferrin' with the flowers,\n",
" consulting with the rain\n",
" and my head I'd be a scratchin'\n",
" while my thoughts are busy hatchin'\n",
" if I only had a brain.\n",
"There's a federal tax, and a state tax, and a city tax, and a street tax, and a sewer tax.\n",
"A ham sandwich is better than nothing \n",
" and nothing is better than eternal happiness\n",
" therefore a ham sandwich is better than eternal happiness.\n",
"If I were a carpenter\n",
" and you were a lady,\n",
" would you marry me anyway?\n",
" and would you have my baby.\n",
"Either Danny didn't come to the party or Virgil didn't come to the party.\n",
"Either Wotan will triumph and Valhalla will be saved or else he won't and Alberic will have \n",
" the final word.\n",
"Sieglinde will survive, and either her son will gain the Ring and Wotan’s plan \n",
" will be fulfilled or else Valhalla will be destroyed.\n",
"Wotan will intervene and cause Siegmund's death unless either Fricka relents \n",
" or Brunnhilde has her way.\n",
"Figaro and Susanna will wed provided that either Antonio or Figaro pays and Bartolo is satisfied \n",
" or else Marcellina’s contract is voided and the Countess does not act rashly.\n",
"If the Kaiser neither prevents Bismarck from resigning nor supports the Liberals, \n",
" then the military will be in control and either Moltke's plan will be executed \n",
" or else the people will revolt and the Reich will not survive'''.split('.')\n",
"\n",
"import textwrap\n",
"\n",
"def logic(sentences, width=80): \n",
" \"Match the rules against each sentence in text, and print each result.\"\n",
" for s in map(clean, sentences):\n",
" logic, defs = match_rules(s, rules, {})\n",
" print('\\n' + textwrap.fill('English: ' + s +'.', width), '\\n\\nLogic:', logic)\n",
" for P in sorted(defs):\n",
" print('{}: {}'.format(P, defs[P]))\n",
"\n",
"logic(sentences)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That looks pretty good! But far from perfect. Here are some errors:\n",
"\n",
"* `Should I stay` *etc.*:<br>questions are not poropositional statements.\n",
"\n",
"* `If I were a carpenter`:<br>doesn't handle modal logic.\n",
"\n",
"* `nothing is better`:<br>doesn't handle quantifiers.\n",
"\n",
"* `Either Wotan will triumph and Valhalla will be saved or else he won't`:<br>gets `'he will'` as one of the propositions, but better would be if that refered back to `'Wotan will triumph'`.\n",
"\n",
"* `Wotan will intervene and cause Siegmund's death`:<br>gets `\"cause Siegmund's death\"` as a proposition, but better would be `\"Wotan will cause Siegmund's death\"`.\n",
"\n",
"* `Figaro and Susanna will wed`:<br>gets `\"Figaro\"` and `\"Susanna will wed\"` as two separate propositions; this should really be one proposition. \n",
"\n",
"* `\"either Antonio or Figaro pays\"`:<br>gets `\"Antonio\"` as a proposition, but it should be `\"Antonio pays\"`.\n",
"\n",
"* `If the Kaiser neither prevents`:<br>uses the somewhat bogus propositions `PQ` and `PR`. This should be done in a cleaner way. The problem is the same as the previous problem with Antonio: I don't have a good way to attach the subject of a verb phrase to the multiple parts of the verb/object, when there are multiple parts.\n",
"\n",
"\n",
"\n",
"I'm sure more test sentences would reveal many more types of errors.\n",
"\n",
"There's also [a version](proplogic.py) of this program that is in Python 2 and uses only ASCII characters; if you have a Mac or Linux system you can download this as [`proplogic.py`](proplogic.py) and run it with the command `python proplogic.py`. Or you can run it [online](https://www.pythonanywhere.com/user/pnorvig/files/home/pnorvig/proplogic.py?edit)."
]
}
],
"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.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
+868
View File
@@ -0,0 +1,868 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Riddler Battle Royale\n",
"\n",
"\n",
"\n",
"> [538's *The Riddler* Asks](http://fivethirtyeight.com/features/the-battle-for-riddler-nation-round-2/): *In a distant, war-torn land, there are 10 castles. There are two warlords: you and your archenemy, with whom you’re competing to collect the most victory points. Each castle has its own strategic value for a would-be conqueror. Specifically, the castles are worth 1, 2, 3, …, 9, and 10 victory points. You and your enemy each have 100 soldiers to distribute, any way you like, to fight at any of the 10 castles. Whoever sends more soldiers to a given castle conquers that castle and wins its victory points. If you each send the same number of troops, you split the points. You don’t know what distribution of forces your enemy has chosen until the battles begin. Whoever wins the most points wins the war. Submit a plan distributing your 100 soldiers among the 10 castles.*\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# Load some useful modules\n",
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"import csv\n",
"import random\n",
"from collections import Counter\n",
"from statistics import mean"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's play with this and see if we can find a good solution. Some implementation choices:\n",
"* A `Plan` will be a tuple of 10 soldier counts (one for each castle).\n",
"* `castles` will hold the indexes of the castles. Note that index 0 is castle 1 (worth 1 point) and index 9 is castle 10 (worth 10 points).\n",
"* `half` is half the total number of points; if you get more than this you win.\n",
"* `plans` will hold a set of plans that were submitted in the previous contest.\n",
"* `play(A, B)` gives the single game reward for Plan A against Plan B: 1 if A wins, 0 if A loses, and 1/2 for a tie.\n",
"* `reward(a, b, payoff)` returns payoff, payoff/2, or 0, depending on whether `a` is bigger than `b`."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"Plan = tuple \n",
"castles = range(10)\n",
"half = 55/2 \n",
"plans = {Plan(map(int, row[:10])) \n",
" for row in csv.reader(open('battle_royale.csv'))}\n",
"\n",
"def play(A, B): \n",
" \"Play Plan A against Plan B and return a reward (0, 1/2, or 1).\"\n",
" A_points = sum(reward(A[c], B[c], c + 1) for c in castles)\n",
" return reward(A_points, half) \n",
"\n",
"def reward(a, b, payoff=1): return (payoff if a > b else payoff / 2 if a == b else 0) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Some tests:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"assert reward(6, 5, 9) == 9 # 6 soldiers defeat 5, winning all 9 of the castle's points\n",
"assert reward(6, 6, 8) == 4 # A tie on an 8-point castle is worth 4 points\n",
"assert reward(6, 7, 7) == 0 # No points for a loss\n",
"assert reward(30, 25) == 1 # 30 victory points beats 25\n",
"\n",
"assert len(plans) == 1202\n",
"\n",
"assert play((26, 5, 5, 5, 6, 7, 26, 0, 0, 0),\n",
" (25, 0, 0, 0, 0, 0, 0, 25, 25, 25)) == 1 # A wins game\n",
"\n",
"assert play((26, 5, 5, 5, 6, 7, 26, 0, 0, 0),\n",
" (0, 25, 0, 0, 0, 0, 0, 25, 25, 25)) == 0 # B wins game\n",
"\n",
"assert play((25, 5, 5, 5, 6, 7, 26, 0, 0, 0),\n",
" (25, 0, 0, 0, 0, 0, 0, 25, 25, 25)) == 1/2 # Tie game"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's run a tournament, playing each plan against every other, and returning a list of `[(plan, mean_game_points),...]`. I will also define `show` to pretty-print these results and display a histogram:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def tournament(plans):\n",
" \"Play each plan against each other; return a sorted list of [(plan: mean_points)]\"\n",
" rankdict = {A: mean_points(A, plans) for A in plans}\n",
" return Counter(rankdict).most_common()\n",
"\n",
"def mean_points(A, opponents): \n",
" \"Mean points for A playing against all opponents (but not against itself).\"\n",
" return mean(play(A, B) for B in opponents if B is not A)\n",
"\n",
"def show(rankings, n=10): \n",
" \"Pretty-print the n best plans, and display a histogram of all plans.\"\n",
" print('Top', n, 'of', len(rankings), 'plans:')\n",
" for (plan, points) in rankings[:n]:\n",
" print(pplan(plan), pct(points))\n",
" plt.hist([s for (p, s) in rankings], bins=20)\n",
" \n",
"def pct(x): return '{:6.1%}'.format(x)\n",
"def pplan(plan): return '(' + ', '.join('{:2}'.format(c) for c in plan) + ')'"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[((0, 25, 0, 0, 0, 0, 0, 25, 25, 25), 1),\n",
" ((26, 5, 5, 5, 6, 7, 26, 0, 0, 0), 0.5),\n",
" ((25, 0, 0, 0, 0, 0, 0, 25, 25, 25), 0)]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# This is what the result of a tournament looks like:\n",
"tournament({(26, 5, 5, 5, 6, 7, 26, 0, 0, 0),\n",
" (25, 0, 0, 0, 0, 0, 0, 25, 25, 25),\n",
" (0, 25, 0, 0, 0, 0, 0, 25, 25, 25)})"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Top 10 of 1202 plans:\n",
"( 0, 3, 4, 7, 16, 24, 4, 34, 4, 4) 85.6%\n",
"( 5, 7, 9, 11, 15, 21, 25, 2, 2, 3) 84.1%\n",
"( 3, 5, 8, 10, 13, 1, 26, 30, 2, 2) 83.3%\n",
"( 2, 2, 6, 12, 2, 18, 24, 30, 2, 2) 83.3%\n",
"( 2, 8, 2, 2, 10, 18, 26, 26, 3, 3) 83.2%\n",
"( 3, 6, 7, 9, 11, 2, 27, 31, 2, 2) 83.2%\n",
"( 1, 1, 1, 5, 11, 16, 28, 29, 3, 5) 82.8%\n",
"( 1, 3, 1, 1, 17, 20, 21, 30, 3, 3) 82.6%\n",
"( 3, 6, 10, 12, 16, 21, 26, 2, 2, 2) 82.4%\n",
"( 6, 6, 6, 11, 20, 21, 21, 3, 3, 3) 82.2%\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x10b2c3a20>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# A tournament with all 1202 plans:\n",
"rankings = tournament(plans)\n",
"show(rankings)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It looks like there are a few really bad plans in there. Let's just keep the top 1000 plans (out of 1202), and re-run the rankings:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Top 10 of 1000 plans:\n",
"( 0, 3, 4, 7, 16, 24, 4, 34, 4, 4) 87.4%\n",
"( 5, 5, 5, 5, 5, 5, 27, 30, 6, 7) 84.8%\n",
"( 5, 5, 5, 5, 5, 5, 30, 30, 5, 5) 84.2%\n",
"( 3, 3, 5, 5, 7, 7, 30, 30, 5, 5) 84.1%\n",
"( 1, 2, 3, 4, 6, 16, 25, 33, 4, 6) 82.5%\n",
"( 2, 2, 2, 5, 5, 26, 26, 26, 3, 3) 82.4%\n",
"( 1, 1, 1, 5, 11, 16, 28, 29, 3, 5) 82.0%\n",
"( 0, 1, 3, 3, 11, 18, 25, 33, 3, 3) 82.0%\n",
"( 5, 7, 9, 11, 15, 21, 25, 2, 2, 3) 81.7%\n",
"( 0, 0, 5, 5, 25, 3, 25, 3, 31, 3) 81.5%\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x113439358>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plans = {A for (A, _) in rankings[:1000]}\n",
"rankings = tournament(plans)\n",
"show(rankings)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The top 10 plans are still winning over 80%, and the top plan remains `(0, 3, 4, 7, 16, 24, 4, 34, 4, 4)`. This is an interesting plan: it places most of the soldiers on castles 4+5+6+8, which totals only 23 points, so it needs to pick up 5 more points from the other castles (that have mostly 4 soldiers attacking each one). Is this a good strategy? Where should we optiomally allocate soldiers? \n",
"\n",
"To gain some insight, I'll create a plot with 10 curves, one for each castle. Each curve maps the number of soldiers sent to the castle (on the x-axis) to the expected points won (against the 1000 plans) on the y-axis:\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x10adc7160>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def plotter(plans, X=range(41)):\n",
" X = list(X)\n",
" def mean_reward(c, s): return mean(reward(s, p[c], c+1) for p in plans)\n",
" for c in range(10):\n",
" plt.plot(X, [mean_reward(c, s) for s in X], '.-')\n",
" plt.xlabel('Number of soldiers (on each of the ten castles)')\n",
" plt.ylabel('Expected points won')\n",
" plt.grid()\n",
" \n",
"plotter(plans)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For example, this says that for castle 10 (the orange line at top), there is a big gain in expected return as we increase from 0 to 4 soldiers, and after that the gains are relatively less steep. This plot is interesting, but I can't see how to directly read off a best plan from it.\n",
"\n",
"## Hillclimbing\n",
"\n",
"Instead I'll see if I can improve the existing plans, using a simple *hillclimbing* strategy: Take a Plan A, and change it by randomly moving some soldiers from one castle to another. If that yields more `mean_points`, then keep the updated plan, otherwise discard it. Repeat."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def hillclimb(A, plans=plans, steps=1000):\n",
" \"Try to improve Plan A, repeat `steps` times; return new plan and total.\"\n",
" m = mean_points(A, plans)\n",
" for _ in range(steps):\n",
" B = mutate(A)\n",
" m, A = max((m, A), \n",
" (mean_points(B, plans), B))\n",
" return A, m\n",
"\n",
"def mutate(plan):\n",
" \"Return a new plan that is a slight mutation.\"\n",
" plan = list(plan) # So we can modify it.\n",
" i, j = random.sample(castles, 2)\n",
" plan[i], plan[j] = random_split(plan[i] + plan[j])\n",
" return Plan(plan)\n",
"\n",
"def random_split(n):\n",
" \"Split the integer n into two integers that sum to n.\"\n",
" r = random.randint(0, n)\n",
" return r, n - r"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's see how well this works. Remember, the best plan so far had a score of `87.4%`. Can we improve on that?"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((0, 3, 4, 15, 7, 21, 3, 31, 3, 13), 0.916)"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hillclimb((0, 3, 4, 7, 16, 24, 4, 34, 4, 4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We got an improvement. Let's see what happens if we start with other plans:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((0, 5, 5, 12, 7, 22, 3, 31, 3, 12), 0.912)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hillclimb((10, 10, 10, 10, 10, 10, 10, 10, 10, 10))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((1, 3, 4, 14, 7, 21, 3, 31, 4, 12), 0.9105)"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hillclimb((0, 1, 2, 3, 4, 18, 18, 18, 18, 18))"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((0, 5, 5, 15, 7, 21, 3, 31, 6, 7), 0.9065)"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hillclimb((2, 3, 5, 5, 5, 20, 20, 20, 10, 10))"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((0, 2, 6, 7, 18, 1, 26, 2, 32, 6), 0.8855)"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hillclimb((0, 0, 5, 5, 25, 3, 25, 3, 31, 3))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What if we hillclimb 20 times longer?"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((0, 5, 6, 14, 9, 21, 3, 31, 3, 8), 0.9065)"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hillclimb((0, 3, 4, 7, 16, 24, 4, 34, 4, 4), steps=20000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Opponent modelling\n",
"\n",
"To have a chance of winning the second round of this contest, we have to predict what the other entries will be like. Nobody knows for sure, but I can hypothesize that the entries will be slightly better than the first round, and try to approximate that by hillclimbing from each of the first-round plans for a small number of steps:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"def hillclimbers(plans, steps=100):\n",
" \"Return a sorted list of [(improved_plan, mean_points), ...]\"\n",
" pairs = {hillclimb(plan, plans, steps) for plan in plans}\n",
" return sorted(pairs, key=lambda pair: pair[1], reverse=True)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[((29, 19, 2, 10, 6, 5, 3, 1, 25, 0), 1),\n",
" ((11, 9, 18, 3, 1, 11, 0, 27, 0, 0), 1),\n",
" ((0, 25, 0, 0, 0, 0, 0, 25, 25, 25), 1)]"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# For example:\n",
"hillclimbers({(26, 5, 5, 5, 6, 7, 26, 0, 0, 0),\n",
" (25, 0, 0, 0, 0, 0, 0, 25, 25, 25),\n",
" (0, 25, 0, 0, 0, 0, 0, 25, 25, 25)})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I will define `plans2` (and `rankings2`) to be my estimate of the entries for round 2:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 6min 11s, sys: 3.21 s, total: 6min 14s\n",
"Wall time: 6min 17s\n",
"Top 10 of 1000 plans:\n",
"( 1, 4, 5, 15, 6, 21, 3, 31, 3, 11) 90.8%\n",
"( 0, 3, 5, 14, 7, 21, 3, 30, 4, 13) 90.6%\n",
"( 0, 4, 6, 15, 9, 21, 4, 31, 5, 5) 90.2%\n",
"( 2, 4, 3, 13, 5, 22, 3, 32, 4, 12) 90.1%\n",
"( 0, 3, 5, 15, 8, 21, 4, 32, 6, 6) 90.0%\n",
"( 0, 3, 5, 15, 6, 24, 3, 31, 5, 8) 90.0%\n",
"( 0, 3, 6, 13, 6, 21, 5, 30, 4, 12) 90.0%\n",
"( 3, 4, 5, 15, 7, 21, 2, 31, 6, 6) 89.9%\n",
"( 2, 3, 3, 13, 6, 21, 3, 30, 5, 14) 89.8%\n",
"( 0, 2, 2, 12, 2, 23, 4, 31, 3, 21) 89.8%\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x113a56908>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%time rankings2 = hillclimbers(plans)\n",
"plans2 = {A for (A, _) in rankings2}\n",
"show(rankings2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Even though we only took 100 steps, the `plans2` plans are greatly improved: Almost all of them defeat 75% or more of the first-round `plans`. The top 10 plans are all very similar, targeting castles 4+6+8+10 (for 28 points), but reserving 20 or soldiers to spread among the other castles. Let's look more carefully at every 40th plan, plus the last one:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"( 1, 4, 5, 15, 6, 21, 3, 31, 3, 11) 90.8%\n",
"( 0, 6, 3, 13, 3, 22, 2, 32, 4, 15) 88.9%\n",
"( 1, 3, 6, 13, 9, 22, 1, 30, 4, 11) 88.3%\n",
"( 2, 2, 1, 13, 3, 21, 2, 32, 3, 21) 87.9%\n",
"( 0, 2, 5, 5, 15, 2, 28, 31, 5, 7) 87.6%\n",
"( 2, 2, 4, 14, 9, 1, 27, 30, 6, 5) 87.3%\n",
"( 3, 2, 3, 12, 3, 28, 3, 32, 6, 8) 87.0%\n",
"( 1, 3, 2, 5, 18, 3, 26, 3, 33, 6) 86.7%\n",
"( 0, 4, 4, 6, 15, 3, 29, 30, 5, 4) 86.5%\n",
"( 5, 5, 4, 5, 13, 22, 2, 29, 3, 12) 86.2%\n",
"( 5, 6, 5, 6, 16, 24, 26, 1, 5, 6) 85.9%\n",
"( 0, 2, 5, 15, 8, 3, 20, 36, 6, 5) 85.7%\n",
"( 5, 1, 6, 12, 2, 24, 5, 32, 4, 9) 85.4%\n",
"( 2, 5, 8, 16, 11, 3, 2, 36, 5, 12) 85.1%\n",
"( 2, 7, 3, 15, 14, 2, 3, 31, 9, 12) 84.8%\n",
"( 6, 5, 8, 6, 7, 22, 30, 3, 7, 6) 84.6%\n",
"( 5, 3, 3, 5, 3, 21, 26, 26, 3, 5) 84.4%\n",
"( 0, 2, 4, 13, 2, 22, 17, 33, 2, 5) 84.0%\n",
"( 0, 7, 12, 6, 8, 21, 2, 29, 12, 3) 83.5%\n",
"( 5, 5, 4, 13, 18, 2, 26, 2, 6, 19) 83.0%\n",
"( 5, 6, 3, 15, 17, 24, 4, 2, 5, 19) 82.5%\n",
"( 5, 6, 5, 9, 6, 22, 34, 1, 7, 5) 81.8%\n",
"( 4, 3, 7, 17, 17, 22, 3, 3, 5, 19) 81.0%\n",
"( 0, 1, 2, 11, 12, 13, 28, 27, 2, 4) 80.4%\n",
"( 5, 6, 13, 16, 15, 26, 2, 4, 7, 6) 78.9%\n",
"( 0, 0, 1, 13, 0, 1, 24, 21, 36, 4) 70.3%\n"
]
}
],
"source": [
"for (p, m) in rankings2[::40] + [rankings2[-1]]:\n",
" print(pplan(p), pct(m))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see a wider variety in plans as we go farther down the rankings. Now for the plot:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x113b38a90>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plotter(plans2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see that many castles (e.g. 9 (green), 8 (blue), 7 (black), 6 (yellowish)) have two plateaus. Castle 7 (black) has a plateau at 3.5 points for 6 to 20 soldiers (suggesting that 6 soldiers is a good investment and 20 soldiers a bad investment), and then another plateau at 7 points for everything above 30 soldiers.\n",
"\n",
"Now that we have an estimate of the opponents, we can use `hillclimbers` to try to find a plan that does well against all the others:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 5min 40s, sys: 1 s, total: 5min 41s\n",
"Wall time: 5min 42s\n",
"Top 10 of 1000 plans:\n",
"( 3, 8, 10, 18, 21, 3, 5, 6, 10, 16) 99.9%\n",
"( 1, 9, 10, 17, 21, 6, 4, 6, 9, 17) 99.9%\n",
"( 1, 8, 10, 18, 21, 4, 4, 6, 11, 17) 99.9%\n",
"( 0, 10, 10, 17, 20, 4, 5, 6, 7, 21) 99.9%\n",
"( 2, 11, 1, 16, 18, 7, 6, 6, 8, 25) 99.8%\n",
"( 1, 8, 11, 19, 20, 4, 6, 5, 7, 19) 99.8%\n",
"( 0, 1, 11, 15, 18, 7, 6, 5, 13, 24) 99.8%\n",
"( 2, 10, 1, 17, 18, 9, 5, 6, 8, 24) 99.8%\n",
"( 1, 9, 10, 17, 19, 4, 6, 6, 9, 19) 99.8%\n",
"( 0, 2, 11, 18, 21, 4, 6, 8, 8, 22) 99.8%\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAX8AAAEACAYAAABbMHZzAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAFDJJREFUeJzt3XGsnfV93/H3xxhTCInLmmKnOMVUMAKVMpIJJxosO1M3\nQqiEUSYxkmkNYdki0Swof1SxI1W+qSolREqqTgl/bKSJ6UDERekwXQsOci5TKsWkBYMTe8wbMXGc\n+HYj6RqENNnhuz/OY3O52L7n3nPOvef6935Jj/w7z/P8zvmec/18znN+z3nOk6pCktSWVctdgCRp\n6Rn+ktQgw1+SGmT4S1KDDH9JapDhL0kNGjj8k6xK8lSSnd3ti5LsSvJckseSrJ217tYkB5McSHLD\nOAqXJC3eQvb87wL2z7q9BXi8qq4EdgNbAZJcDdwKXAW8D7gnSUZTriRpFAYK/yQbgJuAe2fN3gxs\n79rbgVu69s3Ag1V1vKoOAQeBTSOpVpI0EoPu+f8B8DvA7NOB11XVDEBVHQUu7uZfAhyetd6Rbp4k\naULMG/5JfhOYqaq9wJmGb/ydCElaIVYPsM51wM1JbgLOB96Y5I+Bo0nWVdVMkvXA33TrHwHeOqv/\nhm7eayTxzUKSFqGqhj6OOu+ef1V9qqp+tap+DbgN2F1V/xp4BLi9W+1DwMNdeydwW5I1SS4DLgee\nPM19T/y0bdu2Za/BOq1zJde5EmpcSXWOyiB7/qfzWWBHkjuAF+h/w4eq2p9kB/1vBh0D7qxRVixJ\nGtqCwr+qngCe6No/Af7Zadb7DPCZoauTJI2FZ/jOo9frLXcJA7HO0bLO0VkJNcLKqXNUslwjMkkc\nDZKkBUpCLcUBX0nS2cfwl6QGGf6S1CDDX5IaZPhLUoMMf0lqkOEvSQ0y/CWpQYa/JDXI8JekBhn+\nktQgw1+SGmT4S1KDDH9JapDhL0kNMvwlqUGGvyQ1aN7wT3Jekj1Jnk6yL8m2bv62JD9M8lQ33Tir\nz9YkB5McSHLDOJ+AJGnhBrqMY5ILqurlJOcAfwl8HHgf8LOq+sKcda8CHgCuBTYAjwNXzL1mo5dx\nlLQSrV+/kZmZFxbVd926Szl69NBQj7+kl3Gsqpe75nnAauBEap+qgM3Ag1V1vKoOAQeBTUPWKUkT\noR/8tahpsW8a4zBQ+CdZleRp4Cjwjar6TrfoY0n2Jrk3ydpu3iXA4Vndj3TzJEkTYtA9/1eq6h30\nh3E2JbkauAf4taq6hv6bwufHV6YkaZRWL2Tlqvq7JNPAjXPG+v8T8EjXPgK8ddayDd2815mamjrZ\n7vV69Hq9hZQjSWe96elppqenR36/8x7wTfJm4FhV/d8k5wOPAZ8Fnqqqo906nwCuraoPdp8K7gfe\nRX+45xt4wFfSWSIJrx72XHBvhs29UR3wHWTP/y3A9iSr6A8Tfa2q/jzJfUmuAV4BDgEfBaiq/Ul2\nAPuBY8CdprwkTZaBvuo5lgd2z1/SCnS27Pl7hq8kNcjwl6QGGf6S1CDDX5IaZPhLUoMMf0lqkOEv\nSQ0y/CWpQYa/JDXI8JekBhn+ktQgw1+SGmT4S1KDDH9JapDhL0kNMvwlqUGGvyQ1yPCXpAYZ/pLU\noHnDP8l5SfYkeTrJviTbuvkXJdmV5LkkjyVZO6vP1iQHkxxIcsM4n4AkaeEGuoB7kguq6uUk5wB/\nCXwc+BfAi1X1uSSfBC6qqi1JrgbuB64FNgCPA1fMvVq7F3CXtBI1dQH3qnq5a54HrKb/zDcD27v5\n24FbuvbNwINVdbyqDgEHgU3DFipJGp2Bwj/JqiRPA0eBb1TVd4B1VTUDUFVHgYu71S8BDs/qfqSb\nJ0maEKsHWamqXgHekeRNwJ8m+XVe/7lnwZ9lpqamTrZ7vR69Xm+hdyFJZ7Xp6Wmmp6dHfr8Djfm/\npkPyu8DLwEeAXlXNJFkPfLOqrkqyBaiqurtb/1FgW1XtmXM/jvlLWnGaGfNP8uYT3+RJcj7wz4ED\nwE7g9m61DwEPd+2dwG1J1iS5DLgceHLYQiVJozPIsM9bgO1JVtF/s/haVf15km8DO5LcAbwA3ApQ\nVfuT7AD2A8eAO93Fl6TJsuBhn5E9sMM+klagZoZ9JElnH8Nfkhpk+EtSgwx/SWqQ4S9JDTL8JalB\nhr8kNcjwl6QGGf6S1CDDX5KWzHkkWdS0fv3GkVbizztI0gIM+/MOw/40hD/vIElaNMNfkhpk+EtS\ngwx/SWqQ4S9JDTL8JalBhr8kNcjwl6QGzRv+STYk2Z3ke0n2Jfn33fxtSX6Y5KluunFWn61JDiY5\nkOSGcT4BSdLCzXuGb5L1wPqq2pvkQuCvgc3AvwR+VlVfmLP+VcADwLXABuBx4Iq5p/N6hq+klaiZ\nM3yr6mhV7e3aLwEHgEtOVvN6m4EHq+p4VR0CDgKbhi1UkjQ6CxrzT7IRuAbY0836WJK9Se5Nsrab\ndwlweFa3I7z6ZiFJmgCrB12xG/J5CLirql5Kcg/we1VVSX4f+DzwkYU8+NTU1Ml2r9ej1+stpLsk\nNWC6m16bmcMa6Fc9k6wG/gz4i6r6w1MsvxR4pKrenmQLUFV1d7fsUWBbVe2Z08cxf0krTjNj/p0/\nAvbPDv7uQPAJ7we+27V3ArclWZPkMuBy4MlhC5Ukjc68wz5JrgP+FbAvydP037Y+BXwwyTXAK8Ah\n4KMAVbU/yQ5gP3AMuNNdfEmaLF7MRZIWoLVhH0nSWcTwl6QGGf6S1CDDX5IaZPhLUoMMf0lqkOEv\nSQ0y/CWpQYa/JDXI8JekBhn+ktQgw1+SGmT4S1KDDH9JapDhL0kNMvwlqUGGvyQ1yPCXpAYZ/pLU\noHnDP8mGJLuTfC/JviQf7+ZflGRXkueSPJZk7aw+W5McTHIgyQ3jfAKSpIWb9wLuSdYD66tqb5IL\ngb8GNgMfBl6sqs8l+SRwUVVtSXI1cD9wLbABeBy4Yu7V2r2Au6SVqJkLuFfV0ara27VfAg7QD/XN\nwPZute3ALV37ZuDBqjpeVYeAg8CmYQuVJI3Ogsb8k2wErgG+Dayrqhnov0EAF3erXQIcntXtSDdP\nkjQhVg+6Yjfk8xBwV1W9lGTuZ5cFf5aZmpo62e71evR6vYXehSSd5aa76bWZOax5x/wBkqwG/gz4\ni6r6w27eAaBXVTPdcYFvVtVVSbYAVVV3d+s9Cmyrqj1z7tMxf0krTjNj/p0/AvafCP7OTuD2rv0h\n4OFZ829LsibJZcDlwJPDFipJGp1Bvu1zHfDfgH3037IK+BT9QN8BvBV4Abi1qv6267MV+DfAMfrD\nRLtOcb/u+Utacc6WPf+Bhn3GwfCXtBKdLeHvGb6S1CDDX5IaZPhLUoMMf0lqkOEvSQ0y/CWpQYa/\nJDXI8JekBhn+ktQgw1+SGmT4S1KDDH9JapDhL0kNMvwlqUGGvyQ1yPCXpAYZ/pLUIMNfkhpk+EtS\ng+YN/yRfTjKT5NlZ87Yl+WGSp7rpxlnLtiY5mORAkhvGVbgkLdb69RtJsqjpbDHvBdyTXA+8BNxX\nVW/v5m0DflZVX5iz7lXAA8C1wAbgceCKU12p3Qu4S1ouy3kR9hVzAfeq+hbw01NW8nqbgQer6nhV\nHQIOApuGqlCSNHLDjPl/LMneJPcmWdvNuwQ4PGudI908SdIEWb3IfvcAv1dVleT3gc8DH1nonUxN\nTZ1s93o9er3eIsuRpLPVdDe9NjOHNe+YP0CSS4FHToz5n25Zki1AVdXd3bJHgW1VtecU/Rzzl7Qs\nHPMffNgnzBrjT7J+1rL3A9/t2juB25KsSXIZcDnw5LBFSpJGa95hnyQPAD3gl5L8ANgG/NMk1wCv\nAIeAjwJU1f4kO4D9wDHgTnfvJWnyDDTsM5YHdthH0jJx2MczfCWpSYa/JDXI8JekBhn+ktQgw1+S\nGmT4S1KDDH9JapDhL0kNMvwlqUGGvyQ1yPCXpAYZ/pLUIMNfkhpk+EtSgwx/SWqQ4S9JDTL8JalB\nhr8kNcjwl6QGzRv+Sb6cZCbJs7PmXZRkV5LnkjyWZO2sZVuTHExyIMkN4ypckrR4g+z5fwV475x5\nW4DHq+pKYDewFSDJ1cCtwFXA+4B70r9SsiRpgswb/lX1LeCnc2ZvBrZ37e3ALV37ZuDBqjpeVYeA\ng8Cm0ZQqSRqVxY75X1xVMwBVdRS4uJt/CXB41npHunmSpAmyekT3U4vpNDU1dbLd6/Xo9XojKkeS\nzhbT3fTazBxWqubP7SSXAo9U1du72weAXlXNJFkPfLOqrkqyBaiqurtb71FgW1XtOcV91iCPLUmj\n1j8Uudj8Wb6+VUUSqmroY6mDDvukm07YCdzetT8EPDxr/m1J1iS5DLgceHLYIiVJozXvsE+SB4Ae\n8EtJfgBsAz4L/EmSO4AX6H/Dh6ran2QHsB84Btzp7r0kTZ6Bhn3G8sAO+0haJg77eIavJDXJ8Jek\nBhn+ktQgw1+SGmT4S1KDDH9JapDhL0kNMvwlqUGGv6QVZ/36jSRZ9CTP8JW0Ag13hi4s51m6nuEr\nSVo2hr8kNcjwl6QGGf6S1CDDX5IaZPhLUoMMf0lqkOEvSQ0aKvyTHEryTJKnkzzZzbsoya4kzyV5\nLMna0ZQq6WwyzFm6Gt5QZ/gmeR74h1X101nz7gZerKrPJfkkcFFVbTlFX8/wlRq2fNfRHba/Z/j2\nq3n9fWwGtnft7cAtQz6GJGnEhg3/Ar6R5DtJPtLNW1dVMwBVdRS4eMjHkCSN2Ooh+19XVT9O8svA\nriTP8frPNI7tSNKEGSr8q+rH3b//O8l/ATYBM0nWVdVMkvXA35yu/9TU1Ml2r9ej1+sNU44knYWm\nu+m1mTmsRR/wTXIBsKqqXkryBmAX8GngN4CfVNXdHvCVdDoe8F1431Ee8B1mz38d8Line truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x113d16588>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%time rankings3 = hillclimbers(plans2)\n",
"show(rankings3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can try even harder to improve the champ:"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((3, 9, 10, 17, 21, 3, 5, 6, 7, 19), 1)"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"champ, _ = rankings3[0]\n",
"hillclimb(champ, plans2, 10000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here are some champion plans from previous runs of this notebook:"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [],
"source": [
"champs = {\n",
" (0, 1, 3, 16, 20, 3, 4, 5, 32, 16),\n",
" (0, 1, 9, 16, 15, 24, 5, 5, 8, 17),\n",
" (0, 1, 9, 16, 16, 24, 5, 5, 7, 17),\n",
" (0, 2, 9, 16, 15, 24, 5, 5, 8, 16),\n",
" (0, 2, 9, 16, 15, 25, 5, 4, 7, 17),\n",
" (0, 3, 4, 7, 16, 24, 4, 34, 4, 4),\n",
" (0, 3, 5, 6, 20, 4, 4, 33, 8, 17),\n",
" (0, 4, 5, 7, 20, 4, 4, 33, 7, 16),\n",
" (0, 4, 6, 7, 19, 4, 4, 31, 8, 17),\n",
" (0, 4, 12, 18, 21, 7, 6, 4, 8, 20),\n",
" (0, 4, 12, 19, 25, 4, 5, 6, 8, 17),\n",
" (0, 5, 6, 7, 18, 4, 5, 32, 7, 16),\n",
" (0, 5, 7, 3, 18, 4, 4, 34, 8, 17),\n",
" (1, 2, 9, 16, 15, 24, 5, 4, 7, 17),\n",
" (1, 2, 9, 16, 15, 24, 5, 4, 8, 16),\n",
" (1, 2, 11, 16, 15, 24, 5, 4, 7, 15),\n",
" (1, 3, 14, 18, 24, 4, 5, 6, 8, 17),\n",
" (1, 6, 3, 16, 16, 24, 5, 5, 7, 17),\n",
" (2, 3, 7, 16, 16, 25, 5, 5, 8, 13),\n",
" (2, 3, 8, 16, 12, 25, 5, 4, 8, 17),\n",
" (2, 3, 8, 16, 15, 24, 5, 4, 7, 16),\n",
" (2, 3, 8, 16, 15, 25, 4, 5, 8, 14),\n",
" (2, 3, 8, 16, 16, 24, 5, 5, 8, 13),\n",
" (2, 3, 9, 15, 12, 25, 4, 5, 8, 17),\n",
" (2, 3, 9, 16, 12, 24, 5, 5, 8, 16),\n",
" (2, 4, 12, 18, 24, 4, 6, 5, 8, 17),\n",
" (3, 3, 7, 16, 16, 24, 5, 5, 8, 13),\n",
" (3, 3, 8, 16, 12, 25, 4, 4, 8, 17),\n",
" (3, 3, 8, 16, 15, 25, 5, 4, 7, 14),\n",
" (3, 4, 12, 18, 23, 4, 6, 5, 8, 17),\n",
" (3, 4, 15, 18, 23, 4, 5, 6, 8, 14),\n",
" (3, 5, 7, 16, 5, 4, 5, 34, 7, 14),\n",
" (3, 6, 13, 17, 23, 4, 6, 5, 8, 15),\n",
" (4, 3, 12, 18, 23, 4, 5, 6, 8, 17),\n",
" (4, 5, 3, 15, 11, 23, 5, 5, 10, 19),\n",
" (4, 6, 3, 16, 14, 25, 5, 5, 8, 14),\n",
" (4, 6, 3, 16, 16, 24, 5, 5, 7, 14),\n",
" (4, 6, 3, 16, 16, 24, 5, 5, 8, 13),\n",
" (5, 3, 12, 17, 23, 4, 5, 6, 8, 17),\n",
" (5, 5, 3, 16, 12, 25, 4, 5, 8, 17),\n",
" (5, 6, 3, 16, 16, 24, 5, 5, 7, 13),\n",
" (5, 6, 7, 3, 21, 4, 27, 5, 8, 14),\n",
" (5, 6, 8, 3, 18, 4, 27, 5, 8, 16),\n",
" (5, 6, 8, 3, 20, 4, 27, 5, 8, 14),\n",
" (5, 6, 8, 3, 21, 4, 27, 5, 8, 13)}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can evaluate each of them against the original `plans`, against the improved `plans2`, against their fellow champs, and against all of those put together:"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Plan plans plans2 champs all\n",
"( 0, 5, 7, 3, 18, 4, 4, 34, 8, 17) 85.5% 96.0% 68.5% 90.2%\n",
"( 0, 4, 6, 7, 19, 4, 4, 31, 8, 17) 84.7% 95.0% 63.0% 89.2%\n",
"( 0, 1, 3, 16, 20, 3, 4, 5, 32, 16) 85.6% 95.2% 31.5% 89.0%\n",
"( 0, 3, 5, 6, 20, 4, 4, 33, 8, 17) 84.1% 95.2% 60.9% 89.0%\n",
"( 0, 5, 6, 7, 18, 4, 5, 32, 7, 16) 84.3% 96.3% 28.3% 88.9%\n",
"( 3, 5, 7, 16, 5, 4, 5, 34, 7, 14) 85.2% 95.7% 18.5% 88.8%\n",
"( 5, 6, 8, 3, 18, 4, 27, 5, 8, 16) 81.8% 96.4% 64.1% 88.6%\n",
"( 0, 4, 5, 7, 20, 4, 4, 33, 7, 16) 84.7% 95.0% 18.5% 88.2%\n",
"( 5, 6, 8, 3, 20, 4, 27, 5, 8, 14) 82.0% 96.2% 48.9% 88.2%\n",
"( 0, 1, 9, 16, 15, 24, 5, 5, 8, 17) 78.2% 98.6% 72.8% 88.0%\n",
"( 5, 6, 7, 3, 21, 4, 27, 5, 8, 14) 81.8% 96.0% 51.1% 88.0%\n",
"( 0, 1, 9, 16, 16, 24, 5, 5, 7, 17) 79.1% 98.5% 46.7% 87.8%\n",
"( 5, 6, 8, 3, 21, 4, 27, 5, 8, 13) 82.0% 95.2% 45.7% 87.6%\n",
"( 2, 3, 9, 15, 12, 25, 4, 5, 8, 17) 78.5% 97.9% 58.7% 87.5%\n",
"( 4, 5, 3, 15, 11, 23, 5, 5, 10, 19) 76.8% 97.8% 97.8% 87.5%\n",
"( 2, 3, 8, 16, 12, 25, 5, 4, 8, 17) 78.2% 98.1% 57.6% 87.5%\n",
"( 2, 3, 8, 16, 15, 25, 4, 5, 8, 14) 79.7% 97.9% 31.5% 87.5%\n",
"( 0, 2, 9, 16, 15, 24, 5, 5, 8, 16) 78.5% 98.2% 50.0% 87.4%\n",
"( 2, 3, 7, 16, 16, 25, 5, 5, 8, 13) 79.3% 97.5% 44.6% 87.4%\n",
"( 4, 6, 3, 16, 14, 25, 5, 5, 8, 14) 79.0% 97.4% 48.9% 87.3%\n",
"( 5, 5, 3, 16, 12, 25, 4, 5, 8, 17) 78.1% 97.8% 60.9% 87.3%\n",
"( 4, 6, 3, 16, 16, 24, 5, 5, 7, 14) 80.3% 97.2% 21.7% 87.3%\n",
"( 2, 3, 8, 16, 15, 24, 5, 4, 7, 16) 80.2% 97.8% 10.9% 87.2%\n",
"( 1, 2, 9, 16, 15, 24, 5, 4, 7, 17) 79.8% 97.8% 19.6% 87.2%\n",
"( 0, 2, 9, 16, 15, 25, 5, 4, 7, 17) 79.1% 97.9% 31.5% 87.2%\n",
"( 2, 3, 8, 16, 16, 24, 5, 5, 8, 13) 79.5% 97.5% 29.3% 87.2%\n",
"( 2, 3, 9, 16, 12, 24, 5, 5, 8, 16) 78.0% 98.2% 45.7% 87.1%\n",
"( 3, 3, 8, 16, 15, 25, 5, 4, 7, 14) 80.3% 97.6% 6.5% 87.1%\n",
"( 1, 2, 9, 16, 15, 24, 5, 4, 8, 16) 79.2% 97.6% 27.2% 87.0%\n",
"( 3, 3, 7, 16, 16, 24, 5, 5, 8, 13) 79.8% 97.1% 26.1% 87.0%\n",
"( 4, 6, 3, 16, 16, 24, 5, 5, 8, 13) 80.0% 96.7% 28.3% 87.0%\n",
"( 3, 3, 8, 16, 12, 25, 4, 4, 8, 17) 78.8% 97.8% 28.3% 87.0%\n",
"( 5, 6, 3, 16, 16, 24, 5, 5, 7, 13) 80.9% 96.5% 8.7% 86.9%\n",
"( 1, 2, 11, 16, 15, 24, 5, 4, 7, 15) 79.9% 97.2% 6.5% 86.7%\n",
"( 1, 6, 3, 16, 16, 24, 5, 5, 7, 17) 75.2% 97.9% 41.3% 85.5%\n",
"( 5, 3, 12, 17, 23, 4, 5, 6, 8, 17) 64.3% 99.5% 84.8% 82.0%\n",
"( 4, 3, 12, 18, 23, 4, 5, 6, 8, 17) 64.0% 99.5% 88.0% 81.9%\n",
"( 3, 4, 12, 18, 23, 4, 6, 5, 8, 17) 63.2% 99.5% 88.0% 81.5%\n",
"( 2, 4, 12, 18, 24, 4, 6, 5, 8, 17) 63.0% 99.5% 91.3% 81.5%\n",
"( 3, 4, 15, 18, 23, 4, 5, 6, 8, 14) 63.4% 99.5% 76.1% 81.3%\n",
"( 3, 6, 13, 17, 23, 4, 6, 5, 8, 15) 63.2% 99.4% 78.3% 81.2%\n",
"( 1, 3, 14, 18, 24, 4, 5, 6, 8, 17) 62.4% 99.5% 93.5% 81.2%\n",
"( 0, 4, 12, 19, 25, 4, 5, 6, 8, 17) 62.1% 99.5% 95.7% 81.1%\n",
"( 1, 9, 11, 17, 21, 6, 5, 4, 10, 16) 62.1% 100.0% 76.1% 80.9%\n",
"( 0, 4, 12, 18, 21, 7, 6, 4, 8, 20) 61.4% 99.6% 100.0% 80.9%\n",
"( 1, 7, 13, 17, 23, 6, 6, 5, 8, 14) 62.4% 99.5% 78.3% 80.9%\n",
"( 0, 3, 4, 7, 16, 24, 4, 34, 4, 4) 87.4% 37.6% 0.0% 61.1%\n"
]
}
],
"source": [
"def μ(plan, plans): return pct(mean_points(plan,plans))\n",
"all = plans | plans2 | champs\n",
"\n",
"print('Plan plans plans2 champs all')\n",
"for p in sorted(champs, key=lambda p: -mean_points(p, all)):\n",
" print(pplan(p), μ(p, plans), μ(p, plans2), μ(p, champs), μ(p, all))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Which plan is best? In the end, we don't know, because we don't know the pool we will be competing against."
]
}
],
"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.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+697
View File
@@ -0,0 +1,697 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"button": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"source": [
"# SET\n",
"\n",
"How many cards are there? There are four features (color, shape, shading, and number of figures) on each card, and each feature can take one of three values. So the number of cards is:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"81"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"3 ** 4"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"source": [
"How many sets are there?"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"1080.0"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"81 * 80 * 1 / 6"
]
},
{
"cell_type": "markdown",
"metadata": {
"run_control": {}
},
"source": [
"How many sets does each card participate in? We need to look at the number of sets (1080) and the number of cards in a set (3), divided by the number of cards (81):"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"40.0"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"1080 * 3 / 81"
]
},
{
"cell_type": "markdown",
"metadata": {
"run_control": {}
},
"source": [
"Note that each *pair* of cards participates in exactly one set.\n",
"\n",
"How many layouts of 12 cards are there? The answer is (81 choose 12):"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"70724320184700.0"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from math import factorial as fact\n",
"\n",
"def C(n, k): \n",
" \"Number of ways of choosing k things from n things.\"\n",
" return fact(n) / fact(n-k) / fact(k)\n",
"\n",
"C(81, 12)"
]
},
{
"cell_type": "markdown",
"metadata": {
"run_control": {}
},
"source": [
"That's a lot of digits; hard to read. This should help:"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"70.7243201847"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"M = 10 ** 6 # Million\n",
"T = 10 ** 12 # Trillion\n",
"\n",
"C(81, 12) / T"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"source": [
"70 trillion layouts. Of those some will have 6 sets, some more, some less.\n",
"\n",
"In a layout of 12 cards, how many triples (that could potentially be a set) are there?"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"220.0"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"C(12, 3)"
]
},
{
"cell_type": "markdown",
"metadata": {
"run_control": {}
},
"source": [
"So, what we're looking for is when exactly 6 of these are sets, and the other 214 are not."
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"2173.5413336637"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"C(1080, 6) / T"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"81"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from itertools import product\n",
"\n",
"feature = (1, 2, 3)\n",
"\n",
"Card = tuple\n",
"\n",
"cards = set(product(feature, feature, feature, feature))\n",
"len(cards)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [],
"source": [
"def match(card1, card2):\n",
" return Card(match_feature(card1[i], card2[i]) \n",
" for i in (0, 1, 2, 3))\n",
"\n",
"def match_feature(f1, f2):\n",
" return f1 if f1 == f2 else 6 - f1 - f2"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"(1, 3, 1, 3)"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"match((1, 2, 3, 3),\n",
" (1, 1, 2, 3))"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"collapsed": true,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"source": [
"2173 trillion; even worse.\n",
"\n",
"How many layouts are there where:\n",
"- The first six cards form no sets\n",
"- The last six cards each form a set?"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"246.7179"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"81 * 80 * (79 - 1) * (78 - 3) * (77 - 6) * (76 - 10) / fact(6) / M"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"1.091475"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"15 * 21 * 28 * 36 * 45 * 55 / fact(6) / M"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"All tests pass.\n",
"\n",
"Size | Sets | NoSets | Set:NoSet ratio for the instruction booklet\n",
"-----+--------+--------+----------------\n",
" 12 | 33 | 1 | 33:1\n",
" 15 | 2,500 | 1 | 2500:1\n",
"\n",
"Size | Sets | NoSets | Set:NoSet ratio for initial layout\n",
"-----+--------+--------+----------------\n",
" 12 | 96,844 | 3,156 | 31:1\n",
" 15 | 99,963 | 37 | 2702:1\n",
"\n",
"Size | Sets | NoSets | Set:NoSet ratio for game play\n",
"-----+--------+--------+----------------\n",
" 12 | 86,065 | 5,871 | 15:1\n",
" 15 | 5,595 | 64 | 87:1\n",
" 18 | 57 | 0 | inft:1\n",
"\n",
"Size | Sets | NoSets | Set:NoSet ratio for initial layout, but no sets before dealing last 3 cards\n",
"-----+--------+--------+----------------\n",
" 12 | 26,426 | 3,242 | 8:1\n",
" 15 | 3,207 | 35 | 92:1\n"
]
}
],
"source": [
"import random\n",
"import collections \n",
"import itertools \n",
"\n",
"\"\"\"\n",
"Game of Set (Peter Norvig 2010-2015)\n",
"\n",
"How often do sets appear when we deal an array of cards?\n",
"How often in the course of playing out the game?\n",
"\n",
"Here are the data types we will use:\n",
"\n",
" card: A string, such as '3R=0', meaning \"three red striped ovals\".\n",
" deck: A list of cards, initially of length 81.\n",
" layout: A list of cards, initially of length 12.\n",
" set: A tuple of 3 cards.\n",
" Tallies: A dict: {12: {True: 33, False: 1}}} means a layout of size 12\n",
" tallied 33 sets and 1 non-set.\n",
"\"\"\"\n",
"\n",
"#### Cards, dealing cards, and defining the notion of sets.\n",
"\n",
"CARDS = [number + color + shade + symbol \n",
" for number in '123' \n",
" for color in 'RGP' \n",
" for shade in '@O=' \n",
" for symbol in '0SD']\n",
"\n",
"def deal(n, deck): \n",
" \"Deal n cards from the deck.\"\n",
" return [deck.pop() for _ in range(n)]\n",
"\n",
"def is_set(cards):\n",
" \"Are these 3 cards a set? No if any feature has 2 values.\"\n",
" for f in range(4):\n",
" values = {card[f] for card in cards}\n",
" if len(values) == 2: \n",
" return False\n",
" return True\n",
"\n",
"def find_set(layout):\n",
" \"Return a set found from this layout, if there is one.\"\n",
" for cards in itertools.combinations(layout, 3):\n",
" if is_set(cards):\n",
" return cards\n",
" return ()\n",
"\n",
"#### Tallying set:no-set ratio\n",
"\n",
"def Tallies(): \n",
" \"A data structure to keep track, for each size, the number of sets and no-sets.\"\n",
" return collections.defaultdict(lambda: {True: 0, False: 0})\n",
"\n",
"def tally(tallies, layout):\n",
" \"Record that a set was found or not found in a layout of given size; return the set.\"\n",
" s = find_set(layout)\n",
" tallies[len(layout)][bool(s)] += 1\n",
" return s\n",
" \n",
"#### Three experiments\n",
"\n",
"def tally_initial_layout(N, sizes=(12, 15)):\n",
" \"Record tallies for N initial deals.\"\n",
" tallies = Tallies()\n",
" deck = list(CARDS)\n",
" for deal in range(N):\n",
" random.shuffle(deck)\n",
" for size in sizes:\n",
" tally(tallies, deck[:size])\n",
" return tallies\n",
"\n",
"def tally_initial_layout_no_prior_sets(N, sizes=(12, 15)):\n",
" \"\"\"Simulate N initial deals for each size, keeping tallies for Sets and NoSets,\n",
" but only when there was no set with 3 fewer cards.\"\"\"\n",
" tallies = Tallies()\n",
" deck = list(CARDS)\n",
" for deal in range(N):\n",
" random.shuffle(deck)\n",
" for size in sizes:\n",
" if not find_set(deck[:size-3]):\n",
" tally(tallies, deck[:size])\n",
" return tallies\n",
"\n",
"def tally_game_play(N):\n",
" \"Record tallies for the play of N complete games.\"\n",
" tallies = Tallies()\n",
" for game in range(N):\n",
" deck = list(CARDS)\n",
" random.shuffle(deck)\n",
" layout = deal(12, deck)\n",
" while deck:\n",
" s = tally(tallies, layout)\n",
" # Pick up the cards in the set, if any\n",
" for card in s: layout.remove(card)\n",
" # Deal new cards\n",
" if len(layout) < 12 or not s:\n",
" layout += deal(3, deck) \n",
" return tallies\n",
"\n",
"def experiments(N):\n",
" show({12: [1, 33], 15: [1, 2500]}, \n",
" 'the instruction booklet')\n",
" show(tally_initial_layout(N), \n",
" 'initial layout')\n",
" show(tally_game_play(N // 25), \n",
" 'game play')\n",
" show(tally_initial_layout_no_prior_sets(N), \n",
" 'initial layout, but no sets before dealing last 3 cards')\n",
"\n",
"\n",
"def show(tallies, label):\n",
" \"Print out the counts.\"\n",
" print()\n",
" print('Size | Sets | NoSets | Set:NoSet ratio for', label)\n",
" print('-----+--------+--------+----------------')\n",
" for size in sorted(tallies):\n",
" y, n = tallies[size][True], tallies[size][False]\n",
" ratio = ('inft' if n==0 else int(round(float(y)/n)))\n",
" print('{:4d} |{:7,d} |{:7,d} | {:4}:1'\n",
" .format(size, y, n, ratio))\n",
"\n",
"def test():\n",
" assert len(CARDS) == 81 == len(set(CARDS))\n",
" assert is_set(('3R=O', '2R=S', '1R=D'))\n",
" assert not is_set(('3R=0', '2R=S', '1R@D'))\n",
" assert find_set(['1PO0', '2G=D', '3R=0', '2R=S', '1R=D']) == ('3R=0', '2R=S', '1R=D')\n",
" assert not find_set(['1PO0', '2G=D', '3R=0', '2R=S', '1R@D'])\n",
" photo = '2P=0 3P=D 2R=0 3GO0 2POD 3R@D 2RO0 2ROS 1P@S 2P@0 3ROS 2GOD 2P@D 1GOD 3GOS'.split()\n",
" assert not find_set(photo)\n",
" assert set(itertools.combinations([1, 2, 3, 4], 3)) == {(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)}\n",
" print('All tests pass.')\n",
"\n",
"test()\n",
"experiments(100000)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"button": false,
"collapsed": false,
"deletable": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Size | Sets | NoSets | Set:NoSet ratio for the instruction booklet\n",
"-----+--------+--------+----------------\n",
" 12 | 33 | 1 | 33:1\n",
" 15 | 2,500 | 1 | 2500:1\n",
"\n",
"Size | Sets | NoSets | Set:NoSet ratio for initial layout\n",
"-----+--------+--------+----------------\n",
" 12 | 9,696 | 304 | 32:1\n",
" 15 | 9,995 | 5 | 1999:1\n",
"\n",
"Size | Sets | NoSets | Set:NoSet ratio for game play\n",
"-----+--------+--------+----------------\n",
" 12 | 8,653 | 542 | 16:1\n",
" 15 | 513 | 5 | 103:1\n",
" 18 | 5 | 0 | inft:1\n",
"\n",
"Size | Sets | NoSets | Set:NoSet ratio for initial layout, but no sets before dealing last 3 cards\n",
"-----+--------+--------+----------------\n",
" 12 | 2,630 | 294 | 9:1\n",
" 15 | 293 | 1 | 293:1\n"
]
}
],
"source": [
"experiments(10000)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true,
"run_control": {}
},
"outputs": [],
"source": []
}
],
"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.5.1"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
+13278
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+335
View File
@@ -0,0 +1,335 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# A Chaos Game with Triangles\n",
"\n",
"John D. Cook [proposed](https://www.johndcook.com/blog/2017/07/08/the-chaos-game-and-the-sierpinski-triangle/) an interesting \"game\" from the book *[Chaos and Fractals](https://smile.amazon.com/Chaos-Fractals-New-Frontiers-Science/dp/0387202293)*: start at a vertex of an equilateral triangle. Then move to a new point halfway between the current point and one of the three vertexes of the triangle, chosen at random. Repeat to create *N* points, and plot them. What do you get? \n",
"\n",
"I'll refactor Cook's code a bit and then we'll see:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import random\n",
"\n",
"def random_walk(vertexes, N):\n",
" \"Walk halfway from current point towards a random vertex; repeat for N points.\"\n",
" points = [random.choice(vertexes)]\n",
" for _ in range(N-1):\n",
" points.append(midpoint(points[-1], random.choice(vertexes)))\n",
" return points\n",
"\n",
"def show_walk(vertexes, N=5000):\n",
" \"Walk halfway towards a random vertex for N points; show reults.\"\n",
" Xs, Ys = transpose(random_walk(vertexes, N))\n",
" Xv, Yv = transpose(vertexes)\n",
" plt.plot(Xs, Ys, 'r.')\n",
" plt.plot(Xv, Yv, 'bs')\n",
" plt.gca().set_aspect('equal')\n",
" plt.gcf().set_size_inches(9, 9)\n",
" plt.axis('off')\n",
" plt.show()\n",
" \n",
"def midpoint(p, q): return ((p[0] + q[0])/2, (p[1] + q[1])/2)\n",
"\n",
"def transpose(matrix): return zip(*matrix)\n",
"\n",
"triangle = ((0, 0), (0.5, (3**0.5)/2), (1, 0))"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAiAAAAHlCAYAAADMREeGAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAC+tJREFUeJzt3UFOXMcahuGvrsKwl+CpJWg2wCBKVpElZFFZQlZxUQ+S\nBdAgeeolMGNSd9CJnMRcO9j4O5zD84wMttw/OjS8OlVdPeacAQBo+s/SAwAAr48AAQDqBAgAUCdA\nAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMg\nAECdAAEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAAQN13Sw8AbMMYV78kb95+/Dfv3835\n28/9iYCXTIAAz+TN2+TXHz7+/E/9UYAXzxIMAFAnQACAOgECANQJEACgziZU4Jm8f/f4htP37+qj\nAC/emHMuPQMA8MpYggEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAAQJ0AAQDqBAgAUCdA\nAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMg\nAECdAAEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6AQIA1AkQ\nAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQI\nAFAnQACAOgECANQJEACgToAAAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIE\nAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgEC\nANQJEACgToAAAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQAB\nAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAA\nAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AA\ngDoBAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAA\nQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAA\noE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAAQJ0AAQDqBAgA\nUCdAAIA6AQIA1AkQWKMxdhnjKmPslh4F4EsIEFibU3QcklwnOYgQYI0ECKzPZZKLJGdJzpPslx0H\n4OkECKzPTZLbJA9J7pIclx0H4OnGnHPpGYCnOi277JMcM+f90uMAPJUAAQDqLMEAAHUCBACoEyAA\nQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AAgDoBAqzXGLuM\ncZUxdkuPAjyNAAHW6RQdhyTXSQ4iBNZFgABrdZnkIslZkvMk+2XHAZ5CgABrdZPkNslDkrskx2XH\nAZ5izDmXngHgy5yWXfZJjpnzfulxgH9PgAAAdZZgAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQAB\nAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAA\nAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIEYAvG2GWMq4yxW3oU+DcECMDa\nnaLjkOQ6yUGEsAYCBGD9LpNcJDlLcp5kv+w48HkCBGD9bpLcJnlIcpfkuOw48Hljzrn0DAB8rdOy\nyz7JMXPeLz0OfI4AAQDqLMEAAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIE\nAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgEC\nANQJEACgToAAAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQAB\nAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAA\nAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6AQIA1AkQAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AA\ngDoBAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAA\nQJ0AAQDqBAh/N8YuY1xljN3SowCwXQKED07RcUhyneQgQgD4VgQIf3WZ5CLJWZLzJPtlxwFgqwQI\nf3WT5DbJQ5K7JMdlxwFgq8acc+kZeElOyy77JMfMeb/0OABskwABAOoswQAAdQIEAKgTIABAnQAB\nAOoECADLcPLyqyZAAOhz8vKrJ0AAWIKTl185AQLAEpy8/Mo5iAyAZTh5+VUTIABAnSUYAKBOgAAA\ndQIEAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMg8P+MscsYV3+8YRYAz0iA\nwGNO0XFIcp3kIEIAnpcAgcddJrlIcpbkPKe3DAfgmQgQeNxNktskD0nukhyXHQdgW8acc+kZ4GU6\nLbvskxwz5/3S4wBsiQABAOoswQAAdQIEAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1\nAgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAAQJ0AAQDqBAgAUCdAAIA6\nAQIA1AkQAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQAqBMgAECd\nAAH4nDF2GeMqY+yWHgW2QoAAfMopOg5JrpMcRAg8DwEC8GmXSS6SnCU5T7JfdhzYBgEC8Gk3SW6T\nPCS5S3JcdhzYhjHnXHoGgJfttOyyT3LMnPdLjwNbIECg7fTL7DLJjV9mwGtlCQaabGgESCJAoM2G\nRoAIEGizoREg9oBAnw2NAAIEAOizBAMA1AkQAKBOgAAAdQIEAKgTIABAnQABAOoECABQJ0AAgDoB\nAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAAQJ0A\nAQDqBAgAUCdAAIA6AQIA1AkQgC0aY5cxrjLGbulR4DECBGBrTtFxSHKd5CBCeIkECMD2XCa5SHKW\n5DzJftlx4GMCBGB7bpLcJnlIcpfkuOw48LEx51x6BgCe22nZZZ/kmDnvlx4H/kmAAAB1lmAAgDoB\nAgDUCRAAoE6AAAB1AgQAqBMgAECdAAEA6gQIAFAnQACAOgECANQJEABITu+fM8bVH++jwzcmQADg\nFB2HJNdJDiLk2xMgAJBcJrlIcpbkPKd3EuYbEiAAkNwkuU3ykOQuyXHZcbZvzDmXngEAlndadtkn\nOWbO+6XH2ToBAgDUWYIBAOoECABQJ0BgrZxZAKyYAIE1cmYBsHICBNbJmQXAqgkQWCdnFgCr5mW4\nsFbOLOBrnb6HLpPc+B6iTYAAvEYf9hFd5HQ37XsRQpMlGIDXyT6iz/FKs2/qu+aDjfHTfz989P7d\nnL/93Hx8nsCt2fVwrfgyf+4jOo99RB/75x2iMdwh+sMYV78kb97++fGcv/74Jf9PNUCSX3/48Oef\nug/9rWzxh78n3nq8xGu1xefEFs15nzG+z3PtI9redX/sDtHvj/7L7X3tn/Hm7d9/n38ZSzBfY7tn\nMbg1ux4v61pt9zmxTXPeZ87fnyk+tnbd/90rzbb5tVcIkK/zsn74Px8v8VyPl3attvqc4NO2d91P\nUfZ9kh/y6Q262/vaS7wKBgCocwcEAKgTIABAnQABAOoECABQJ0AAgDoBAgDUCRAAoE6AAAB1AgQA\nqBMgAECdAAEA6gQIAFAnQACAOgECANQJEACgToAAAHUCBACoEyAAQJ0AAQDq/gdkC1IJv/ZtegAA\nAABJRU5ErkJggg==\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x1087f3748>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"show_walk(triangle, 20)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"OK, the first 20 points don't tell me much. What if I try 20,000 points?"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x10879e710>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"show_walk(triangle, 20000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wow! The [Sierpinski Triangle](https://en.wikipedia.org/wiki/Sierpinski_triangle)! \n",
"What happens if we start with a different set of vertexes, like a square?"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAiAAAAIXCAYAAACl9uUJAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvbuuHcmSNrYoyJBDQ+by6Y8loLdLmiQgQ5r/Bch2/u5x\nJD3DPIQMAXqTAeQLB8TGGJscp+c39ATN5mYfpozadSovcb9k1mavBRTOae6qvERExuWLyMwXpZTL\n7Xf73X633+13+91+t9/M33+3egC33+13+91+t9/td/v99X43B+T2u/1uv9vv9rv9br/pv5sDcvvd\nfrff7Xf73X633/TfzQG5/W6/2+/2u/1uv9tv+u/mgNx+t9/td/vdfrff7Tf9d3NAbr/b7/a7/W6/\n2+/2m/67OSC33+13+91+t9/td/tN/90ckNvv9rv9br/b7/a7/ab/bg7I7Xf73X633+13+91+0383\nB+T2u/1uv9vv9rv9br/pv5sDcvvdfrff7Xf73X633/TfVAfkxYtLOZ7/8m/Ei3eXFy8eLy9elMuL\nF18vL178xDT88vLixd+evvnb5cWLl4LB1N88XF68uBJ//3h58eIN2e72/p2ob35c9na032tp3fZT\n0/yNqR28PR8d27bfXF68+AaOzdqvXkbzZdr7PSc7Vlmx/sb+Xg9zypSbqJ+Gbivnk83fWfITpYvP\nNo5WNn4fZEQqOyMfPoj4Us3nxYv/8m+1PTfPqZQy7blcSjmef/439N3L5WW5XP5WLpevT//7kmz7\ncrkrl8vjU8Nfy+XyQflNKZfLQ/NN+/fv5XL5ho7lGO+jaLz8vG3tWL7X0hqn+Wu0na2PO7Ltsb2f\nQuRu6/vjEw+/P/1/jM/yfvUy6pVpHT2030Oy0/PNKis+3tX9vRnm5OMfLZN589CsA16XrRhnPh1s\n/InSxdgYZLrs5ZOsflSPg2p/tFm7jPyEyA68FkY+XFm+dHT97y//y/9TD8NK43yhrjtr6EY4IMeE\nfxIxriXo7yKmb988gIw8/r4bLvgdWDDsxtOrfKBxyBeMjNa4EL8E25EqhCzl19LksVwur8P63Rbu\nh3K5XBU0s8i0xxm1Ojy7Q9nyzSIrPv4d/eEyp6NTpJGSz+HNE001jqpMl2XRe1X7Hv7E6WLMIafH\ndbzzrRy2QzYOrv3DJj0+yUYr85s++v2pz99JvdTzAdfdd9X//oOu/+Plf/5/n6ED8s//djw//Z8J\n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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x111077438>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"square = ((0, 0), (0, 1), (1, 0), (1, 1))\n",
"\n",
"show_walk(square)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There doesn't seem to be any structure there. Let's try again to make sure:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAiAAAAIXCAYAAACl9uUJAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvT2vXFmWJRYUIMjKHxCmBPrpjZIQNN0gTdISmF6XxSwn\nyTSmspxuOeNUNaCuAWbGGI05A2l+iQDZCYJgVj9+ZIvMbHS33AGTLx7zbRk3Vp2111773PvisXJU\n0gvggnwR9+Pcc/bn2h/nVkTsbj43n5vPzefmc/O5+dx8fs7Pf/VfegA3n5vPzefmc/O5+dx8/v/3\nuTFAbj43n5vPzefmc/O5+fzsnxsD5OZz87n53HxuPjefm8/P/rkxQG4+N5+bz83n5nPzufn87J8b\nA+Tmc/O5+dx8bj43n5vPz/65MUBuPjefm8/N5+Zz87n5/OyfGwPk5nPzufncfG4+N5+bz8/+uTFA\nbj43n5vPzefmc/O5+fzsnxsD5OZz87n53HxuPjefm8/P/rkxQG4+N5+bz83n5nPzufn87J8bA+Tm\nc/O5+dx8bj43n5vPz/75WQ2QW7d2geO/vvXw/1g5+c7u1q3D7tat2N26db67desz+f3e7taty+Pv\nl7tbt14d/73c3br1dHfr1icr9//k+IxP5Ltvjs/9hs7BOC52t27dtddefTL2u1u3zo73fnf89+nx\nvT6Rc+u4xvf37DX+ur29z2njn6/Px/yM+d5fad7r+9+TMX/x0d9heebTI61UOpy9i5vTLbTmr+Pv\n4khroJt7x/Hl987zdba7dWt/7bH5d18//7q0u1x/Ru9/eXzn/trxzIvdkC141/3u1q1fpjk55dPx\n8vKbrtnpdMlzne+LeTgjGqw8UOXrfTtuXdP8fi93t249oN+uLzOWMb873uOd0GhPX1t/q2N8IvPw\npJmvfl3XxrDl2u3zM+exj/GsW7c++e9v3fvPrM9PHm9E/GzHbheB45/t7v7n2O0+ac/f7T6J3e6b\n2O3Oj/9+Ir/fi93u8njDy9jtPsR4wCF2u89W7v00druL47+fHL+/c7wW97hL514ej2fHvw92XFuO\n5Z5nwRMy3uOi3DeP6zx2u8+O93hG43paxlKv+4L+vojd7u5kfHeutT7b56E+h78fzznEbvdu87wv\n1/1S3vehjHl/7Xeoz1W6vCtj0nd5erxmf/z3qRnf4Ugv+81rUWmM6Ubp2dF/HK//pHkGj23revD5\n+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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x10d934320>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"show_walk(square, 20000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I'm still not seeing anything but random points. How about a right triangle?"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x110e3e048>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"right_triangle = ((0, 0), (0, 1), (1, 0))\n",
"\n",
"show_walk(right_triangle, 20000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We get a squished Serpinski triangle. How about a pentagon? (I'm lazy so I had Wolfram Alpha [compute the vertexes](https://www.wolframalpha.com/input/?i=vertexes+of+regular+pentagon).)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x110f7e588>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"pentagon = ((0.5, -0.688), (0.809, 0.262), (0., 0.850), (-0.809, 0.262), (-0.5, -0.688))\n",
"\n",
"show_walk(pentagon)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To clarify, let's try again with different numbers of points:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x111093048>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"show_walk(pentagon, 10000)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAigAAAHlCAYAAADfkwdyAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvX20pWdVJ/g7qQohSK1RG4WrQAVSmM8xPc2ESS0Mma6y\nG2KVa9ZoRe3paLtM0JEkKklckmAPONOEdghCtAUanW7tblRciC0qgWENCHQGhXFkam7q49a9t77z\nyYctTN17zrn37PljP788+9nv877n3Eqlzqmq/Vtrr3vuOe/H8z7v+z7Pfvb+7b17IoJAIBAIBAKB\nWcJF025AIBAIBAKBgEcoKIFAIBAIBGYOoaAEAoFAIBCYOYSCEggEAoFAYOYQCkogEAgEAoGZQygo\ngUAgEAgEZg6hoAQCgUAgEJg5hIISCAQCgUBg5hAKSiAQCAQCgZlDKCiBQCAQCARmDqGgBAKBQCAQ\nmDmEghIIBAKBQGDmEApKIBAIBAKBmUMoKIFAIBAIBGYOoaAEAoFAIBCYOYSCEggEAoFAYOYQCkog\nEAgEAoGZQygogUAgEAgEZg6hoAQCgUAgEJg5hIISCAQCgUBg5hAKSiAQCAQCgZlDKCiBQCAQCARm\nDqGgBAKBQCAQmDmEghIIBAKBQGDmEApKIBAIBAKBmUMoKIFAIBAIBGYOoaAEAoFAIBCYOYSCEggE\nAoFAYOYQCkogEAgEAoGZQygogUAgEAgEZg6hoAQCgUAgEJg5hIISCAQCgUBg5hAKSiAQCAQCgZlD\nKCiBQCAQCARmDqGgBAKBQCAQmDmEghIIBAKBQGDmEApKIBAIBAKBmUMoKIFAIBAIBGYOoaAEAoFA\nIBCYOYSCEggEAoFAYOYQCkogEAgEAoGZQygogUAgEAgEZg6hoAQCgUAgEJg5hIISCAQCgUBg5hAK\nSiAQCAQCgZnD5mk3IBAIXDjo9bZ/EHjZ9zR/Ob4g8oWfPvstCgQCs4pQUAKBwFnEy74H+MObmt//\nyNlvSiAQmGmEiycQCAQCgcDMIRSUQCAQCAQCM4dQUAKBQCAQCMwcQkEJBAKBQCAwcwiSbCAQOIs4\nvlAnxB5fOOtNCQQCM42eiEy7DYFA4EJBr7cFwLUA5iHyjWk3JxAIzC7CxRMIBM4OVDn5PIDPAvh8\n+r/8vdfbjl5vLv3dUjtMIBC4MBAunkAgcLZwLYCrAVwM4CoA1wD4SwBWebkawBp0bNqHXu/GsLQE\nAhcmwoISCATOFuYB7AMwALAfwKPmN6u8XIpSiQkEAhcgwoISCATODkS+gV7vRgDXA+i5X6m8XAUd\nly4CIABW0OttR3BWAoELDkGSDQQCzx6Tkl9LV84+ANmFo7/9KID3QS0oANCHKitHANwEkcefmwsI\nBAKzhnDxBAKBZ4fJya9UYjwPRaGKyoehyghxSdr2VQA+h15vZ5BnA4ELA6GgBAKB04cqCz+GmtKh\nislOAI+AygvwNJQECwDrAI4Wx1Ml5SYAh6Bclb759XIAnwDwN+j1doeiEgic3wgFJRAInB6y5eQ3\noUrHAMCTAL5ifvsE1GpyMVRxeQMy920TgK3F8ZRvAgA/C+CHACxDuSj0RW+GKiofA/CFsKgEAucv\ngoMSCAROD6pMfBaqfAygxNeLAaxA08V+NP1vB5kD6buXQyN5bkzkWctNGSHzTi5L2w8B/C2A7zDH\nEqgV5lFYLksgEDgvEBaUQCBwujiShJYTElsvhVpGGFK8BFUkegCuhCodRwHcbJQKy00h72SrOf4a\ngG+Funy8ReUaaGRQIBA4jxAKSiAQ2DjU4vEwsrLxg1DLCdLfjwK4EconuRnAYWTuyWao8pHdOxpm\nfMyd5Xja/460z8VQJefOdL59UEVlE4D3hqsnEDi/EApKIBA4HViLx1ao1eRyALcD+F6o4gKo++Uj\nyIrMAaiichBM1NbrzQH4yfQ9LSMCtZxcDeDP037DtN8fA1gF8DsoLTNhRQkEziMEByUQCGwcqlR8\nFqqctHFJ9gG4B2ppIU/lOLKy8n3paEtQBceDHJP90MXUFVAFBen4gLp8Lkmf5wG81uVVicKEgcA5\nirCgBAKBjaHp3mnjklwFVTLIRTkG4JVQl8wrAfwnAPeirpxYd9DVUOVkc/p7JdRq0gPwfCiptpfO\nd71pY3tulkAgMPMIC0ogEKijzQKhuU0+AVUYBtAMr77o31VQy8fNAF4NVWaehCZiY5p7QVYu/GLp\n6wCegCojQM4oexDAFmQXElIbSNBVK4q220YY5TYGAoFzAlGLJxAINOFdNawqrN8/hDx2lMnWcr2d\na9L3n4QqC4AqF0eRlYse1JpCV84m04Jvgaa8fw9ybZ43AfhDKHH2T822F0EVnU1QCwtr/RxI//vC\nhIFA4BxAuHgCgUANbSnpX4Ns1QByRI5HL+1/FbI75goAL0OZF4XbMtHbGjKJ9hNQYiygCszHkyXn\nr1EmcDuI7EY6COC9UBcUoBacyJESCJyDCAUlEAj4ejlAri48AC0QSoz9APK4IVArxaPFcTS1/eeg\nlpaDyAoJLSaAKiE2jX0PwH0ATiC7fLbCZ53N/JeXQZWUHwHw8wBeD1VGfguqQF0MVYgA4DWRcTYQ\nOPcQHJRA4EJHjsi5DLbCsE7o1yArIH8NLdoHqGXjFwD8+wo/5VNQJUOgCsRLkF01gComPwxVXj4O\nVUQOQt06r0zbCDTXyTuhCscB1LkldBkdhSo929Jf8lUuQo74OQxVZj4bFpVAYPYRFpRA4EKGKiGf\nhSoeF0Mnc42EEfkGRP4yTebXoiSmHgHw79MxrOXFRuT0APwqgD+CKgf2+69CZBFKoL0JwJuh6e/t\nNpeaz4S17DwN4BWp3dugisxmaNjx3emYVyC7mF4JX8OnaTkKBAIzglBQAoELFbkS8WXm282wWVl7\nvTn0endBFYD1tE0fwC1QPkquVKyWmH+JUqF4BYB/gLJCcSatUgkCvghVegiBWl6odFyBzIP5Zagr\n6LvQTvTfn4550H1PbswnUtu/AHVHPRJKSiAwWwgXTyBwISJzRa6CukQ2Q3kePTAsV90my9BcI4Aq\nDT2oe+cYVLG5yOxzD4BfR6mgAJqI7b9Kn9Vl5F0s2p43QK0yl0DT5X8vNGX+VVBF4y0AHkj/s61s\nF8m1m0F3kLqpbEg0t4W5js3I7qjvh8inx3VdIBA4OwgLSiBwYeI1ULcNXSK/BHWfWAvHbmTlBMgK\nwQg54Ron94MoLSAWL0O2fnjlxYY0/x6A56VvN6F0+bwSmuKebQbKKJ43QxWaN6TPL0zVlvelaxkA\nWIRyYnidvvZPIBCYIUQelEDgQkJOvuazt34ZSkLNFo5e7zNQZcQvZPz/I2gEzuegk/+VUGXgeVDl\nZD/UGlPmVVFcC+AF6fvNyNaQY+l7unjsWLWWjnlf+v+dUBLuAeSU+Ovp8z5odM9Wc12fhPJsfjPt\nz2y3X6p3WiAQmAbCxRMIXCgok69xMv8eqDJwI0Qed9tvR46YseinfV5lvjsIrTq8D1QGFIwC8tE3\nNwP4NdeWq1G6bQ4gKzkjZGtOH8C1EFl0bbQuGyJnkbWZcdWCZKONfgLqioq6PYHAjCBcPIHAhQOb\nfO0KqFvnCNSV8tlEcrU4glwTZ2S+35SOYVc3V0C5Hp8FcDSRX20UkM+rQrIq2/J+dw5WKN4KLTD4\nu+a3S6AcGbjjHkUTR6E5XMraPM1oo99G1O0JBGYKoaAEAucD2sJly++9krAKJbpeDLWG+Mn5MmTX\nih0rNGlak0+yOR3nrxrKjiopN0OtLDdDI2xsWz6JOodlM5R/cjuyQrQK4En0elsqx92HzE1ZglpP\nGCZtM+OeghKAiUvQzJrL/tsZid4CgbOPcPEEAuc6fN2cMtEavz8CtTp8E2Xytb8BcHn6LAB+DsDv\nmv1tcjYLGw1TwyEAr37GXaIKy+ehis1+KN8FyDV7Hk6ffbTNM1eZ/q5Bc6q8Ih3nxwH8GYAXI/NN\nrkrbfsmc3xcxvBHAC6FWk63ItYBY4PAyaJ/ZWkJaiDBcQIHAWUEoKIHAuY6Sh2E5F55DUioNuu8t\n0AJ8xBpUeSGJ9SZo6PBl6f8BVFk4BOD/gBbwY1Xj/w/At5ntLPejSxHy7fShwD2UJFmGO0v6nZE/\ndb5JqaSwkOA+ZCVkK1RJ4t9PQhWZIyitSEMAr4uqyIHA2UG4eAKBcx/Nujn5+yNmu62w7gvF11Ba\nKzZDJ+froRaHj0KtLj8B4CR0zHgq/f1Z6KQ9gFogXgglsNr6PUwGZwsK9qBRN583rqcj7ndaTI67\n9g1R4nnm85Oo8U1K18x7oVyZpfT7w8jp8h+FWpsYynw5lAxMl1FZdygQCDynCAUlEDiXkS0FN0Ot\nHTem77enLeoZXPO+l0I5HYCSVLndC6DKDEms7wTw0vT/S5EL8m0C8G7ohH4xVLF4E7IF5hEA70M5\n1giyInRNsnD8AFQZWUOJD0MViCE0e+z1UIVmCA1lFnPMH03Huin1Sa0SM5WPS83vn0Umz9ICRPwc\ngO9P0nTvRKr8QOA5Q7h4AoFzFTXuiaL2XZnBNe97DcoMsm+CFvCz3Au6PyzfhPlKjkCVi49AJ/uj\nUDfL465wIDGEWmC+E7kAoG3zUwBeBCWtAjkPC3OVUPG6HsAHkd1GgFpF7gPw75BDkh8FsD25knZD\na/HYUObD0DBmuseOmWPue2bf3GevSb990bU7c38CgcAZQVhQAoFzFz4y5ZrKdz8KACbc1+9r+RX7\noXyUy6AWErparBuF2+5DTnf/HwH8CYCvpP8f7rAobEKuoXMR1C30z5CtNd8N5bIQHKMYdnx9uo4V\nlJlmAY32+X2U2W/fp3v3dkLrBFmQ23IUqpw8iayIrUHDsK99pqigWoM+leQRqLLi+Line truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x111001160>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"show_walk(pentagon, 20000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I definitely see a central hole, and five secondary holes surrounding that, and then, maybe 15 holes surrounding those? Or maybe not 15; hard to tell. Is a \"Sierpinski Pentagon\" a thing? I hadn't heard of it but a [quick search](https://www.google.com/search?q=sierpinski+pentagon) reveals that yes indeed, it is [a thing](http://ecademy.agnesscott.edu/~lriddle/ifs/pentagon/sierngon.htm), and it does have 15 holes surrounding the 5 holes. Let's try the hexagon:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAigAAAIXCAYAAAC2IaX9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvUuMZkl2HhYlkKZhoVcyLP8wBBJyUiKpAWZFogvEgEDW\nxuPKjY0er6RV9Wgx1Q2LHC5M7UkBEm35AXhBL+y1N14Z1MrwmCBEkDAwKCR7yKzseVQ3YQ4BLyQa\n9cisquPF/WPixHlEnHjcGzfzjwAOZrryv/eeeJ3Hd06ceAAAbrbZZpttttlmm21P7W+MZmC22Wab\nbbbZZpuNtmmgzDbbbLPNNttsu2vTQJltttlmm2222XbXpoEy22yzzTbbbLPtrk0DZbbZZpttttlm\n212bBspss80222yzzba7Ng2U2WabbbbZZpttd20aKLPNNttss8022+7aNFBmm2222WabbbbdtWmg\nzDbbbLPNNttsu2vTQJltttlmm2222XbXpoEy22yzzTbbbLPtrk0DZbbZZpttttlm212bBspss802\n22yzzba7Ng2U2WabbbbZZpttd20aKLPNNttss8022+7aNFBmm2222WabbbbdtWmgzDbbbLPNNtts\nu2vTQJltttlmm2222XbXpoEy22yzzTbbbLPtrk0DZbbZZpttttlm212bBspss80222yzzba7Ng2U\n2WabbbbZZpttd20aKLPNNttss8022+7aNFBmm2222WabbbbdtWmgzDbbbLPNNttsu2vTQJltttlm\nm2222XbXpoEy22yzzTbbbLPtrk0DZbbZZpttttlm212bBspss80222yzzba7Ng2U2WabbbbZZptt\nd20aKLPNNttss8022+7aNFBmm2222WabbbbdtWmgzDbbbLPNNttsu2vTQJltttlmm2222XbXpoEy\n22yzzTbbbLPtrk0DZbbZZpttttlm212bBspss80222yzzba7Ng2U2WabbbbZZpttd20aKLPNNtts\ns8022+7aNFBmm2222WabbbbdtZ8azcBsp9kePHj4e879nb/H//LFFcC//sfbczTbbLOt0eZen622\nTQNltkHt7/w95/7XX+P//l9sz8pss822Ypt7fba6NkM8s80222y4PXjwgXvw4KF78OCD0azMNtsp\nt2mgzLar9jPu3b8zmofZTrgtRskfOOe+45z7gyojpcXAyT07jafZTqhNA2W2XbX/0L3+90fzcGfa\n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\nOsj5DevezCo3XTFdOi3uHytFmzLMK1Xe4jH5Q/fgwaPNDZXAw+8f/8VX0OVhrlSfWg0t6XlJQYdw\nxNddqbHRamzVr2FvBPzU8b/fOueeuyXMlOIFGw/Ljdr918e80Xi2+jYawpl0whTD4lfg3JkZbqZQ\neI+TMn368xDx5MNR5xCOez5i4akSWL8EMh95WkbmQZ6X/JFkPmZ16yx+nh+hvW76zhZjle6fPwlk\nO8a+hFdfoP733zdh/vodrZ90MjScgUknTryOR6sSGpOnoStBb7ScHY2w26OxAFUKoUSJxTzVf7PP\nuOjzks5P+e4qYybnbNCaJNsauS1ruDR3JV4br1fZNzP/ZFIjzRDPbOs0KyTvYfEl1FMfq+6bA1DT\ncpcLXroFRvcw/K3rdXxXa8sYfN0tp53GwOy2eUnl8eDQRa8x+5ELYafXzrnP0d++52josDWHp2Qv\n1K7h8vASXq8PnHPfKv5m2Tdm/slsxW0eM56tf6u5i2ONO0a2bPnLBXH73Dn3jx097pk6jpm6IyjP\nky+p/k/YN2396lHu3nKEnF4hgMf0C+fcf+mc+78a+PBHnvG83Drn/jPn3P/rguGjHY3O37xM+7rX\ne2m22G93fU/PNr6NhnAm3UNqi6WXwtTrHZEs7zfNi5GqcF6BcwflWUtpeDtU3l4NtiTfhc9F6/PL\nv5cdQ7f3Cx8BvwYp/6k+nKbluYwIr9GcKPrf6x7xnhVlJzXQcAYm3UPaIh8kn/PRS5G1vSvOsamr\nvbHWHUH5523fLVXOsqLUjDMtf6R1Xmj599fKWrKNXz6PZtvcKD6mbTlekyYNoJmDMlv/tk0+SC7n\no+2occu7eM7BA+fc/+fCDbFSPkIqt6TuqGbNPNQcgeZz8cukpL12HHw5+rxU2+W5CnL9lB+5PnP8\nx865H6D//hn2/bLxk8eqdS/U58DgOfkl59x/7tbIB9lDnZ3Z7m8bbSFNuue0VhhG8kx7etvtqIW/\nqRcfW017sRocvtVRTQnJsED0/Jgr7bN2HNwffcYX5uHv8hugY+SjLWQSwkf8IryaddM7nNFyCoaH\nsf4cnPuiK5IzT+lMWpmGMzDpHtPaAiyd8+GV4zOkBCXFr90+WwfNxwqYHlv9uNjo2VIJtN/k++HR\ngNDfIR8dvgHnHicMGWxs5q8PyPMp5WEc0Df2oXjbc4guwLm3EMJYAEso6xudDJTxtLine truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x1111368d0>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"hexagon = ((0.5, -0.866), (1, 0), (0.5, 0.866), (-0.5, 0.866), (-1, 0), (-0.5, -0.866))\n",
"\n",
"show_walk(hexagon)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAigAAAIXCAYAAAC2IaX9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvc+PXUmWHnbLGhmW7foDnoGBBDghWPIAhG1YKMIQBJPL\npDcGy6vqVVV50WQZnqpeuLsBa7ToGkDdA6tHkEeQZWAEQ4ZGkOyNYRvwaiwI1s4FIp1VnZkkq0hW\n/6juGck/QPK9JPPz4t5v4osvTsS9L5lVL0lGAAeZ771748avG+fEOd855w0AQy+99NJLL7300stl\nKv/SrhvQSy+99NJLL7304qULKL300ksvvfTSy6UrXUDppZdeeumll14uXekCSi+99NJLL730culK\nF1B66aWXXnrppZdLV7qA0ksvvfTSSy+9XLrSBZReeumll1566eXSlS6g9NJLL7300ksvl650AaWX\nXnrppZdeerl0pQsovfTSSy+99NLLpStdQOmll1566aWXXi5d6QJKL7300ksvvfRy6UoXUHrppZde\neumll0tXuoDSSy+99NJLL71cutIFlF566aWXXnrp5dKVLqD00ksvvfTSSy+XrnQBpZdeeumll156\nuXSlCyi99NJLL7300sulK11A6aWXXnrppZdeLl3pAkovvfTSSy+99HLpShdQeumll1566aWXS1e6\ngNJLL7300ksvvVy60gWUXnrppZdeeunl0pUuoPTSSy+99NJLL5eudAGll1566aWXXnq5dKULKL30\n0ksvvfTSy6UrXUDppZdeeumll14uXekCSi+99NJLL730culKF1B66aWXXnrppZdLV7qA0ksvvfTS\nSy+9XLrSBZReeumll1566eXSlS6g9NJLL7300ksvl650AaWXXnrppZdeerl0pQsovfTSSy+99NLL\npStdQOmll1566aWXXi5d6QJKL7300ksvvfRy6UoXUHrppZdeeumll0tXuoDSSy+99NJLL71cutIF\nlF566aWXXnrp5dKVLqD00ksvvfTSSy+XrnQBpZdeeumll156uXTl13bdgF5ez/LGG1f/zjD8+p8v\nf3l4BPwf/+k336Jeeunl6yj9Xe/lvKULKL3sqPz6nx+Gf/hXyu//k2++Kb300svXWPq73sv5Sjfx\n9NJLLy9neeONN4c33rg6vPHGm7tuSi+99HLxpQsovbx85VVhTC97P3bZ/vGZ/2QYhj8chuGfvLRj\n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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x1087f3630>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"show_walk(hexagon, 20000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can see a little of the six-fold symmetry, but it is not as clear as the triangle and pentagon."
]
}
],
"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.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+209
View File
@@ -0,0 +1,209 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Bad Grade, Good Experience\n",
"\n",
"Recently I was asked a question I hadn't thought about before: \n",
"\n",
"> *As a student, did you ever get a bad grade on a programming assignment?* \n",
"\n",
"It has been a long time since college so I've forgotten many of my assignments, but there is one I do remember. It was something like this:\n",
"\n",
"## The Concordance Assignment\n",
"\n",
"> *Using the [`Snobol`](http://www.snobol4.org/) language, read lines of text from the standard input and print a *concordance*, which is an alphabetized list of words in the text, with the line number(s) where each word appears. Words with different capitalization (like \"A\" and \"a\") should be merged into one entry.*\n",
"\n",
"After studying Snobol a bit, I realized that the intended solution was along these lines:\n",
"\n",
"1. Create an empty `dict`, let's call it `entries`.\n",
"2. Read the lines of text (tracking the line numbers), split them into words, and build up the string of line numbers for each word with something like\n",
"`entries[word] += line_number + \", \"`\n",
"3. Convert the table into a two-dimensional `array` where each row has the two columns `[word, line_numbers]`.\n",
"4. Write a function to sort the array alphabetically (`sort` was not built-in to Snobol).\n",
"5. Write a function to print the array.\n",
"\n",
"That would probably be about 40 lines of code; an easy task, the main point of which was just to get familiar with a new and different programming language. But I noticed three interesting things about Snobol:\n",
"\n",
"* There is an *indirection* operator, `$`, so the two statements `word = \"A\"; $word = \"1\"` are equivalent to `A = \"1\"`.\n",
"* Uninitialized variables are treated as the empty string, so `$word += \"1\"` works even if we haven't seen `$word` before.\n",
"* When the program ends, the Snobol interpreter automatically\n",
"prints the values of\n",
"every variable, sorted alphabetically, as a debugging aid.\n",
"\n",
"That means I can do away with the `dict` and the `array`, eliminating steps 1, 3, 4, and 5, and just do step 2! My entire program is as follows (to make it easier to understand I use Python syntax, except for Snobol's `$word` indirection:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"program = \"\"\"\n",
"for num, line in enumerate(input):\n",
" for word in re.findall(r\"\\w+\", line.upper()):\n",
" $word += str(num) + \", \"\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That's 3 lines, not 40, and it works! Actually, it wasn't quite perfect, because the post-mortem printout included the values of the variables `num, line,` and `word`. So, reluctantly, I had to increase the program's line count by 33%:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"program = \"\"\"\n",
"for num, line in enumerate(input):\n",
" for word in re.findall(r\"\\w+\", line.upper()):\n",
" $word += str(num) + \", \"\n",
"del num, line, word\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To prove it works, I'll write a mock Snobol/Python interpreter, which is really just a call to the Python interpreter, `exec`, except that it does three Snobol-ish things: (1) the `context` in which the program is executed is a `defaultdict(str)`, which means that variables default to the empty string. The context is pre-loaded with builtins. (2) Any `$word` in the program is rewritten as `_context[word]`, which enables\n",
"indirection. (3) After the `exec` completes, the user-defined variables (but not the built-in ones) and their values are printed."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from collections import defaultdict\n",
"import re\n",
"\n",
"def snobol(program, data=''):\n",
" \"\"\"A Python interpreter with three Snobol-ish features:\n",
" (1) variables default to '', (2) $word indirection, (3) post-mortem printout.\"\"\"\n",
" context = defaultdict(str, vars(__builtins__))\n",
" context.update(_context=context, re=re, input=data.splitlines())\n",
" builtins = set(context) # The builtin functions won't appear in the printout\n",
" program = re.sub(r'\\$(\\w+)', r'_context[\\1]', program) # \"$word\" => \"_context[word]\"\n",
" exec(program, context)\n",
" print('-' * 50)\n",
" for name in sorted(context):\n",
" if name not in builtins and not name.startswith('_'):\n",
" print('{:10} = {}'.format(name, context[name]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can run my program on some data:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--------------------------------------------------\n",
"A = 1, \n",
"AND = 2, 4, \n",
"DIDDY = 1, 1, 1, 2, 2, 2, \n",
"DO = 1, 1, 2, 2, \n",
"DOWN = 1, \n",
"DUM = 1, 2, \n",
"FEET = 2, \n",
"FINE = 3, 3, 4, \n",
"FINGERS = 2, \n",
"GOOD = 3, 3, 4, \n",
"HER = 2, 2, \n",
"I = 4, \n",
"JUST = 1, \n",
"LOOKED = 3, 3, 3, 3, 4, 4, \n",
"LOST = 4, \n",
"MIND = 4, \n",
"MY = 4, \n",
"NEARLY = 4, \n",
"SHE = 1, 3, 3, 4, 4, \n",
"SHUFFLIN = 2, \n",
"SINGIN = 1, 2, \n",
"SNAPPIN = 2, \n",
"STREET = 1, \n",
"THE = 1, \n",
"THERE = 1, \n",
"WAH = 1, 2, \n",
"WALKIN = 1, \n",
"WAS = 1, \n"
]
}
],
"source": [
"data = \"\"\"\n",
"There she was just a-walkin' down the street, singin' \"Do wah diddy diddy dum diddy do\"\n",
"Snappin' her fingers and shufflin' her feet, singin' \"Do wah diddy diddy dum diddy do\"\n",
"She looked good (looked good), she looked fine (looked fine)\n",
"She looked good, she looked fine and I nearly lost my mind\n",
"\"\"\"\n",
"\n",
"snobol(program, data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looks good to me! But sadly, the grader did not agree, complaining that my program was not extensible: what if I wanted to cover two or more files in one run? What if I wanted the output to have a slightly different format? I argued that [YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it), but the grader wasn't impressed so I got points taken off.\n",
"\n",
"# TFW you flunk AI\n",
"\n",
"Here's another example that I had completely forgotten about until 2016, when I was cleaning out some old files and came across my old college transcript. It turns out that *I flunked an AI course!* (Or at least, didn't complete it.) This course was\n",
"offered by Prof. Richard Millward in the Cognitive Science program. I certainly remember a lot of influential material from this class: we read David Marr, we read Winston's just-published *Psychology of Computer Vision*, we read a chapter from Duda and Hart which was then only a few years old. The things I learned in that course have stuck with me for decades, but one thing that didn't stick is that my transcript said I never completed the course! I'm not sure what happened. I did an independent study with Ulf Grenander that semester; maybe I had to drop the AI course to fit that in. I don't know. \n",
"\n",
"So in both the concordance program and the Cognitive Science AI class, I had a great experience and I learned a lot, even if it wasn't well-reflected in official credit. The moral is: look for the good experiences, and don;t worry about the official credit.\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"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.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
File diff suppressed because it is too large. Load diff
+4651
View File
File diff suppressed because it is too large. Load diff
+871
View File
@@ -0,0 +1,871 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# WWW: Will the Warriors Win?\n",
"\n",
"## 18 April 2016\n",
"\n",
"The Golden State Warriors have had a historic basketball season, winning more games than any other team ever has. But will they top that off by winning the championship? There are 15 other teams in contention, including one, the Spurs, that has had a historic season as the best second-best team ever. The web site fivethirtyeight, using a complicated scoring syste, [gives](http://projects.fivethirtyeight.com/2016-nba-picks/) the Warriors a 44% chance of winning, with the Spurs at 28%. Basketball-reference [has](http://www.basketball-reference.com/friv/playoff_prob.cgi) the Warriors at 41% and Spurs at 32.5%, while a [betting site](http://www.oddsshark.com/nba/nba-futures) had the Warriors at 54% and Spurs at 18%. But what's a good way to make a prediction? There are several choices:\n",
"\n",
"- Subjective impression of a team's strength? Or a statistical model?\n",
"- Predictions based on:\n",
" - Holistic impression of entire postseason (e.g. \"I think the Warriors have a 50% chance of winning it all\")\n",
" - Series by series (e.g. \"I think the Warriors have a 95% chance of beating the Rockets in the first round, then ...\")\n",
" - Game by game (e.g. \"I think the Warriors have a 83% chance of beating the Rockets in Game 1, then ...\")\n",
" - Possession by possession (e.g. simulate games basket by basket, based on past stats)\n",
" \n",
"Here are the top four teams with their Won-Loss percentage and [SRS](http://www.basketball-reference.com/blog/?p=39) (Simple rating system: average margin of victory, adjusted for strength of opponents):\n",
"\n",
" TEAM PCT SRS\n",
" Warriors .890 10.38\n",
" Spurs .817 10.28\n",
" Thunder .671 7.09\n",
" Cavaliers .695 5.45\n",
"\n",
"I decided to go with a subjective impression of one team beating another in a single game. For example, I might think that the Warriors have a 58% chance of beating the Cavaliers in any one game, and from that compute the odds of winning a series:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def win_series(p, W=0, L=0):\n",
" \"Probability of winning best-of-7 series, given a probability p of winning a game.\"\n",
" return (1 if W == 4 else\n",
" 0 if L == 4 else\n",
" p * win_series(p, W + 1, L) +\n",
" (1 - p) * win_series(p, W, L + 1))"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"0.6705883933696"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"win_series(0.58)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In other words, if you have a 58% chance of winning a game, you have a 67% chance of winning the series.\n",
"\n",
"Note that I ignore the fact that games aren't strictly independent; I ignore home court advantage; and I ignore the chance of catastrophic injuries. Why? Because all these factors would change the final winning estimate by only a few percentage points, and I already have more uncertainty than that.\n",
"\n",
"Note that `win_series` takes optional arguments to say how many games in the sries have been won and lost so far. Here's a table showing your chance of winning a series, given the current tally of games won and lost on the left, and your expected percentage of winning a game at the top:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def percents(items, fmt='{:4.0%}'): return ' '.join(fmt.format(item) for item in items)\n",
"\n",
"def series_table(pcts=[p/100 for p in range(20, 81, 5)]):\n",
" print('W-L | Singe Game Win Percentage')\n",
" print(' | ' + percents(pcts))\n",
" for W in range(4):\n",
" print('----+' + '-' * 5 * len(pcts))\n",
" for L in reversed(range(4)):\n",
" results = [win_series(p, W, L) for p in pcts]\n",
" print('{}-{} | {}'.format(W, L, percents(results)))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"W-L | Singe Game Win Percentage\n",
" | 20% 25% 30% 35% 40% 45% 50% 55% 60% 65% 70% 75% 80%\n",
"----+-----------------------------------------------------------------\n",
"0-3 | 0% 0% 1% 2% 3% 4% 6% 9% 13% 18% 24% 32% 41%\n",
"0-2 | 1% 2% 3% 5% 9% 13% 19% 26% 34% 43% 53% 63% 74%\n",
"0-1 | 2% 4% 7% 12% 18% 26% 34% 44% 54% 65% 74% 83% 90%\n",
"0-0 | 3% 7% 13% 20% 29% 39% 50% 61% 71% 80% 87% 93% 97%\n",
"----+-----------------------------------------------------------------\n",
"1-3 | 1% 2% 3% 4% 6% 9% 12% 17% 22% 27% 34% 42% 51%\n",
"1-2 | 3% 5% 8% 13% 18% 24% 31% 39% 48% 56% 65% 74% 82%\n",
"1-1 | 6% 10% 16% 24% 32% 41% 50% 59% 68% 76% 84% 90% 94%\n",
"1-0 | 10% 17% 26% 35% 46% 56% 66% 74% 82% 88% 93% 96% 98%\n",
"----+-----------------------------------------------------------------\n",
"2-3 | 4% 6% 9% 12% 16% 20% 25% 30% 36% 42% 49% 56% 64%\n",
"2-2 | 10% 16% 22% 28% 35% 43% 50% 57% 65% 72% 78% 84% 90%\n",
"2-1 | 18% 26% 35% 44% 52% 61% 69% 76% 82% 87% 92% 95% 97%\n",
"2-0 | 26% 37% 47% 57% 66% 74% 81% 87% 91% 95% 97% 98% 99%\n",
"----+-----------------------------------------------------------------\n",
"3-3 | 20% 25% 30% 35% 40% 45% 50% 55% 60% 65% 70% 75% 80%\n",
"3-2 | 36% 44% 51% 58% 64% 70% 75% 80% 84% 88% 91% 94% 96%\n",
"3-1 | 49% 58% 66% 73% 78% 83% 88% 91% 94% 96% 97% 98% 99%\n",
"3-0 | 59% 68% 76% 82% 87% 91% 94% 96% 97% 98% 99% 100% 100%\n"
]
}
],
"source": [
"series_table()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And here's a function to tabulate the chances of winning each series on the way to a title:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def playoffs(name, rounds):\n",
" \"Print probability for team winning each series.\"\n",
" overall = (1, 1, 1) # (lo, med, hi) probabilities of winning it all\n",
" for (opponent, probs) in rounds:\n",
" this_round = [win_series(p) for p in probs]\n",
" overall = [overall[i] * this_round[i] for i in range(len(probs))]\n",
" print('{} vs {:8} win this round: {}; win through here: {}'.format(\n",
" name, opponent, percents(this_round), percents(overall)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now I enter my subjective probability estimates (low, medium, and high), and likely opponents for each round, for the three top contenders:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Warriors vs Rockets win this round: 93% 98% 99%; win through here: 93% 98% 99%\n",
"Warriors vs Clippers win this round: 83% 91% 97%; win through here: 77% 89% 95%\n",
"Warriors vs Spurs win this round: 39% 67% 87%; win through here: 30% 60% 83%\n",
"Warriors vs Cavs win this round: 71% 83% 93%; win through here: 22% 50% 78%\n"
]
}
],
"source": [
"playoffs('Warriors',\n",
" [('Rockets', (0.75, 0.83, 0.85)),\n",
" ('Clippers', (0.67, 0.73, 0.80)),\n",
" ('Spurs', (0.45, 0.58, 0.70)),\n",
" ('Cavs', (0.60, 0.67, 0.75))])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Spurs vs Memphis win this round: 93% 98% 99%; win through here: 93% 98% 99%\n",
"Spurs vs Thunder win this round: 39% 75% 87%; win through here: 36% 73% 86%\n",
"Spurs vs Warriors win this round: 13% 33% 61%; win through here: 5% 24% 53%\n",
"Spurs vs Cavs win this round: 71% 83% 93%; win through here: 3% 20% 49%\n"
]
}
],
"source": [
"playoffs('Spurs',\n",
" [('Memphis', (0.75, 0.83, 0.85)),\n",
" ('Thunder', (0.45, 0.62, 0.70)),\n",
" ('Warriors', (0.30, 0.42, 0.55)),\n",
" ('Cavs', (0.60, 0.67, 0.75))])"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cavs vs Pistons win this round: 93% 98% 99%; win through here: 93% 98% 99%\n",
"Cavs vs Hawks win this round: 39% 71% 93%; win through here: 36% 70% 92%\n",
"Cavs vs Raptors win this round: 29% 61% 80%; win through here: 11% 42% 73%\n",
"Cavs vs GSW/SAS win this round: 7% 17% 29%; win through here: 1% 7% 21%\n"
]
}
],
"source": [
"playoffs('Cavs',\n",
" [('Pistons', (0.75, 0.83, 0.85)),\n",
" ('Hawks', (0.45, 0.60, 0.75)),\n",
" ('Raptors', (0.40, 0.55, 0.65)),\n",
" ('GSW/SAS', (0.25, 0.33, 0.40))])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I have the Warriors at 50% (for the medium estimate of winning it all) and the Spurs at 20%, so I'm more of a Warriors fan than fivethirtyeight and basketball-reference, but I have very wide margins between my low and high estimate: 22% to 78% for the Warriors; 3% to 49% for the Spurs; 1% to 21% for the Cavs. Interestingly, while fivethirtyeight does not think this year's Warriors are better than the 1995 Bulls, they [do think](http://fivethirtyeight.com/features/the-warriors-still-arent-the-best-team-ever/) the Spurs, Thunder, and Cavs are the best ever second-, third-, and fourth-best teams in a season.\n",
"\n",
"\n",
"\n",
"What's better--a holistic guess at the outcome, or a reductionist model like this one? I can't say that one is better than the other in every case, but it can be instructive to examine the cases where the reductionist model differs from your holistic impressions. For example, look at the low end of my prediction for the Spurs. I feel like it is crazy to say the Spurs only have a 3% chance of winning the title, but I don't feel that any of the individual game win probabilities (75%, 45%, 30%, and 60%, respectively) are crazy. So now I know that at least one of my intutions is wrong. But I'm not sure how to reconcile the mismatch..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# WWWWC: Will Warriors Win Without Curry?\n",
"\n",
"## 27 April 2016\n",
"\n",
"The Playoff picture has changed! \n",
"\n",
"We have some results for first-round series, and there have been key injuries to players including Steph Curry, Avery Bradley, Chris Paul, and Blake Griffin. To update, first I make a small alteration to allow the current state of a series in terms of wins/loses to be specified as part of the input to `playoffs`:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def playoffs(name, rounds):\n",
" \"Print probability for team winning each series.\"\n",
" overall = (1, 1, 1) # (lo, med, hi) probabilities of winning it all\n",
" for (opponent, probs, *args) in rounds:\n",
" this_round = [win_series(p, *args) for p in probs]\n",
" overall = [overall[i] * this_round[i] for i in range(len(probs))]\n",
" print('{} vs {:8} win this round: ({}) win through here: ({})'.format(\n",
" name, opponent, percents(this_round), percents(overall)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We don't know for sure how long Curry will be out, but here are my updated odds for the Warriors, with the middle probability value representing the assumption that Curry misses the second round, and comes back in time for the Western Conference Finals at a mildly reduced capacity; the low and high probability values represent more and less severe injuries:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Warriors vs Rockets win this round: ( 88% 97% 99%) win through here: ( 88% 97% 99%)\n",
"Warriors vs Blazers win this round: ( 39% 61% 83%) win through here: ( 34% 59% 83%)\n",
"Warriors vs Spurs win this round: ( 13% 61% 83%) win through here: ( 4% 36% 69%)\n",
"Warriors vs Cavs win this round: ( 29% 71% 87%) win through here: ( 1% 26% 60%)\n"
]
}
],
"source": [
"playoffs('Warriors',\n",
" [('Rockets', (0.50, 0.70, 0.80), 3, 1),\n",
" ('Blazers', (0.45, 0.55, 0.67)),\n",
" ('Spurs', (0.30, 0.55, 0.67)),\n",
" ('Cavs', (0.40, 0.60, 0.70))])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The Spurs and Cavs are rolling; let's update their odds:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Spurs vs Memphis win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Spurs vs Thunder win this round: ( 39% 75% 87%) win through here: ( 39% 75% 87%)\n",
"Spurs vs Warriors win this round: ( 17% 39% 87%) win through here: ( 7% 29% 76%)\n",
"Spurs vs Cavs win this round: ( 71% 83% 93%) win through here: ( 5% 24% 71%)\n"
]
}
],
"source": [
"playoffs('Spurs',\n",
" [('Memphis', (0.75, 0.83, 0.85), 4, 0),\n",
" ('Thunder', (0.45, 0.62, 0.70)),\n",
" ('Warriors', (0.33, 0.45, 0.70)),\n",
" ('Cavs', (0.60, 0.67, 0.75))])"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cavs vs Pistons win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Hawks win this round: ( 39% 71% 93%) win through here: ( 39% 71% 93%)\n",
"Cavs vs Raptors win this round: ( 29% 61% 80%) win through here: ( 11% 43% 74%)\n",
"Cavs vs GSW/SAS win this round: ( 13% 29% 71%) win through here: ( 1% 13% 53%)\n"
]
}
],
"source": [
"playoffs('Cavs',\n",
" [('Pistons', (0.75, 0.83, 0.85), 4, 0),\n",
" ('Hawks', (0.45, 0.60, 0.75)),\n",
" ('Raptors', (0.40, 0.55, 0.65)),\n",
" ('GSW/SAS', (0.30, 0.40, 0.60))])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So my updated odds are that the Warriors and Spurs are roughly equally likely to win (26% and 24%); the Cavs are still less likely (13%), and there is more uncertainty.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# WWWWCB: Will Warriors Win With Curry Back?\n",
"\n",
"## 10 May 2016\n",
"\n",
"Curry has returned from his injury, and after a slow shooting start, had the highest-scoring overtime period in the history of the NBA. Meanwhile, the Thunder lead the Spurs, 3-2, and the Cavaliers have been dominant in the East, hitting a historic number of 3-point shots. Here is my revised outlook: "
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Warriors vs Rockets win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Blazers win this round: ( 91% 96% 98%) win through here: ( 91% 96% 98%)\n",
"Warriors vs Spurs win this round: ( 39% 71% 83%) win through here: ( 36% 68% 82%)\n",
"Warriors vs Cavs win this round: ( 29% 61% 83%) win through here: ( 10% 42% 68%)\n"
]
}
],
"source": [
"playoffs('Warriors',\n",
" [('Rockets', (0.50, 0.70, 0.80), 4, 1),\n",
" ('Blazers', (0.55, 0.67, 0.75), 3, 1),\n",
" ('Spurs', (0.45, 0.60, 0.67)),\n",
" ('Cavs', (0.40, 0.55, 0.67))])"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Spurs vs Memphis win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Spurs vs Thunder win this round: ( 16% 36% 49%) win through here: ( 16% 36% 49%)\n",
"Spurs vs Warriors win this round: ( 17% 29% 61%) win through here: ( 3% 10% 30%)\n",
"Spurs vs Cavs win this round: ( 29% 50% 87%) win through here: ( 1% 5% 26%)\n"
]
}
],
"source": [
"playoffs('Spurs',\n",
" [('Memphis', (0.75, 0.83, 0.85), 4, 0),\n",
" ('Thunder', (0.40, 0.60, 0.70), 2, 3),\n",
" ('Warriors', (0.33, 0.40, 0.55)),\n",
" ('Cavs', (0.40, 0.50, 0.70))])"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Thunder vs Dallas win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Thunder vs Spurs win this round: ( 51% 64% 84%) win through here: ( 51% 64% 84%)\n",
"Thunder vs Warriors win this round: ( 17% 29% 61%) win through here: ( 9% 19% 51%)\n",
"Thunder vs Cavs win this round: ( 20% 39% 71%) win through here: ( 2% 7% 36%)\n"
]
}
],
"source": [
"playoffs('Thunder',\n",
" [('Dallas', (0.75, 0.83, 0.85), 4, 1),\n",
" ('Spurs', (0.30, 0.40, 0.60), 3, 2),\n",
" ('Warriors', (0.33, 0.40, 0.55)),\n",
" ('Cavs', (0.35, 0.45, 0.60))])"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cavs vs Pistons win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Hawks win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Raptors win this round: ( 50% 80% 93%) win through here: ( 50% 80% 93%)\n",
"Cavs vs GS/SA/OK win this round: ( 17% 39% 61%) win through here: ( 8% 31% 57%)\n"
]
}
],
"source": [
"playoffs('Cavs',\n",
" [('Pistons', (0.75, 0.83, 0.85), 4, 0),\n",
" ('Hawks', (0.45, 0.60, 0.75), 4, 0),\n",
" ('Raptors', (0.50, 0.65, 0.75)),\n",
" ('GS/SA/OK', (0.33, 0.45, 0.55))])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So overall, from the start of the playoffs up to May 10th, I have:\n",
"\n",
"- **Warriors:** Dropped from 50% to 26% with Curry's injury, and rebounded to 42%. \n",
"- **Spurs:** Dropped from 20% to 5% after falling behind Thunder.\n",
"- **Thunder:** Increased to 7%.\n",
"- **Cavs:** Increased to 31%.\n",
"\n",
"# Time to Panic?\n",
"\n",
"## 17 May 2016\n",
"\n",
"The Thunder finished off the Spurs and beat the Warriors in game 1. Are the Thunder, like the Cavs, peaking at just the right time, after an inconsistant regular season? Is it time for Warriors fans to panic?\n",
"\n",
"Sure, the Warriors were down a game twice in last year's playoffs and came back to win both times. Sure, the Warriors are still 3-1 against the Thunder this year, and only lost two games all season to elite teams (Spurs, Thunder, Cavs, Clippers, Raptors). But the Thunder are playing at a top level. Here's my update, showing that the loss cost the Warriors 5%:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Warriors vs Rockets win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Blazers win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Thunder win this round: ( 26% 61% 74%) win through here: ( 26% 61% 74%)\n",
"Warriors vs Cavs win this round: ( 29% 61% 80%) win through here: ( 7% 37% 60%)\n"
]
}
],
"source": [
"playoffs('Warriors',\n",
" [('Rockets', (0.50, 0.70, 0.80), 4, 1),\n",
" ('Blazers', (0.55, 0.67, 0.75), 4, 1),\n",
" ('Thunder', (0.45, 0.63, 0.70), 0, 1),\n",
" ('Cavs', (0.40, 0.55, 0.65))])"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# Not Yet?\n",
"\n",
"## 18 May 2016\n",
"\n",
"The Warriors won game two of the series, so now they're back up to 45%, with the Cavs at 35%. At this time, [fivethirtyeight](http://projects.fivethirtyeight.com/2016-nba-picks/) has the Warriors at 45%, Cavs at 28% and Thunder at 24%"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Warriors vs Rockets win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Blazers win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Thunder win this round: ( 41% 73% 84%) win through here: ( 41% 73% 84%)\n",
"Warriors vs Cavs win this round: ( 29% 61% 80%) win through here: ( 12% 45% 67%)\n"
]
}
],
"source": [
"playoffs('Warriors',\n",
" [('Rockets', (0.50, 0.70, 0.80), 4, 1),\n",
" ('Blazers', (0.55, 0.67, 0.75), 4, 1),\n",
" ('Thunder', (0.45, 0.63, 0.70), 1, 1),\n",
" ('Cavs', (0.40, 0.55, 0.65))])"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cavs vs Pistons win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Hawks win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Raptors win this round: ( 66% 88% 96%) win through here: ( 66% 88% 96%)\n",
"Cavs vs GSW win this round: ( 20% 39% 71%) win through here: ( 13% 35% 68%)\n"
]
}
],
"source": [
"playoffs('Cavs',\n",
" [('Pistons', (0.75, 0.83, 0.85), 4, 0),\n",
" ('Hawks', (0.45, 0.60, 0.75), 4, 0),\n",
" ('Raptors', (0.50, 0.65, 0.75), 1, 0),\n",
" ('GSW', (0.35, 0.45, 0.60))])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Yet!\n",
"\n",
"## 24 May 2016\n",
"\n",
"The Thunder won two in a row (first time the Warriors had lost two in a row all year), putting the Warriors down 3-1. And the Cavs are looking mortal, losing two to the Raptors. So now it looks to me like the Thunder are favorites to win it all:"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Warriors vs Rockets win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Blazers win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Thunder win this round: ( 2% 17% 27%) win through here: ( 2% 17% 27%)\n",
"Warriors vs Cavs win this round: ( 29% 61% 80%) win through here: ( 0% 10% 22%)\n"
]
}
],
"source": [
"playoffs('Warriors',\n",
" [('Rockets', (0.50, 0.70, 0.80), 4, 1),\n",
" ('Blazers', (0.55, 0.67, 0.75), 4, 1),\n",
" ('Thunder', (0.25, 0.55, 0.65), 1, 3),\n",
" ('Cavs', (0.40, 0.55, 0.65))])"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cavs vs Pistons win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Hawks win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Raptors win this round: ( 50% 57% 78%) win through here: ( 50% 57% 78%)\n",
"Cavs vs Thunder win this round: ( 20% 39% 71%) win through here: ( 10% 23% 56%)\n"
]
}
],
"source": [
"playoffs('Cavs',\n",
" [('Pistons', (0.75, 0.83, 0.85), 4, 0),\n",
" ('Hawks', (0.45, 0.60, 0.75), 4, 0),\n",
" ('Raptors', (0.50, 0.55, 0.70), 2, 2),\n",
" ('Thunder', (0.35, 0.45, 0.60))])"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Thunder vs Dallas win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Thunder vs Spurs win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Thunder vs Warriors win this round: ( 73% 83% 98%) win through here: ( 73% 83% 98%)\n",
"Thunder vs Cavs win this round: ( 29% 61% 80%) win through here: ( 21% 51% 79%)\n"
]
}
],
"source": [
"playoffs('Thunder',\n",
" [('Dallas', (0.75, 0.83, 0.85), 4, 1),\n",
" ('Spurs', (0.30, 0.40, 0.60), 4, 2),\n",
" ('Warriors', (0.35, 0.45, 0.75), 3, 1),\n",
" ('Cavs', (0.40, 0.55, 0.65))])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# But Not Done Yet\n",
"\n",
"## 26 May 2016\n",
"\n",
"The Warriors won game 5, bringing them up from a 10% to an 18% chance of winning it all:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Warriors vs Rockets win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Blazers win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Warriors vs Thunder win this round: ( 12% 30% 42%) win through here: ( 12% 30% 42%)\n",
"Warriors vs Cavs win this round: ( 29% 61% 80%) win through here: ( 4% 18% 34%)\n"
]
}
],
"source": [
"playoffs('Warriors',\n",
" [('Rockets', (0.50, 0.70, 0.80), 4, 1),\n",
" ('Blazers', (0.55, 0.67, 0.75), 4, 1),\n",
" ('Thunder', (0.35, 0.55, 0.65), 2, 3),\n",
" ('Cavs', (0.40, 0.55, 0.65))])"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cavs vs Pistons win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Hawks win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Cavs vs Raptors win this round: ( 75% 80% 91%) win through here: ( 75% 80% 91%)\n",
"Cavs vs Thunder win this round: ( 20% 39% 71%) win through here: ( 15% 31% 65%)\n"
]
}
],
"source": [
"playoffs('Cavs',\n",
" [('Pistons', (0.75, 0.83, 0.85), 4, 0),\n",
" ('Hawks', (0.45, 0.60, 0.75), 4, 0),\n",
" ('Raptors', (0.50, 0.55, 0.70), 3, 2),\n",
" ('Thunder', (0.35, 0.45, 0.60))])"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Thunder vs Dallas win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Thunder vs Spurs win this round: (100% 100% 100%) win through here: (100% 100% 100%)\n",
"Thunder vs Warriors win this round: ( 58% 70% 94%) win through here: ( 58% 70% 94%)\n",
"Thunder vs Cavs win this round: ( 29% 61% 80%) win through here: ( 17% 42% 75%)\n"
]
}
],
"source": [
"playoffs('Thunder',\n",
" [('Dallas', (0.75, 0.83, 0.85), 4, 1),\n",
" ('Spurs', (0.30, 0.40, 0.60), 4, 2),\n",
" ('Warriors', (0.35, 0.45, 0.75), 3, 2),\n",
" ('Cavs', (0.40, 0.55, 0.65))])"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# The Finals\n",
"\n",
"## 1 June 2016\n",
"\n",
"The Warriors completed their comeback against the Thunder, putting them in a great position to win this year (and they are already established as [favorites for next year](http://www.foxsports.com/nba/story/golden-state-warriors-title-favorites-cleveland-cavaliers-odds-2016-17-053016)). Rather than update the odds after each game 0f the finals, I'll just repeat the table (with the note that I think the Warriors are somewhere in the 60% range for each game):\n"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"W-L | Singe Game Win Percentage\n",
" | 20% 25% 30% 35% 40% 45% 50% 55% 60% 65% 70% 75% 80%\n",
"----+-----------------------------------------------------------------\n",
"0-3 | 0% 0% 1% 2% 3% 4% 6% 9% 13% 18% 24% 32% 41%\n",
"0-2 | 1% 2% 3% 5% 9% 13% 19% 26% 34% 43% 53% 63% 74%\n",
"0-1 | 2% 4% 7% 12% 18% 26% 34% 44% 54% 65% 74% 83% 90%\n",
"0-0 | 3% 7% 13% 20% 29% 39% 50% 61% 71% 80% 87% 93% 97%\n",
"----+-----------------------------------------------------------------\n",
"1-3 | 1% 2% 3% 4% 6% 9% 12% 17% 22% 27% 34% 42% 51%\n",
"1-2 | 3% 5% 8% 13% 18% 24% 31% 39% 48% 56% 65% 74% 82%\n",
"1-1 | 6% 10% 16% 24% 32% 41% 50% 59% 68% 76% 84% 90% 94%\n",
"1-0 | 10% 17% 26% 35% 46% 56% 66% 74% 82% 88% 93% 96% 98%\n",
"----+-----------------------------------------------------------------\n",
"2-3 | 4% 6% 9% 12% 16% 20% 25% 30% 36% 42% 49% 56% 64%\n",
"2-2 | 10% 16% 22% 28% 35% 43% 50% 57% 65% 72% 78% 84% 90%\n",
"2-1 | 18% 26% 35% 44% 52% 61% 69% 76% 82% 87% 92% 95% 97%\n",
"2-0 | 26% 37% 47% 57% 66% 74% 81% 87% 91% 95% 97% 98% 99%\n",
"----+-----------------------------------------------------------------\n",
"3-3 | 20% 25% 30% 35% 40% 45% 50% 55% 60% 65% 70% 75% 80%\n",
"3-2 | 36% 44% 51% 58% 64% 70% 75% 80% 84% 88% 91% 94% 96%\n",
"3-1 | 49% 58% 66% 73% 78% 83% 88% 91% 94% 96% 97% 98% 99%\n",
"3-0 | 59% 68% 76% 82% 87% 91% 94% 96% 97% 98% 99% 100% 100%\n"
]
}
],
"source": [
"series_table()"
]
}
],
"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.5.1"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
+1
View File
@@ -0,0 +1 @@
5092,20.9,1360 13902.6,50,3183 4239.4,34,1011 14146.5,71,3407 1728,0,341 9595.8,58.6,1866 49571.4,377,8491 35040.3,113,6300 30775,60,5671 10246.2,41,1976 27850.7,67,5010 38901.6,69,7097 109988,1868.4,22392 32369.6,85,5723 40331.7,81,7573 40767.3,209,7162 1325.3,1,246 18910.7,43,3471 44109.3,86,7399 72041.9,194,11428 3683.3,8,672 62921,950,12532 38522.8,81,6817 33413.1,80,5413 32539.5,63,5828 38397.7,186,6436 21211.6,54,3526 48657.5,124,8447 40202.8,71,6669 42429,151,7301 11228.3,28,1915 24194.9,69,4318 12900.4,24,2016 15412.6,120,2305 19762.3,51,3504 1508.1,0,249 65299.3,158,10478 67507.3,876,13065 16946.2,77,2962 33243.3,112,5498 68070.4,680,13021 40093.5,196,6935 21359.9,54,3585 16272.9,58,2737 25929.6,66,4315 35441.9,127,6007 48548.5,183,8293 49959.7,417,8345 69561.3,1388,15231 0.2,0,1 8300,24,1390 8237,15,1434 26401.3,69,4627 8168,14,1380 52829.7,522,10629 90544,420,14305 23826.4,47,4256 21086.4,94,3688 39275.8,96,6794 38191.9,176,6345 22013.5,54,3740 42223.2,347,7006 65702,192,10873 33739.4,123,5642 21822.6,56,3837 48156.2,141,8536 17929.1,36,3193 64814.1,828,13178 66246.4,662,11974 83721.1,530,13959 0,0,11 65020.5,490,10428 83146.7,537,13594 17641.1,59,3075 42280.8,117,7278 49080.6,212,8387 96739.2,441,16418 112767,774,19010 41312.9,79,6969 32162.6,66,5377 33011.5,88,5915 15596.3,34,2551 50336.6,180,8616 32474.4,170,5403 28057,168,4916 23086.7,45,3707 43936.7,163,7637 72673.5,437,12868 72616,571,11927 49429.1,142,7957 80494.5,487,13948 26072.9,23.9,2108 32420.7,199,5635 19073.9,35,3322 49665,330,8026 34703.7,622,8248 9267.8,370,3502 8305.9,16,1279 32831.9,329,6889 56756.1,814,10380 33488.7,167,5813 36365.8,78,6147 33074.1,79,5575 64424.1,575,11041 64547.7,650,11491 2859.7,4,696 61900.6,321,10442 21655.9,43,3932 19244.1,38,3475 1354,5,369 59636.6,446,9253 52398.9,721,11092 22230.9,48,3773 33055.3,73,5556 54726.3,354,8969 7160.3,14,1326 58262.9,878,11317 29205,59,5105 33279.9,80,5748 38607.9,79,6432 73392.1,420,11051 26514.8,173,5638 48349.1,158,8337 34877.5,121,5978 80676.8,563,12721 22651.4,55,3744 27670.4,191,4888 17636.8,71,2876 51046.6,388,8626 30042.8,110,4658 54232.7,663,9899 24780,50,4293 65700.3,508,10326 34360.6,111,5877 27519.4,58,4746 84482.6,385,13108 7046.2,124,1453 29369.4,311,5233 31900.4,233,4671 47932.3,157,8155 32291.4,97,4985 62318.2,671,11662 17393.4,28,3106 12934.2,23,2189 19439.3,43,3300 32342.4,80,5424 33504.2,116,5455 64917.8,518,9693 9168.5,0,2427 5490.9,0,1274 1815,0,450 1566.9,0,325 81511.6,417,12496 40274.7,77,6450 48466.7,100,8391 32327.9,67,5379 34313.4,118,5611 4852.9,5,809 4003.1,10,699 64600.5,337,9666 4972.8,7,964 54646.6,419,8717 15142.8,107,2586 50941.5,726,8806 17622.9,20,3059 14987.3,38,2656 45143.8,84,7847 37883.4,82,6561 41869.6,197,6885 2909.7,5,696 128861,1840.7,27038 33671.9,76,5691 32367.1,97,5064 30664.4,111.5,6983 70708.5,408,10402 34208.1,153,6044 50046.8,168,8164 27035.1,75,4557 21870.6,78,3536 57424.6,227,9424 17102,218,3305 50667.6,459,8446 43844.1,146,7467 35509.1,132,5633 29458.2,204,5039 25335.3,53,4294 39604.9,230,6757 78509.9,1005.9,14584 25583,221,4460 43220,331,7004 38660.5,158,6438 35590.3,91,6511 24214.5,62,4284 40242.2,275,6176 24288.2,51,4087 8119.4,12,1667 54157.6,851,11248 33391.6,103,5615 1731.4,4,625 14488.5,5,4622 12931.9,19,4623 19671.6,3,6864 21323,26,7314 24358.2,60,4076 34266.7,112,5832 52480.8,108,9135 57017.8,411,8421 4547.7,9,872 76487.7,650,12624 32234.7,105,5277 42221.6,217,7086 26164.2,100,4212 7646.2,15,1097 25250.3,60,4374 23113.3,120,4018 8740.4,8,1561 34901.9,60,6499 88119.1,1611.3,16212 6387.2,51,1161 119660,2014.6,24254 25061.3,62,4305 18813.5,59,3251 18255.7,41,3217 40512,129,6758 66298.8,465,9778 23189.3,57,3702 18581,44,3180 42637,201,6947 9042.8,18,1479 8365.1,23,1326 7424.3,13,1345 15483.5,41,2575 25959,110,4217 36353.3,297.8,5620 55027.4,853,10215 26839.5,162,4649 8236.9,14,1300 16276.6,31,2776 8763,15,1385 27597.3,60,4165 7951.3,14,1129 22357.5,72,3690 14552.3,16,2389 18398.2,58,3168 8243.6,16,1342 77980.6,324,12650 24063.2,203,4107 10192.1,17,1744 3946.9,8,609 21610.1,69,3593 8824.6,11,1369 27286.8,156,4466 8190.7,13,1224 1683.8,6,345 8905.6,80,1490 17897.8,53,2879 8337.6,14,1375 32768.6,81,4876 93721.9,1232,13823 32419,99,4967 26123.5,156,4342 8645,8,1383 32184.2,117,5195 8241.7,13,1294 28078.8,212,4607 16656,54,2805 22401.7,30,3375 28791.7,76,5348 33602.8,112,5325 72761,607,12200 2901.5,6,575 38062.6,257,6482 42240.4,152,6736 42483.2,171,6573 42279.7,174,6931 81722,360,11793 40401.8,173,6283 40410.1,190,5725 32810.6,54,5203 8554.7,19,1340 45050.9,114,7240 32335.2,69,5306 10338.3,16,1622 44697.8,162,6733 40308,118,6565 101134,974,15698 6199.1,41,1156 32917.6,91,5153 42628,182,6499 56628.6,326,9673 32068.1,116,5053 8210.9,14,1433 47801.2,394,8282 40547.7,281,7043 42453.6,153,7017 16806.5,34,2509 17838.9,33,2851 38779.4,179,6400 34223.2,98,5279 59031.7,406,7940 42557.2,63,7034 30057.6,125,4886 42068.8,286,6776 22912.5,48,3591 10109.2,10,1668 45221.4,168,7615 67573.2,516,10551 58289.2,139,9663 29634.4,150,4974 1891Line truncated
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+559
View File
@@ -0,0 +1,559 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Palindromic Panama Sentences\n",
"\n",
"I [previously](http://norvig.com/palindrome.html) wrote a program to find long palindromic sentences. Starting with the sentence:\n",
"\n",
" A man, a plan, a canal, Panama\n",
" \n",
"the program added words to the middle, one comma-separated phrase at a time, backtracking when a choice didn't work out. The program is able to find palindromes of over 15,000 words that look like:\n",
"\n",
" A man, a plan, a caddy, Ore, Lee, ..., Steele, Roydd, a canal, Panama.\n",
"\n",
"In this notebook I revisit the problem, but this time progressing one *letter* at a time rather than one phrase. That should allow me, at each choice point, to pick the letter that is most likely to lead to a complete phrase,\n",
"perhaps leading to longer palindromes.\n",
"\n",
"# Phrase Dictionary\n",
"\n",
"First, I will build a dictionary of phrases. I start with a file, [npdict.txt](npdict.txt), which lists noun phrases &mdash;proper nouns like `'Panama'`, plural nouns like `'dogs'`, and two-word noun phrases like `'a plan'`. Capitalization, whitespace and punctuation don't count in palindromes, just letters, so I use the function `letters` to extract just the letters of each phrase and create a dict of entries like `{'donaldeknuth': 'Donald E. Knuth'}`. In addition, to decide what letter to choose next, I will want to know if a given sequence of letters forms a *prefix* or *suffix* of other words. For example, if I have formed the partial phrase `'am'`, then adding `'a'` gives us `'ama'`, which is a prefix of over 1000 phrases in the dictionary, but adding `'d'` gives us `'amd'`, which is not a prefix of any phrase, and thus can be rejected.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"! [ -e npdict.txt ] || curl -O http://norvig.com/npdict.txt"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.88 s, sys: 92.8 ms, total: 1.97 s\n",
"Wall time: 2.04 s\n"
]
},
{
"data": {
"text/plain": [
"'ok'"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from collections import Counter, deque\n",
"import re\n",
"\n",
"class PhraseDict(dict):\n",
" \"\"\"A dictionary of {letters: phrase}, such as {'donaldeknuth': 'Donald E. Knuth'}, with:\n",
" .prefixes: Counter of {'pre': n} where n is the number of keys that start with 'pre'\n",
" .suffixes: Counter of {'xes': n} where n is the number of keys that end with 'xes'\"\"\"\n",
" def __init__(self, phrases):\n",
" for phrase in phrases:\n",
" phrase = phrase.strip()\n",
" self[letters(phrase)] = phrase\n",
" self.prefixes = Counter(x for p in self for x in prefixes(p))\n",
" self.suffixes = Counter(x for p in self for x in suffixes(p))\n",
" \n",
"def prefixes(phrase): return [phrase[:i] for i in range(1, len(phrase) + 1)]\n",
"\n",
"def suffixes(phrase): return [phrase[-i:] for i in range(1, len(phrase) + 1)]\n",
"\n",
"def letters(phrase, sub=re.compile(r'[\\W]+').sub):\n",
" \"Remove all the non-letters from phrase; return lowercase version.\"\n",
" return sub('', phrase).lower()\n",
"\n",
"def test1():\n",
" assert prefixes('hello') == ['h', 'he', 'hel', 'hell', 'hello']\n",
" assert suffixes('hello') == ['o', 'lo', 'llo', 'ello', 'hello']\n",
" assert letters('a man') == 'aman'\n",
" assert letters('an elk') == 'anelk'\n",
" assert letters('Mr. T') == 'mrt'\n",
" assert letters('Donald E. Knuth') == 'donaldeknuth'\n",
" assert len(DICT) == 125512\n",
" assert 'panama' in DICT\n",
" assert 'aman' in DICT\n",
" assert 'threemen' not in DICT\n",
" assert DICT['acanal'] == 'a canal'\n",
" return 'ok'\n",
"\n",
"%time DICT = PhraseDict(open('npdict.txt'))\n",
"test1()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Search for Panama Palindromes\n",
"\n",
"Now the `Panama` class will search for a palindrome. I'll show the code first, then explain it."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'ok'"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import time\n",
"\n",
"class Panama:\n",
" \"\"\"Panama represents a palindrome, or a state in searching for one.\n",
" It has .left and .right to hold the phrases that are chosen,\n",
" and .L and .R to hold the current partial phrases in the middle (still working on these).\n",
" Also, a .set of all complete phrases, and the .dict of allowable phrases to choose from.\"\"\"\n",
" \n",
" def __init__(self, left=['aman', 'aplan'], L='aca', R='', right=['acanal', 'panama'], dict=DICT):\n",
" assert cat(left + [L]) == cat([R] + right)[::-1]\n",
" self.left = list(left) # list of complete phrases on left\n",
" self.L = L # an incomplete phrase on left\n",
" self.R = R # an incomplete phrase on right\n",
" self.right = deque(right) # deque of complete phrases on right\n",
" self.dict = dict # a {letters: actual_phrase} mapping\n",
" self.set = set(left + right) # a set of all complete phrases in palindrome\n",
" self.best = [] # list of phrases in longest palindrome found\n",
" self.Nshown = 0 # the number of phrases shown in the previous printout\n",
" self.i = 0 # the number of steps taken in the search\n",
" self.check()\n",
"\n",
" def __str__(self): return self.original_phrases(self.best)\n",
" \n",
" def original_phrases(self, phrases): return ', '.join(self.dict[phrase] for phrase in phrases)\n",
"\n",
" def search(self, steps=10**5):\n",
" \"\"\"Depth-first search for palindromes. From the current state, find all applicable actions.\n",
" Do the first one, and put on the stack reminders to undo it and try the others,\n",
" but first search deeper from the result of the first action.\"\"\"\n",
" self.t0 = time.time()\n",
" stack = [self.applicable_actions()]\n",
" for self.i in range(steps):\n",
" if not stack: \n",
" return\n",
" command = stack.pop()\n",
" if isinstance(command, UndoCommand):\n",
" self.undo(command)\n",
" elif command:\n",
" act = command.pop()\n",
" self.do(act)\n",
" self.check()\n",
" stack.extend([command, UndoCommand(act), self.applicable_actions()])\n",
" \n",
" def do(self, act):\n",
" \"Modify the current state by adding a letter, or finishing a phrase.\"\n",
" if act == ',': # finish phrase on left\n",
" self.set.add(self.L)\n",
" self.left.append(self.L)\n",
" self.L = ''\n",
" elif act == ';': # finish phrase on right\n",
" self.set.add(self.R)\n",
" self.right.appendleft(self.R)\n",
" self.R = ''\n",
" else: # add a letter\n",
" self.L = self.L + act \n",
" self.R = act + self.R\n",
" \n",
" def undo(self, act):\n",
" \"Modify the current state by undoing an action that was previously done.\"\n",
" if act == ',': # unfinish phrase on left\n",
" assert self.L == ''\n",
" self.L = self.left.pop()\n",
" self.set.remove(self.L)\n",
" elif act == ';': # unfinish phrase on right\n",
" assert self.R == ''\n",
" self.R = self.right.popleft()\n",
" self.set.remove(self.R)\n",
" else: # remove a letter\n",
" self.L = self.L[:-1]\n",
" self.R = self.R[1:]\n",
" \n",
" def check(self):\n",
" \"Check to see if current state is a palindrome, and if so, record it and maybe print.\"\n",
" if not self.is_palindrome(): return\n",
" N = len(self.left) + len(self.right) \n",
" if N > len(self.best):\n",
" self.best = self.left + list(self.right)\n",
" if N - self.Nshown > 1000 or N > 16000:\n",
" self.Nshown = N\n",
" print(self.report())\n",
" \n",
" def report(self):\n",
" N = len(self.best)\n",
" nwords = N + sum(self.dict[p].count(' ') for p in self.best)\n",
" nletters = sum(len(p) for p in self.best)\n",
" return ('Pal: {:6,d} phrases, {:6,d} words, {:6,d} letters ({:,d} steps, {} seconds)'\n",
" .format(N, nwords, nletters, self.i+1, round(time.time() - self.t0)))\n",
" \n",
" def applicable_actions(self):\n",
" L, R, D = self.L, self.R, self.dict\n",
" actions = []\n",
" def score(A): return D.prefixes[L+A] * D.suffixes[A+R]\n",
" if self.is_allowed(L):\n",
" actions.append(',')\n",
" if self.is_allowed(R):\n",
" actions.append(';')\n",
" for A in sorted(alphabet, key=score):\n",
" if score(A) > 0:\n",
" actions.append(A) \n",
" return actions\n",
" \n",
" def is_allowed(self, phrase): return phrase in self.dict and phrase not in self.set\n",
" \n",
" def is_palindrome(self): \n",
" \"Is this a palindrome? (Does any extra .L or .R match the other side?)\"\n",
" return ((self.L == '' and self.left[-1].endswith(self.R)) or \n",
" (self.R == '' and self.right[0].startswith(self.L)))\n",
"\n",
"alphabet = 'abcdefghijklmnopqrstuvwxyz'\n",
"cat = ''.join\n",
"UndoCommand = str\n",
"DoCommand = list\n",
" \n",
"def test2():\n",
" p1 = Panama()\n",
" assert p1.is_palindrome()\n",
" assert str(p1) == 'a man, a plan, a canal, Panama'\n",
" p2 = Panama(['aman','aplan'], 'acadd','dd', ['acanal', 'panama'])\n",
" assert not p2.is_palindrome()\n",
" p3 = Panama(['maya'], '', '', ['ayam'])\n",
" assert p3.is_palindrome()\n",
" assert str(p3) == 'Maya, a yam'\n",
" return 'ok'\n",
"\n",
"test2()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The main point of the `Panama` class is to allow the `.search()` method to search for a long palindrome. The [overall strategy](http://norvig.com/pal-alg.html#v3) is explained elsewhere. The search\n",
"adds a lett1er at a time to both left and right side, backtracking when necessary. The state of the search is represented by the four fields `.left, .L, .R, .right`, for example:\n",
" \n",
" A man, a plan, a cam, a yak, a yam, a canal, Panama\n",
" \n",
"gets encoded into the four fields like this:\n",
" \n",
" .left: ['aman', 'aplan', 'acam', 'ayak']\n",
" .L: ''\n",
" .R: 'k'\n",
" .right: ['ayam', 'acanal', 'panama']\n",
" \n",
"We always maintain the invariant:\n",
"\n",
" cat(left + [L]) == cat([R] + right)[::-1]\n",
" \n",
"The `search` method would be more straightforward if we could write it is a recursive function. But Python does not perform well when recursing to 100 thousand levels deep. So instead I manually manage a *stack* of *commands* that tell the search what to do and to undo. A command can be a `DoCommand`, which consists of a list of letters describing all the possible actions we could take at this point. Actions include letters that could be added (only the letters that make a legitimate prefix of a known phrase in `.L` and a legitimate suffix of a known phrase in `.R`). Actions can also include the character `','`, which is used to dignal that `.L` is a complete word and should be moved ontot he `.left` list, or `';'` to signal the same for `.R/.right`. A command can also be a `UndoCommand`, which consists of a single character; one that was previously chosen to be done. So the list of characters in a `DoCommand` constitutes a choice point; we first choose one, and continue deeper from there, but we put on the command stack an `UndoCommand` to reverse the effects of the action, and put back the remaining characters to try instead, if the first character doesn't work out. Note that we pop characters off the end of a `DoCommand`, so the last character is the first one tried.\n",
"\n",
"Let's see how it works:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pal: 1,001 phrases, 1,395 words, 6,246 letters (30,023 steps, 1 seconds)\n",
"Pal: 2,003 phrases, 2,802 words, 12,375 letters (112,614 steps, 3 seconds)\n",
"CPU times: user 5.61 s, sys: 17.4 ms, total: 5.63 s\n",
"Wall time: 5.65 s\n"
]
}
],
"source": [
"p = Panama()\n",
"%time p.search(200000)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Pal: 2,791 phrases, 3,876 words, 17,101 letters (200,000 steps, 4 seconds)'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p.report()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That seemed to go ok; let's try a longer search (100 million steps):"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pal: 1,001 phrases, 1,395 words, 6,246 letters (30,023 steps, 1 seconds)\n",
"Pal: 2,003 phrases, 2,802 words, 12,375 letters (112,614 steps, 2 seconds)\n",
"Pal: 3,004 phrases, 4,162 words, 18,341 letters (227,472 steps, 5 seconds)\n",
"Pal: 4,005 phrases, 5,507 words, 24,199 letters (386,226 steps, 8 seconds)\n",
"Pal: 5,019 phrases, 6,890 words, 30,137 letters (619,790 steps, 12 seconds)\n",
"Pal: 6,021 phrases, 8,235 words, 35,931 letters (877,024 steps, 17 seconds)\n",
"Pal: 7,023 phrases, 9,568 words, 41,593 letters (1,183,926 steps, 23 seconds)\n",
"Pal: 8,024 phrases, 10,878 words, 47,259 letters (1,561,761 steps, 30 seconds)\n",
"Pal: 9,026 phrases, 12,183 words, 52,965 letters (1,947,540 steps, 38 seconds)\n",
"Pal: 10,028 phrases, 13,467 words, 58,583 letters (2,480,848 steps, 48 seconds)\n",
"Pal: 11,029 phrases, 14,730 words, 64,117 letters (3,206,870 steps, 61 seconds)\n",
"Pal: 12,030 phrases, 16,018 words, 69,588 letters (4,044,269 steps, 77 seconds)\n",
"Pal: 13,031 phrases, 17,293 words, 74,967 letters (5,272,674 steps, 99 seconds)\n",
"Pal: 14,033 phrases, 18,519 words, 80,203 letters (7,091,166 steps, 133 seconds)\n",
"Pal: 15,034 phrases, 19,753 words, 85,307 letters (10,753,057 steps, 201 seconds)\n",
"Pal: 16,003 phrases, 20,886 words, 90,015 letters (27,084,693 steps, 506 seconds)\n",
"Pal: 16,004 phrases, 20,886 words, 90,015 letters (27,149,605 steps, 508 seconds)\n",
"Pal: 16,005 phrases, 20,890 words, 90,017 letters (27,316,698 steps, 511 seconds)\n",
"Pal: 16,009 phrases, 20,896 words, 90,039 letters (27,350,925 steps, 511 seconds)\n",
"Pal: 16,010 phrases, 20,896 words, 90,039 letters (27,412,633 steps, 513 seconds)\n",
"Pal: 16,011 phrases, 20,897 words, 90,045 letters (27,427,058 steps, 513 seconds)\n",
"Pal: 16,013 phrases, 20,898 words, 90,049 letters (27,567,249 steps, 516 seconds)\n",
"Pal: 16,014 phrases, 20,898 words, 90,046 letters (27,592,882 steps, 516 seconds)\n",
"Pal: 16,016 phrases, 20,900 words, 90,055 letters (27,595,464 steps, 516 seconds)\n",
"Pal: 16,019 phrases, 20,903 words, 90,070 letters (27,616,478 steps, 517 seconds)\n",
"Pal: 16,020 phrases, 20,904 words, 90,073 letters (27,670,352 steps, 518 seconds)\n",
"Pal: 16,022 phrases, 20,908 words, 90,081 letters (27,690,556 steps, 518 seconds)\n",
"Pal: 16,024 phrases, 20,908 words, 90,096 letters (27,750,047 steps, 519 seconds)\n",
"Pal: 16,025 phrases, 20,911 words, 90,097 letters (27,902,094 steps, 522 seconds)\n",
"Pal: 16,028 phrases, 20,916 words, 90,113 letters (27,913,484 steps, 523 seconds)\n",
"Pal: 16,029 phrases, 20,916 words, 90,119 letters (28,000,410 steps, 524 seconds)\n",
"Pal: 16,030 phrases, 20,916 words, 90,115 letters (28,199,063 steps, 528 seconds)\n",
"Pal: 16,033 phrases, 20,920 words, 90,131 letters (28,207,363 steps, 528 seconds)\n",
"Pal: 16,035 phrases, 20,924 words, 90,143 letters (28,222,377 steps, 529 seconds)\n",
"Pal: 16,036 phrases, 20,924 words, 90,145 letters (28,245,911 steps, 529 seconds)\n",
"Pal: 16,038 phrases, 20,928 words, 90,155 letters (28,319,574 steps, 530 seconds)\n",
"Pal: 16,039 phrases, 20,927 words, 90,165 letters (28,357,649 steps, 531 seconds)\n",
"Pal: 16,044 phrases, 20,933 words, 90,179 letters (28,398,480 steps, 532 seconds)\n",
"Pal: 16,045 phrases, 20,936 words, 90,187 letters (28,669,877 steps, 537 seconds)\n",
"Pal: 16,046 phrases, 20,936 words, 90,193 letters (28,756,803 steps, 538 seconds)\n",
"Pal: 16,047 phrases, 20,937 words, 90,195 letters (28,951,247 steps, 542 seconds)\n",
"Pal: 16,048 phrases, 20,937 words, 90,195 letters (29,168,799 steps, 546 seconds)\n",
"Pal: 16,049 phrases, 20,939 words, 90,197 letters (29,304,707 steps, 548 seconds)\n",
"Pal: 16,050 phrases, 20,940 words, 90,200 letters (29,402,659 steps, 550 seconds)\n",
"Pal: 16,055 phrases, 20,946 words, 90,223 letters (29,483,409 steps, 552 seconds)\n",
"Pal: 16,060 phrases, 20,951 words, 90,241 letters (29,703,353 steps, 556 seconds)\n",
"Pal: 16,062 phrases, 20,951 words, 90,257 letters (29,935,767 steps, 560 seconds)\n",
"Pal: 16,065 phrases, 20,957 words, 90,261 letters (30,401,372 steps, 568 seconds)\n",
"Pal: 16,066 phrases, 20,957 words, 90,267 letters (30,488,256 steps, 570 seconds)\n",
"Pal: 16,067 phrases, 20,961 words, 90,271 letters (30,742,860 steps, 575 seconds)\n",
"Pal: 16,068 phrases, 20,961 words, 90,277 letters (30,829,744 steps, 576 seconds)\n",
"Pal: 16,069 phrases, 20,965 words, 90,279 letters (31,091,412 steps, 581 seconds)\n",
"Pal: 16,070 phrases, 20,965 words, 90,285 letters (31,178,296 steps, 583 seconds)\n",
"Pal: 16,071 phrases, 20,969 words, 90,287 letters (31,452,702 steps, 588 seconds)\n",
"Pal: 16,072 phrases, 20,969 words, 90,293 letters (31,539,586 steps, 589 seconds)\n",
"Pal: 16,075 phrases, 20,973 words, 90,299 letters (31,899,517 steps, 597 seconds)\n",
"Pal: 16,077 phrases, 20,975 words, 90,305 letters (32,079,651 steps, 600 seconds)\n",
"Pal: 16,079 phrases, 20,978 words, 90,317 letters (32,418,194 steps, 606 seconds)\n",
"Pal: 16,080 phrases, 20,977 words, 90,327 letters (32,456,269 steps, 607 seconds)\n",
"Pal: 16,085 phrases, 20,983 words, 90,341 letters (32,497,076 steps, 608 seconds)\n",
"Pal: 16,087 phrases, 20,985 words, 90,351 letters (32,651,838 steps, 611 seconds)\n",
"Pal: 16,090 phrases, 20,986 words, 90,365 letters (32,782,954 steps, 613 seconds)\n",
"Pal: 16,091 phrases, 20,988 words, 90,363 letters (33,528,985 steps, 627 seconds)\n",
"Pal: 16,093 phrases, 20,992 words, 90,375 letters (33,696,891 steps, 630 seconds)\n",
"Pal: 16,094 phrases, 20,992 words, 90,381 letters (33,783,775 steps, 631 seconds)\n",
"Pal: 16,095 phrases, 20,992 words, 90,379 letters (34,151,848 steps, 638 seconds)\n",
"Pal: 16,096 phrases, 20,995 words, 90,389 letters (34,283,539 steps, 640 seconds)\n",
"Pal: 16,098 phrases, 20,996 words, 90,397 letters (34,342,030 steps, 642 seconds)\n",
"Pal: 16,099 phrases, 20,998 words, 90,397 letters (34,699,703 steps, 648 seconds)\n",
"Pal: 16,100 phrases, 21,001 words, 90,405 letters (36,050,560 steps, 673 seconds)\n",
"Pal: 16,102 phrases, 21,002 words, 90,413 letters (36,109,051 steps, 674 seconds)\n",
"Pal: 16,103 phrases, 21,004 words, 90,413 letters (36,466,496 steps, 680 seconds)\n",
"Pal: 16,105 phrases, 21,005 words, 90,411 letters (37,036,344 steps, 691 seconds)\n",
"Pal: 16,106 phrases, 21,004 words, 90,423 letters (76,003,720 steps, 1522 seconds)\n",
"Pal: 16,107 phrases, 21,006 words, 90,423 letters (76,361,384 steps, 1530 seconds)\n",
"Pal: 16,108 phrases, 21,009 words, 90,431 letters (77,712,187 steps, 1559 seconds)\n",
"Pal: 16,110 phrases, 21,010 words, 90,439 letters (77,770,678 steps, 1560 seconds)\n",
"Pal: 16,111 phrases, 21,012 words, 90,439 letters (78,128,114 steps, 1568 seconds)\n",
"CPU times: user 33min 11s, sys: 13 s, total: 33min 24s\n",
"Wall time: 33min 38s\n"
]
}
],
"source": [
"p = Panama()\n",
"%time p.search(10**8)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"a man, a plan, a caretaker, a moksha, Lufkin, a jacinth, Gile, Daniell, Ivanov, an odor, a negativeness, a tsarevna, melanomas, an ire, Gib, Olga, Lata Narayanan, a meld, an elecampane, an evonymus, an anatase, Rachael, an anabasis, an anaesthetics, an anamniote, an ani, DRE, Fredra, Cal, Parette, Serang, Isaacs, a ramark, IHD, a nonacademic, an obstet, Sellers, Sanalda, Erbe, Eba, an ane, Zaneski, Red, Erfurt, Sinai, Sale, Gene Roddenberry, a nonactive, Cenaean, Namangan, a mell, Everes, a nonanimal, a sanitarian, a non-American, Gat, Noman, an arista, Lattimer, a nonemendation, Eberta, Poe, LCT, an anaerobe, Ranita, Marella, an antired, ipsissima verba, a nan, a manille, Tiv, a nonet, an one, Kenney, a caret, Alon, Omar Epps, an analemma, an enami, Barents, Regen, A. H. Taub, a self, Farant, Caresse, RdAc, a nil, a nonrevocation, a melanite, Ranite, Canarese, NEbE, an allanite, Caressa, an all, IJssel, DNA, Carolee Carmello, Sara, Pareto, Oran, a mel, Dissenter, Cesare, Tema, an alt, Evslin, an alienist, anabases, a torpedoman, a meta, Garett, a csk, Cabaeus, an aloneness, Emelita, an alerion, Eleni, a cong, Ilan Adler, a keratoderma, an alg, Roe, generalities, a nonrateability, a hellebore, an anatta, Ranitta, petits, a baresark, unities, an iman, a non-Indian, a non-Israelite, Delano, Ita, Ranit, Ame, a hanaper, Tanah, an alarum, an alpha, Lacedaemonian, a tab, Cenac, an alette, Levona, an airbrasive, Lody, Nnadia Chan, a naira, an airstream, Macedon, an anatexis, an aileron, a nail, arts, UART, San, a nong, a morceau, dice, Delavan, a mete, Parent, Artemas, Esau, an ora, Hsingan, Amero, FAD, Naman, Amery, Bareilly, Hwang, Inanna, an aider, Acre, Vona, an ait, an aisle, a decare, billies, Banares, ARE, Nanakuli, codette, Lemon, a nonmember, a jaconet, an aigrette, Sang, Tanagra, Genet, a portal, an ore, Pip, a non-Uralian, Bohaty, a parent, Sikang, Amorette, Locrian, Cameron, Anna, Carole, Delaware, Temenus, an iterativeness, Enos, an aim, a danaite, Ahrens, Enarete, Mona, Marelda, an ataractic, a noise, lanais, anatexes, a noisette, Long, APS, Enalus, Omari, a natl, Usanis, an attestator, Gean, an oiliness, an oilskin, Numanus, Ari, Hellene, RCAF, a caresser, Pare, Tem, a torsel, a nat, a kokanee, LaSorella, Gipsies, Irby, an aperient, Mareld, an enallage, a decalcomania, an argali, an arbalester, an imagnableness, an arith, a nonsensitiveness, Anam, Kramer, a caress, a parens, Asel, Ujiji, Emelina, PMA, Carena, ELAS, an it's, Estella, Marela, Edana, Melba, Tsana, Mel, Bonar, a nidana, Meri, Fareham, an arum, a receivable, Gene Geter, a grammarian, Alanah, Ana, Hsian, a lenity, an aug, an oil, a decane, an arrest, Ibanez, an acosmist, an absorptance, a noisemaker, a metall, a gasser, an acuteness, Ender, an ossa, Maria, Teria, an acetal, a manilla, Marie, Benilda, Erb, a no-side, an amah, Drava, Lat, Ragan, Amer, O'Fallon, an amasser, Tai, Tini, Nanak, a nine, Leninabad, an enargite, Gilles, a benignancy, Lanam, an ami, Vanessa, LCI, Kikelia, Delamare, Terr, Efahan, a visitor, a nies, a nit, OFris, an amenorrhea, Laramie, Lipsey, an augend, Averell, a carelessness, oogoninia, Lad, a nose, Majel Barrett, an amen, AHSA, Lari, Oka, Hsi, Namara, an enate, Caren, a career, Gena, Jenna von Oy, a material, Lebanon, an abaca, an abalone, an abaser, a nebris, a benison, Aug, an osnaburg, a nosh, Tim, Selle, Baram, an aba, Hellen, a decal, a paretta, Marelya, Teri Polo, a predicate, leva, Warenne, R. Bayer, Fred Astaire, an abr, Unit, a legatee, RFA, Nananne, Kenner, Rawley, a greensand, Ronald, Barea, Benildas, an abb, inamoratas, an abuser, an illegalisation, a mess, a heller, a decapitation, a hello, Helles, Ignatia, Hsia-men, a hcl, a north, Gilda, Edan, a mesa, Barotse, Hts, an ill, a green, OPA, Ewan, a menu, Bret, epithalami, an adder, a nos, an ordainer, an adagio, Marion, a menses, a tsi, Perelman, a taker-in, Ume, Arp, an ord, a ceder, ACS, an oracy, Aneto, O'Kelley, Na-Dene, Lassell, a caramel, a derisiveness, a cudgel, an orang, Farewell, a beek, Sella, Geneva, Dela, Derian, a men, Arcadian, a meg, Rabassa, an adenoma, Gareth, Gilbart, Sandy, a hell, a mix, Elam, a danewort, an emanation, Matsys, a gala, Mario, Marilyn, a tob, an origan, a media, Sello, Cinerama, an asonia, Rania, Redeemer, an oddness, endia, Tsan, a medullation, a colin, a mee, RFD, a raft, a tsarina, Venita, an aulos, a ferret, Edna, Rgen, Alena, Idelle, Baras, an isotach, an oceanography, an orinasal, a bander, a jar, Tepic, a carabin, ANG, a mannan, A Mei, paralipses, ATC, Enola, a goner, a caracal, a spur-rowel, an agron, an oribi, an adenine, Lger, a wet, Agana, an agromania, Lehet, an oca, EdB, USAR, an ell, a veritas, an orca, Marena, Elgan, a morale, a m/s, inamoratos, a rasse, Irene, Csel, a dep, Ohara, a ballade, Paramatman, an agr, Efren, a ramie, Wenona, tubas, an ipm, a caravaner, a decanter, a nitrogen, a morn, Onalaska, a sinapine, LAC, Sellma, Lamas, an agar, Ihab, a rani, Artamas, an aga, Nana Visitor, anLine truncated
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"str(p)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Success! We found a palindrome that is longer than any found before. We can do about 50,000 steps per second. I'll stop here, but there are many things to try; feel free to explore on your own:\n",
"\n",
"- I order the actions by the product of the number of prefixes and number of suffixes formed. In other words, choose first the character that forms the most possible completions. But maybe, at least in the early goings, I should choose more difficult combinations of letters, to use up the \"hard\" phrases and save the \"easy\" phrases for later.\n",
"- I'm not sure when to end a word and when to try to continue to a longer word; experimentation here would be useful.\n",
"- The program is deterministic, and thus always finds the same plaindrome. that's boring; can some randomness be introduced?\n",
"- Can we make more interesting phrases? Include determiners other than \"a\" and \"an\"; include adjectives, etc.\n",
"- The counts of prefixes and suffixes include all the phrases in the dictionary. But we are not allowed to repeat a phrase, so can we modify the code to subtract one from the counts every time a phrase is used (and add onr back in when we backtrack over the phrase)?\n",
"\n",
"Perhaps you can find other areas to explore.\n",
"\n",
"# Indefinite Articles\n",
"\n",
"What if every noun phrase (except \"Panama\") has to start with an indefinite article, as in \"an X\" or \"a Y\"? Let's see:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"! [ -e anpdict-short.txt ] || curl -O http://norvig.com/anpdict-short.txt"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"DICT2 = PhraseDict(open('anpdict-short.txt'))"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pal: 1,001 phrases, 2,001 words, 5,649 letters (473,669 steps, 13 seconds)\n",
"CPU times: user 44min 43s, sys: 25.2 s, total: 45min 8s\n",
"Wall time: 46min 55s\n"
]
},
{
"data": {
"text/plain": [
"'Pal: 1,237 phrases, 2,473 words, 6,787 letters (100,000,000 steps, 2816 seconds)'"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p = Panama(dict=DICT2)\n",
"%time p.search(10**8)\n",
"p.report()"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"\"a man, a plan, a caret, a rec, a manana, an anabasis, a tsar, a tanager, an araban, a mat, an aromatic, an amadan, a melanoma, an anatman, an amowt, a barege, a jar, a tav, an arr, a parataxis, an amora, an agron, an ami, a caress, a paracasein, a salad, an allopath, a baronet, an all, a darer, a caravan, an alg, a ratal, an alfa, an agnate, an arg, an arcs, an arcanum, an air, an alp, a nail, a run-on, an aid, a ratan, an anabas, a pat, an ain, a baron, a manakin, a natator, a tarok, an attar, an apa, a nag, an animator, an apr, a tannin, a nap, a janitor, a carapa, an abaca, an atabal, an agal, an abb, a ratel, a salal, a harem, an anime, a hareem, an anim, a gala, a para, a maniac, an ann, a manit, a salep, a laniard, an acaridan, an aced, an adat, an abater, a lca, an amalaka, an aim, a daraf, an anion, a galliwasp, a caraway, a redan, a mow, a nae, a paretic, an avail, a guna, an iter, a nay, a daric, an aug, an origan, a mot, an orinasal, a barret, an ordn, an anil, a torc, an ord, a haram, a darts, an ani, a rater, an imaret, a rut, a sale, a taler, an orc, a nanowatt, an annot, an ore, an angina, a nit, a nos, a nonet, a nogg, a warb, an anus, a nog, a rap, a nome, a dater, a bat, a nom, a let, an ottar, an oil, a tale, a balata, an imam, a rate, a harim, an alapa, a nim, a lar, a cort, an oca, a nil, a saliva, an ips, an oxen, a nod, a rat, an alarum, an oak, an ital, a caracara, a hallah, a nipa, an elem, a nip, a latah, an isallobar, a core, an alias, an inner, a nib, a saloop, a nies, a carol, a varec, a radiov, an emagram, an etalon, a tuba, an evader, a nett, a parol, a jam, a lama, a net, a carob, a tamis, an egg, a lati, an alif, a neep, an eel, a ballot, an alley, an eyalet, a corban, a minah, a regal, a cariama, a nork, a nek, a wallaba, a nomad, an omasum, a lac, an ecad, an ogam, a lame, a haloid, a neb, an esu, a halo, a gallop, a nef, an uta, an okay, a radar, a fanegada, a var, a civ, a nunatak, a zap, an acarid, an upas, a pariah, an udal, a colat, a rabal, a cedar, a gallet, a nub, a retail, a kalam, an alegar, a geb, a retem, a gem, a redd, an alt, an alb, an attabal, a tepal, a timer, a redleg, a repp, an atlas, a ballup, a rep, a carioca, a nut, a resaw, a sal, a tip, a carrot, a resp, a lambda, a tat, a bal, a tot, a resin, a tob, a res, a radula, a tam, a tamas, an amatol, a removal, a sis, a rem, a gall, a carpus, a revel, a rev, a rad, a canalis, a bani, a rps, a recap, a rennet, a reed, a reel, a reek, a fanon, a canikin, a manioc, a reflet, a refl, a dep, a ref, a wall, a cerat, a tub, a reward, a reb, a saros, a vav, a rein, a pannus, a rex, a wanigan, a myriad, a rag, an amah, a rail, a rank, a raob, a rota, a tag, an amurca, a tass, a canoe, an assam, a catalpa, a tad, a room, a rood, a roll, a palais, a rod, a rim, a roc, a rial, a rip, a tapa, a jad, a catnap, a rub-a-dub, a tarp, a tanga, a tenaim, a rani, a tapis, a taps, a taka, a teff, a tap, a lanai, a snob, a tax, a taw, a tennis, a tew, a teg, a teel, a trapan, a mrem, a tram, a trad, a trot, a troop, a trid, a trf, a topaz, a galiot, a topsail, a manuf, a top, a catnip, a tool, a galop, a toom, a tonk, a tom-tom, a tog, a manway, a today, a rom, a titan, a trap, a tid, an assent, a pall, a mar, a bec, a fan, a lad, a til, a tum, a gal, a noma, a tef, a tug, a tufa, a tolan, a rasa, a margay, a datum, a tsi, a waterfall, a far, a mac, a jass, a lag, a batik, a tpd, a sard, a matins, a sav, a samaj, a tabard, a saga, a mallam, a dam, a sakai, a rum, a sari, a palikar, a misaim, a kalema, a mam, a sima, a mag, an assoc, a sumac, a lila, a molar, a peh, a sum, a ramrod, a supp, a pataca, a moc, a matt, a wadi, a matsu, a haeres, a sup, a subah, a lap, a calix, an ass, a pastel, a sepal, a mahaleb, a tabu, a dat, a cuda, a mob, a sex, an ashcan, a rub, a jasey, a strops, a stob, a sora, a minimax, a satsuma, a mix, a mass, a sadi, a ragi, a tab, a lulav, a lay, a wad, a cyc, a sorb, a soda, a mud, a sop, a snug, a slag, a scf, a lack, a laic, a fats, a cassaba, a recast, a hwan, a cep, a laud, a lev, a rayah, a leek, a vat, a gam, a slab, a leg, a baas, a camaca, a ruse, a camoca, a rug, an asb, an aery, an agnail, a lex, an abac, a leud, a liard, a lip, a lied, a lid, a loofah, a loom, a loca, a ripsaw, a swob, a lob, a lub, a bayard, a lyc, a nabob, a cadi, a sag, a dagoba, a lek, an app, a yapok, a dargah, a mib, a daud, a dap, a dab, a dig, a draw, an actg, a drib, a drub, a def, a deb, a dewan, a diam, a dopa, a lune, a papaya, a lug, a doom, a dorp, a dom, a doh, a dog, a doc, a duc, a marga, a liza, a capa, a cup, a marc, a madrigal, a pipa, a babassu, a gad, a dak, an oka, a belga, a grub, a maya, a gib, a map, a mesa, a goy, a mug, a misc, a fat, a qadi, a cay, a sap, a papaw, a myg, a mpg, a pagan, a gob, a powan, a wog, a pika, a pup, a pig, a proc, a peek, a plug, a pcf, a cav, a caf, a cig, a may, a tagrag, a garb, a gaw, a guv, a bob, a kabaka, a dis, a qaid, a kaki, a pay, a bai, a law, a rLine truncated
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"str(p)"
]
}
],
"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.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
File diff suppressed because it is too large. Load diff
+1220
View File
File diff suppressed because it is too large. Load diff
+428
View File
@@ -0,0 +1,428 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def partition(covers):\n",
" # covers: {w: {r,...}}\n",
" # invcovers: {r: {w,...}}\n",
" pass\n",
"\n",
"def connected(w, covers, invcovers, result):\n",
" if w not in result:\n",
" result.add(w)\n",
" for r in covers[w]:\n",
" for w2 in invcovers[r]:\n",
" connected(w2, covers, invcovers, result)\n",
" return result\n",
"\n",
"for (W, L, legend) in ALL:\n",
" covers = eliminate_dominated(regex_covers(W, L))\n",
" invcovers = invert_multimap(covers)\n",
" start = list(covers)[2]\n",
" P = connected(start, covers, invcovers, set())\n",
" print legend, len(P), len(covers), len(covers)-len(P)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finding Shorter Regexes: Trying Multiple Times\n",
"----\n",
" \n",
"Why run just two versions of `findregex`? Why not run 1000 variations, and then pick the best solution? Of course, I don't want to write 1000 different functions by hand; I want an automated way of varying each run. I can think of three easy things to vary:\n",
" \n",
"* The number '4' in the `score` function. That is, vary the tradeoff between number of winners matched and number of characters.\n",
"* The tie-breaker. In case of a tie, Python's `max` function always picks the first one. Let's make it choose a different 'best' regex from among all those that tie.\n",
"* The greediness. Don't be so greedy (picking the best) every time. Occasionally pick a not-quite-best component, and see if that works out better.\n",
" \n",
"The first of these is easy; we just use the `random.choice` function to choose an integer, `K`, to serve as the tradeoff factor. \n",
"\n",
"The second is easy too. We could write an alternative to the `max` function, say `max_random_tiebreaker`. That would work, but an easier approach is to build the tiebreaker into the `score` function. In addition to awarding points for matching winners and the number of characters, we will have add in a tiebreaker: a random number between 0 and 1. Since all the scores are otherwise integers, this will not change the order of the scores, but it will break ties.\n",
"\n",
"The third we can accomplish by allowing the random factor to be larger than 1 (allowing us to pick a component that is not the shortest) or even larger than `K` (allowing us to pick a component that does not cover the most winners). \n",
" \n",
"I will factor out the function `greedy_search` to do a single computation oof a covering regex, while keeping the name `findregex` for the top level function that now calls `greedy_search` 1000 times and chooses the best (shortest length) result."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"def findregex(winners, losers, tries=1000):\n",
" \"Find a regex that matches all winners but no losers (sets of strings).\"\n",
" # Repeatedly call 'findregex1' the given number of tries; pick the shortest result\n",
" covers = regex_covers(winners, losers)\n",
" results = [greedy_search(winners, covers) for _ in range(tries)]\n",
" return min(results, key=len)\n",
"\n",
"def greedy_search(winners, covers):\n",
" # On each iteration, add the 'best' component in covers to 'result',\n",
" # remove winners covered by best, and remove from 'pool' any components\n",
" # that no longer match any remaining winners.\n",
" winners = set(winners) # Copy input so as not to modify it.\n",
" pool = set(covers)\n",
" result = []\n",
" \n",
" def matches(regex, strings): return {w for w in covers[regex] if w in strings}\n",
" \n",
" K = random.choice((2, 3, 4, 4, 5, 6))\n",
" T = random.choice((1., 1.5, 2., K+1., K+2.))\n",
" def score(c): \n",
" return K * len(matches(c, winners)) - len(c) + random.uniform(0., T)\n",
" \n",
" while winners:\n",
" best = max(pool, key=score)\n",
" result.append(best)\n",
" winners -= covers[best]\n",
" pool -= {c for c in pool if covers[c].isdisjoint(winners)}\n",
" return OR(result)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def factorial1(n):\n",
" if (n <= 1):\n",
" return 1\n",
" else:\n",
" return n * factorial1(n-1)\n",
"\n",
"def factorial2(n, partial_solution=1):\n",
" if (n <= 1):\n",
" return partial_solution\n",
" else:\n",
" return factorial2(n-1, n * partial_solution)\n",
" \n",
"assert factorial1(6) == factorial2(6) == 720"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def findregex(winners, losers, calls=100000):\n",
" \"Find the shortest disjunction of regex components that covers winners but not losers.\"\n",
" covers = regex_covers(winners, losers)\n",
" best = '^(' + OR(winners) + ')$'\n",
" state = Struct(best=best, calls=calls)\n",
" return bb_search('', covers, state).best\n",
"\n",
"def bb_search(regex, covers, state):\n",
" \"\"\"Recursively build a shortest regex from the components in covers.\"\"\"\n",
" if state.calls > 0:\n",
" state.calls -= 1\n",
" regex, covers = simplify_covers(regex, covers)\n",
" if not covers:\n",
" state.best = min(regex, state.best, key=len)\n",
" elif len(OR2(regex, min(covers, key=len))) < len(state.best):\n",
" # Try with and without the greedy-best component\n",
" def score(c): return 4 * len(covers[c]) - len(c)\n",
" best = max(covers, key=score)\n",
" covered = covers[best]\n",
" covers.pop(best)\n",
" bb_search(OR2(regex, best), {c:covers[c]-covered for c in covers}, state)\n",
" bb_search(regex, covers, state)\n",
" return state\n",
"\n",
"class Struct(object):\n",
" \"A mutable structure with specified fields and values.\"\n",
" def __init__(self, **kwds): vars(self).update(kwds)\n",
" def __repr__(self): return '<%s>' % vars(self)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def findregex(winners, losers, calls=100000):\n",
" \"Find the shortest disjunction of regex components that covers winners but not losers.\"\n",
" covers = regex_covers(winners, losers)\n",
" solution = '^(' + OR(winners) + ')$'\n",
" solution, calls = bb_search('', covers, solution, calls)\n",
" return solution\n",
"\n",
"def bb_search(regex, covers, solution, calls):\n",
" \"\"\"Recursively build a shortest regex from the components in covers.\"\"\"\n",
" if calls > 0:\n",
" calls -= 1\n",
" regex, covers = simplify_covers(regex, covers)\n",
" if not covers: # Solution is complete\n",
" solution = min(regex, solution, key=len)\n",
" elif len(OR2(regex, min(covers, key=len))) < len(solution):\n",
" # Try with and without the greedy-best component\n",
" def score(c): return 4 * len(covers[c]) - len(c)\n",
" r = max(covers, key=score) # Best component\n",
" covered = covers[r] # Set of winners covered by r\n",
" covers.pop(r)\n",
" solution, calls = bb_search(OR2(regex, r), \n",
" {c:covers[c]-covered for c in covers}, \n",
" solution, calls)\n",
" solution, calls = bb_search(regex, covers, solution, calls)\n",
" return solution, calls"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def findregex(winners, losers, calls=100000):\n",
" \"Find the shortest disjunction of regex components that covers winners but not losers.\"\n",
" global SOLUTION, CALLS\n",
" SOLUTION = '^(' + OR(winners) + ')$'\n",
" CALLS = calls\n",
" return bb_search(None, regex_covers(winners, losers))\n",
"\n",
"def bb_search(regex, covers):\n",
" \"\"\"Recursively build a shortest regex from the components in covers.\"\"\"\n",
" global SOLUTION, CALLS\n",
" CALLS -= 1\n",
" regex, covers = simplify_covers(regex, covers)\n",
" if not covers: # Solution is complete\n",
" SOLUTION = min(regex, SOLUTION, key=len)\n",
" elif CALLS >= 0 and len(OR(regex, min(covers, key=len))) < len(SOLUTION):\n",
" # Try with and without the greedy-best component\n",
" def score(c): return 4 * len(covers[c]) - len(c)\n",
" r = max(covers, key=score) # Best component\n",
" covered = covers[r] # Set of winners covered by r\n",
" covers.pop(r)\n",
" bb_search(OR(regex, r), {c:covers[c]-covered for c in covers})\n",
" bb_search(regex, covers)\n",
" return SOLUTION\n",
" \n",
"def OR(*regexes):\n",
" \"OR together regexes. Ignore 'None' components.\"\n",
" return '|'.join(r for r in regexes if r is not None)\n",
"\n",
"\n",
"def invert_multimap(multimap):\n",
" result = collections.defaultdict(list)\n",
" for key in multimap:\n",
" for val in multimap[key]:\n",
" result[val].append(key)\n",
" return result"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"## For debugging\n",
"\n",
"def findregex(winners, losers, calls=100000):\n",
" \"Find the shortest disjunction of regex components that covers winners but not losers.\"\n",
" solution = '^(' + OR(winners) + ')$'\n",
" covers = regex_covers(winners, losers)\n",
" b = BranchBound(solution, calls)\n",
" b.search(None, covers)\n",
" print b.calls, 'calls', len(b.solution), 'len'\n",
" return b.solution\n",
"\n",
"\n",
"def triage_covers(partial, covers):\n",
" \"Simplify covers by eliminating dominated regexes, and picking ones that uniquely cover a winner.\"\n",
" previous = None\n",
" while covers != previous:\n",
" previous = covers\n",
" # Eliminate regexes that are dominated by another regex\n",
" covers = eliminate_dominated(covers) # covers = {regex: {winner,...}}\n",
" coverers = invert_multimap(covers) # coverers = {winner: {regex,...}}\n",
" # For winners covered by only one component, move winner from covers to regex\n",
" singletons = {coverers[w][0] for w in coverers if len(coverers[w]) == 1}\n",
" if singletons:\n",
" partial = OR(partial, OR(singletons))\n",
" covered = {w for c in singletons for w in covers[c]}\n",
" covers = {c:covers[c]-covered for c in covers if c not in singletons}\n",
" return partial, covers\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
", and to , who suggested looking at [WFSTs](http://www.openfst.org/twiki/bin/view/FST/WebHome)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def regex_covers(winners, losers):\n",
" \"\"\"Generate regex components and return a dict of {regex: {winner...}}.\n",
" Each regex matches at least one winner and no loser.\"\"\"\n",
" losers_str = '\\n'.join(losers)\n",
" wholes = {'^'+winner+'$' for winner in winners}\n",
" parts = {d for w in wholes for p in subparts(w) for d in dotify(p)}\n",
" chars = set(cat(winners))\n",
" pairs = {A+'.'+rep_char+B for A in chars for B in chars for rep_char in '+*?'}\n",
" reps = {r for p in parts for r in repetitions(p)}\n",
" pool = wholes | parts | pairs | reps \n",
" searchers = [re.compile(c, re.MULTILINE).search for c in pool]\n",
" covers = {r: set(filter(searcher, winners)) \n",
" for (r, searcher) in zip(pool, searchers)\n",
" if not searcher(losers_str)}\n",
" covers = eliminate_dominated(covers)\n",
" return covers\n",
" return add_character_class_components(covers)\n",
"\n",
"def add_character_class_components(covers):\n",
" for (B, Ms, E) in combine_splits(covers):\n",
" N = len(Ms)\n",
" or_size = N*len(B+'.'+E) + N-1 # N=3 => 'B1E|B2E|B3E'\n",
" class_size = len(B+'[]'+E) + N # N=3 => 'B[123]E'\n",
" winners = {w for m in Ms for w in Ms[m]}\n",
" if class_size < or_size:\n",
" covers[B + make_char_class(Ms) + E] = winners\n",
" return covers\n",
"\n",
"def split3(word):\n",
" \"Splits a word into 3 parts, all ways, with middle part having 0 or 1 character.\"\n",
" return [(word[:i], word[i:i+L], word[i+L:]) \n",
" for i in range(len(word)+1) for L in (0, 1)\n",
" if not word[i:i+L].startswith(('.', '+', '*', '?'))]\n",
"\n",
"def combine_splits(covers):\n",
" \"Convert covers = {BME: {w...}} into a list of [(B, {M...}, E, {w...}].\"\n",
" table = collections.defaultdict(dict) # table = {(B, E): {M: {w...}}}\n",
" for r in covers:\n",
" for (B, M, E) in split3(r):\n",
" table[B, E][M] = covers[r]\n",
" return [(B, Ms, E) for ((B, E), Ms) in table.items()\n",
" if len(Ms) > 1]\n",
"\n",
"def make_char_class(chars):\n",
" chars = set(chars)\n",
" return '[%s]%s' % (cat(chars), ('?' if '' in chars else ''))\n",
"\n",
"covers = regex_covers(boys, girls)\n",
"old = set(covers)\n",
"print len(covers)\n",
"covers = add_character_class_components(covers)\n",
"print len(covers)\n",
"print set(covers) - old\n",
"\n",
"print dict(combine_splits({'..a': {1,2,3}, '..b': {4,5,6}, '..c':{7}}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Consider the two components `'..a'` and `'..b'`. If we wanted to cover all the winners that both of these match, we could use `'..a|..b'`, or we could share the common prefix and introduce a *character class* to get `'..[ab]'`. Since the former is 7 characters and the later is only 6, the later would be preferred. It would be an even bigger win to replace `'..az|..bz|..cz'` with `'..[abc]z'`; that reduces the count from 14 to 8. Similarly, replacing `'..az|..bz|..z'` with `'..[ab]?z'` saves 5 characters.\n",
"\n",
"There seems to be potential savings with character classes. But how do we know which characters from which components to combine into classes? To keep things from getting out of control, I'm going to only look at components that are left after we eliminate dominated. That is not an ideal approach&mdash;there may well be some components that are dominated on their own, but could be part of an optimal solution when combined with other components into a character class. But I'm going to keep it simple."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"Searching: Better Bounds\n",
"----\n",
"\n",
"Branch and bound prunes the search tree whenever it is on a branch that is guaranteed to result in a solution that is no better than the best solution found so far. Currently we estimate the best possible solution along the current branch by taking the length of the partial solution and adding the length of the shortest component in `covers`. We do that because we know for sure that we need at least one component, but we don't know for sure how many components we'll need (nor how long each of them will be. So our estimate is often severely underestimates the true answer, which means we don't cut off search some places where we could, if only we had a better estimate.\n",
" \n",
"Here's one way to get a better bound. We'll define the following quantities:\n",
"\n",
"+ *P* = the length of the partial solution, plus the \"|\", if needed. So if the partial solution is `None`, then *P* will be zero, otherwise *P* is the length plus 1.\n",
"+ *S* = the length of the shortest regex component in `covers`.\n",
"+ *W* = the number of winners still in `covers`.\n",
"+ *C* = the largest number of winners covered by any regex in `covers`.\n",
"\n",
"If we assume The current estimate is *P* + *S*. We can see that a better estimate is *P* + *S* &times; ceil(*W* / *C*)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import math\n",
"\n",
"class BranchBound(object):\n",
" \"Hold state information for a branch and bound search.\"\n",
" def __init__(self, solution, calls):\n",
" self.solution, self.calls = solution, calls\n",
" \n",
" def search(self, covers, partial=None):\n",
" \"Recursively extend partial regex until it matches all winners in covers.\"\n",
" if self.calls <= 0: \n",
" return self.solution\n",
" self.calls -= 1\n",
" covers, partial = simplify_covers(covers, partial)\n",
" if not covers: # Nothing left to cover; solution is complete\n",
" self.solution = min(partial, self.solution, key=len)\n",
" else:\n",
" P = 0 if not partial else len(partial) + 1\n",
" S = len(min(covers, key=len))\n",
" C = max(len(covers[r]) for r in covers)\n",
" W = len(set(w for r in covers for w in covers[r]))\n",
" if P + S * math.ceil(W / C) < len(self.solution):\n",
" # Try with and without the greedy-best component\n",
" def score(r): return 4 * len(covers[r]) - len(r)\n",
" r = max(covers, key=score) # Best component\n",
" covered = covers[r] # Set of winners covered by r\n",
" covers.pop(r)\n",
" self.search({c:covers[c]-covered for c in covers}, OR(partial, r))\n",
" self.search(covers, partial)\n",
" return self.solution"
]
}
],
"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.5.0"
}
},
"nbformat": 4,
"nbformat_minor": 0
}