Add subdirectories
Add /ipynb/ and /py/ subdirectories to keep the home page neater.
This commit is contained in:
3226
ipynb/Advent of Code.ipynb
Normal file
3226
ipynb/Advent of Code.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
1966
ipynb/BASIC.ipynb
Normal file
1966
ipynb/BASIC.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
1070
ipynb/Beal.ipynb
Normal file
1070
ipynb/Beal.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
184
ipynb/Bike Speed versus Grade.ipynb
Normal file
184
ipynb/Bike Speed versus Grade.ipynb
Normal file
File diff suppressed because one or more lines are too long
1202
ipynb/Cheryl-and-Eve.ipynb
Normal file
1202
ipynb/Cheryl-and-Eve.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
504
ipynb/Cheryl.ipynb
Normal file
504
ipynb/Cheryl.ipynb
Normal 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*—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—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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
1191
ipynb/Coin Flip.ipynb
Normal file
1191
ipynb/Coin Flip.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
2039
ipynb/Convex Hull.ipynb
Normal file
2039
ipynb/Convex Hull.ipynb
Normal file
File diff suppressed because one or more lines are too long
2867
ipynb/Countdown.ipynb
Normal file
2867
ipynb/Countdown.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
1392
ipynb/Differentiation.ipynb
Normal file
1392
ipynb/Differentiation.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
1500
ipynb/Economics.ipynb
Normal file
1500
ipynb/Economics.ipynb
Normal file
File diff suppressed because one or more lines are too long
1477
ipynb/Fred Buns.ipynb
Normal file
1477
ipynb/Fred Buns.ipynb
Normal file
File diff suppressed because one or more lines are too long
2111
ipynb/Gesture Typing.ipynb
Normal file
2111
ipynb/Gesture Typing.ipynb
Normal file
File diff suppressed because one or more lines are too long
1231
ipynb/Ghost.ipynb
Normal file
1231
ipynb/Ghost.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
1326
ipynb/Golomb-Puzzle.ipynb
Normal file
1326
ipynb/Golomb-Puzzle.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
1196
ipynb/How To Count Things.ipynb
Normal file
1196
ipynb/How To Count Things.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
2307
ipynb/How to Do Things with Words.ipynb
Normal file
2307
ipynb/How to Do Things with Words.ipynb
Normal file
File diff suppressed because one or more lines are too long
836
ipynb/Life.ipynb
Normal file
836
ipynb/Life.ipynb
Normal 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—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
|
||||
}
|
||||
1075
ipynb/Mean Misanthrope Density.ipynb
Normal file
1075
ipynb/Mean Misanthrope Density.ipynb
Normal file
File diff suppressed because one or more lines are too long
534
ipynb/Palindrome.ipynb
Normal file
534
ipynb/Palindrome.ipynb
Normal 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
|
||||
}
|
||||
3322
ipynb/Probability.ipynb
Normal file
3322
ipynb/Probability.ipynb
Normal file
File diff suppressed because one or more lines are too long
3102
ipynb/ProbabilityParadox.ipynb
Normal file
3102
ipynb/ProbabilityParadox.ipynb
Normal file
File diff suppressed because one or more lines are too long
1011
ipynb/Project Euler Utils.ipynb
Normal file
1011
ipynb/Project Euler Utils.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
483
ipynb/PropositionalLogic.ipynb
Normal file
483
ipynb/PropositionalLogic.ipynb
Normal 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—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
ipynb/Riddler Battle Royale.ipynb
Normal file
868
ipynb/Riddler Battle Royale.ipynb
Normal file
File diff suppressed because one or more lines are too long
697
ipynb/SET.ipynb
Normal file
697
ipynb/SET.ipynb
Normal 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
ipynb/Scrabble.ipynb
Normal file
13278
ipynb/Scrabble.ipynb
Normal file
File diff suppressed because one or more lines are too long
2087
ipynb/Sicherman Dice.ipynb
Normal file
2087
ipynb/Sicherman Dice.ipynb
Normal file
File diff suppressed because one or more lines are too long
335
ipynb/Sierpinski.ipynb
Normal file
335
ipynb/Sierpinski.ipynb
Normal file
File diff suppressed because one or more lines are too long
209
ipynb/Snobol.ipynb
Normal file
209
ipynb/Snobol.ipynb
Normal 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
|
||||
}
|
||||
1275
ipynb/Sudoku IPython Notebook.ipynb
Normal file
1275
ipynb/Sudoku IPython Notebook.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
4651
ipynb/TSP.ipynb
Normal file
4651
ipynb/TSP.ipynb
Normal file
File diff suppressed because one or more lines are too long
871
ipynb/WWW.ipynb
Normal file
871
ipynb/WWW.ipynb
Normal 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
ipynb/dist-climb-time.csv
Normal file
1
ipynb/dist-climb-time.csv
Normal file
File diff suppressed because one or more lines are too long
BIN
ipynb/money.png
Normal file
BIN
ipynb/money.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
559
ipynb/pal3.ipynb
Normal file
559
ipynb/pal3.ipynb
Normal file
File diff suppressed because one or more lines are too long
3706
ipynb/xkcd1313-part2.ipynb
Normal file
3706
ipynb/xkcd1313-part2.ipynb
Normal file
File diff suppressed because one or more lines are too long
1220
ipynb/xkcd1313.ipynb
Normal file
1220
ipynb/xkcd1313.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
428
ipynb/xkxd-part3.ipynb
Normal file
428
ipynb/xkxd-part3.ipynb
Normal 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—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* × 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
|
||||
}
|
||||
Reference in New Issue
Block a user