Merge pull request #2 from norvig/master

Update from master
This commit is contained in:
Luis San Martin authored and GitHub committed 2018-08-17 17:36:29 +02:00
commit 540ac84332
26 files changed
+27870 -18796

No files matched your search

+32 -14
View File
@@ -8,43 +8,59 @@ Some are in Jupyter (IPython) notebooks, some in `.py` files. You can view the f
# Index of Jupyter (IPython) Notebooks
|Logic and Number Puzzles|
|Programming Examples|
|---|
|[Advent of Code 2017](https://github.com/norvig/pytudes/blob/master/ipynb/Advent%202017.ipynb)<br>*Puzzle site with a coding puzzle each day for Advent 2017.*|
|[Advent of Code 2016](https://github.com/norvig/pytudes/blob/master/ipynb/Advent%20of%20Code.ipynb)<br>*Puzzle site with a coding puzzle each day for Advent 2016*.|
|[Project Euler Utilities](https://github.com/norvig/pytudes/blob/master/ipynb/Project%20Euler%20Utils.ipynb)<br>*My utility functions for the Project Euler problems, including `Primes` and `Factors`.*|
|[Translating English Sentences into Propositional Logic Statements](https://github.com/norvig/pytudes/blob/master/ipynb/PropositionalLogic.ipynb)<br>*Automatically converting informal English sentences into formal Propositional Logic.*|
|[The Puzzle of the Misanthropic Neighbors](https://github.com/norvig/pytudes/blob/master/ipynb/Mean%20Misanthrope%20Density.ipynb)<br>*How crowded will this neighborhood be, if nobody wants to live next door to anyone else?*|
|[Countdown to 2016](https://github.com/norvig/pytudes/blob/master/ipynb/Countdown.ipynb)<br>*Solving the equation 10 _ 9 _ 8 _ 7 _ 6 _ 5 _ 4 _ 3 _ 2 _ 1 = 2016. From an Alex Bellos puzzle.*|
|[Sicherman Dice](https://github.com/norvig/pytudes/blob/master/ipynb/Sicherman%20Dice.ipynb)<br>*Find a pair of dice that is like a regular pair of dice, only different.*|
|[Beal's Conjecture Revisited](https://github.com/norvig/pytudes/blob/master/ipynb/Beal.ipynb)<br>*A search for counterexamples to Beal's Conjecture*|
|[WWW: Who WIll Win (NBA Title)?](https://github.com/norvig/pytudes/blob/master/ipynb/WWW.ipynb)<br>*Computing the probability of winning the NBA title, for my home town Warriors, or any other team.*|
|[Pickleball Tournament](https://github.com/norvig/pytudes/blob/master/ipynb/Pickleball.ipynb)<br>*Scheduling a doubles tournament fairly and efficiently.*|
|[Conway's Game of Life](https://github.com/norvig/pytudes/blob/master/ipynb/Life.ipynb)<br>*The cellular automata zero-player game.*|
|[A Chaos Game with Triangles](https://github.com/norvig/pytudes/blob/master/ipynb/Sierpinski.ipynb)<br>*A surprising appearance of the Sierpinski triangle in a random walk between vertexes.*|
|[Generating Mazes](https://github.com/norvig/pytudes/blob/master/ipynb/Maze.ipynb)<br>*Make a maze by generating a random tree superimposed on a grid.*|
|Logic and Number Puzzles|
|---|
|[When is Cheryl's Birthday?](https://github.com/norvig/pytudes/blob/master/ipynb/Cheryl.ipynb)<br>*Solving the "Cheryl's Birthday" logic puzzle.*|
|[When Cheryl Met Eve: A Birthday Story](https://github.com/norvig/pytudes/blob/master/ipynb/Cheryl-and-Eve.ipynb)<br>*Inventing new puzzles in the Style of Cheryl's Birthday.*|
|[The Devil and the Coin Flip Game](https://github.com/norvig/pytudes/blob/master/ipynb/Coin%20Flip.ipynb)<br>*How to beat the Devil at his own game.*|
|[The Puzzle of the Misanthropic Neighbors](https://github.com/norvig/pytudes/blob/master/ipynb/Mean%20Misanthrope%20Density.ipynb)<br>*How crowded will this neighborhood be, if nobody wants to live next door to anyone else?*|
|[Four 4s, Five 5s, and Countdown to 2016](https://github.com/norvig/pytudes/blob/master/ipynb/Countdown.ipynb)<br>*Solving the equation 10 _ 9 _ 8 _ 7 _ 6 _ 5 _ 4 _ 3 _ 2 _ 1 = 2016. From an Alex Bellos puzzle.*|
|[Sicherman Dice](https://github.com/norvig/pytudes/blob/master/ipynb/Sicherman%20Dice.ipynb)<br>*Find a pair of dice that is like a regular pair of dice, only different.*|
|[Sol Golomb's Rectangle Puzzle](https://github.com/norvig/pytudes/blob/master/ipynb/Golomb-Puzzle.ipynb)<br>*A Puzzle involving placing rectangles of different sizes inside a square. Bonus: cryptarithmetic.*|
|[WWW: Will Warriors Win?](https://github.com/norvig/pytudes/blob/master/ipynb/WWW.ipynb)<br>*Golden State Warriors probability of winning the 2016 NBA title.*|
|[The Riddler: Battle Royale](https://github.com/norvig/pytudes/blob/master/ipynb/Riddler%20Battle%20Royale.ipynb)<br>*A puzzle involving allocating your troops and going up against an opponent.*|
|Word Games|
|---|
|[xkcd 1970: Name Dominoes](https://github.com/norvig/pytudes/blob/master/ipynb/xkcd-Name-Dominoes.ipynb)<br>*Lay out dominoes legally; the dominoes have people names, not numbers.*|
|[Ghost](https://github.com/norvig/pytudes/blob/master/ipynb/Ghost.ipynb)<br>*The word game Ghost (add letters, try to avoid making a word).*|
|[World's Longest Palindrome](https://github.com/norvig/pytudes/blob/master/ipynb/pal3.ipynb)<br>*Searching for a long Panama-style palindrome, this time letter-by-letter.*|
|[Refactoring a Crossword Game Program](https://github.com/norvig/pytudes/blob/master/ipynb/Scrabble.ipynb)<br>*Refactoring the Scrabble / Word with Friends game from Udacity 212.*|
|[xkcd 1313: Regex Golf](https://github.com/norvig/pytudes/blob/master/ipynb/xkcd1313.ipynb)<br>*Find the smallest regular expression; inspired by Randall Monroe.*|
|[xkcd 1313: Regex Golf (Part 2: Infinite Problems)](https://github.com/norvig/pytudes/blob/master/ipynb/xkcd1313-part2.ipynb)<br>*Regex Golf: better, faster, funner. With Stefan Pochmann.*|
|[Let's Code About Bike Locks](https://github.com/norvig/pytudes/blob/master/ipynb/Fred%20Buns.ipynb)<br>*A tale of a bicycle combination lock that uses letters instead of digits. Inspired by Bike Snob NYC.*|
|[Gesture Typing](https://github.com/norvig/pytudes/blob/master/ipynb/Gesture%20Typing.ipynb)<br>*What word has the longest path on a gesture-typing smartphone keyboard? Inspired by Nicolas Schank and Shumin Zhai.*|
|[Gesture Typing](https://github.com/norvig/pytudes/blob/master/ipynb/Gesture%20Typing.ipynb)<br>*What word has the longest path on a gesture-typing smartphone keyboard?*|
|[How to Do Things with Words, or Statistical Natural Language Processing in Python](https://github.com/norvig/pytudes/blob/master/ipynb/How%20to%20Do%20Things%20with%20Words.ipynb)<br>*Spelling Correction, Secret Codes, Word Segmentation, and more: grab your bag of words.*|
|Computer Science Algorithms, Concepts, and Problems|
|Math Concepts|
|---|
|[A Chaos Game with Triangles](https://github.com/norvig/pytudes/blob/master/ipynb/Sierpinski.ipynb)<br>*A surprising appearance of the Sierpinski triangle in a random walk between vertexes.*|
|[BASIC Interpreter](https://github.com/norvig/pytudes/blob/master/ipynb/BASIC.ipynb)<br>*How to write an interpreter for the BASIC programming language.*|
|[Bad Grade, Good Experience](https://github.com/norvig/pytudes/blob/master/ipynb/Snobol.ipynb)<br>*As a student, did you ever get a bad grade on a programming assignment? (Snobol, Concordance)*|
|[Conway's Game of Life](https://github.com/norvig/pytudes/blob/master/ipynb/Life.ipynb)<br>*The cellular automata zero-player game.*|
|[A Concrete Introduction to Probability](https://github.com/norvig/pytudes/blob/master/ipynb/Probability.ipynb)<br>*Code and examples of the basic principles of Probability Theory.*|
|[Probability, Paradox, and the Reasonable Person Principle](https://github.com/norvig/pytudes/blob/master/ipynb/ProbabilityParadox.ipynb)<br>*Some classic paradoxes in Probability Theory, and how to think about disagreements.*|
|[Symbolic Algebra, Simplification, and Differentiation](https://github.com/norvig/pytudes/blob/master/ipynb/Differentiation.ipynb)<br>*A computer algebra system that manipulates expressions, including symbolic differentiation.*|
|[Economics Simulation](https://github.com/norvig/pytudes/blob/master/ipynb/Economics.ipynb)<br>*A simulation of a simple economic game.*|
|[How to Count Things](https://github.com/norvig/pytudes/blob/master/ipynb/How%20To%20Count%20Things.ipynb)<br>*Combinatorial math: how to count how many things there are, when there are a lot of them.*|
|[Euler's Sum of Powers Conjecture](https://github.com/norvig/pytudes/blob/master/ipynb/Euler's%20Conjecture.ipynb)<br>*Solving a 200-year-old puzzle by finding integers that satisfy a<sup>5</sup> + b<sup>5</sup> + c<sup>5</sup> + d<sup>5</sup> = e<sup>5</sup>.*|
|Computer Science Algorithms and Concepts|
|---|
|[BASIC Interpreter](https://github.com/norvig/pytudes/blob/master/ipynb/BASIC.ipynb)<br>*How to write an interpreter for the BASIC programming language.*|
|[Bad Grade, Good Experience](https://github.com/norvig/pytudes/blob/master/ipynb/Snobol.ipynb)<br>*As a student, did you ever get a bad grade on a programming assignment? (Snobol, Concordance)*|
|[The Convex Hull Problem](https://github.com/norvig/pytudes/blob/master/ipynb/Convex%20Hull.ipynb)<br>*A classic Computer Science Algorithm.*|
|[The Traveling Salesperson Problem](https://github.com/norvig/pytudes/blob/master/ipynb/TSP.ipynb)<br>*Another of the classics.*|
|[Economics Simulation](https://github.com/norvig/pytudes/blob/master/ipynb/Economics.ipynb)<br>*A simulation of a simple economic game.*|
|[Project Euler Utilities](https://github.com/norvig/pytudes/blob/master/ipynb/Project%20Euler%20Utils.ipynb)<br>*My utility functions for the Project Euler problems, including `Primes` and `Factors`.*|
# Index of Python Files
@@ -69,7 +85,9 @@ Some are in Jupyter (IPython) notebooks, some in `.py` files. You can view the f
# Etudes for Programmers
I got the idea for the "etudes" part of the name from this [1978 book by Charles Wetherell](https://books.google.com/books/about/Etudes_for_programmers.html?id=u89WAAAAMAAJ)
I got the idea for the "etudes" part of the name from
this [1978 book](https://books.google.com/books/about/Etudes_for_programmers.html?id=u89WAAAAMAAJ)
by [Charles Wetherell](http://demin.ws/blog/english/2012/08/25/interview-with-charles-wetherell/)
that was very influential to me when I was first learning to program.
![](https://images-na.ssl-images-amazon.com/images/I/51ZnZH29dvL._SX394_BO1,204,203,200_.jpg)
+1475
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
+435 -300
View File
File diff suppressed because it is too large. Load diff
+168 -159
View File
@@ -11,7 +11,7 @@
}
},
"source": [
"<div style=\"text-align:right\"><b>Peter Norvig</b> 22 October 2015, revised 28 October 2015, 4 July 2017</div>\n",
"<div style=\"text-align:right\"><b>Peter Norvig</b> 2000; revised 2015&mdash;2018</div>\n",
"\n",
"# Beal's Conjecture Revisited\n",
"\n",
@@ -25,78 +25,52 @@
"made his conjecture in 1993:\n",
"\n",
"> If $A^x + B^y = C^z$, \n",
"> <br>where $A, B, C, x, y, z$ are positive integers and $x, y, z$ are all greater than $2$, \n",
"> <br>where $A, B, C, x, y, z$ are positive integers \n",
"> <br>and $x, y, z$ are all greater than $2$, \n",
"> <br>then $A, B$ and $C$ must have a common prime factor.\n",
"\n",
"[Andrew Wiles](https://en.wikipedia.org/wiki/Andrew_Wiles) proved Fermat's theorem in 1995, but Beal's conjecture remains unproved, and Beal has offered [$1,000,000](http://www.ams.org/profession/prizes-awards/ams-supported/beal-prize) for a proof or disproof. I don't have the mathematical skills of Wiles, so I could never find a proof, but I can write a program to search for counterexamples. I first wrote [that program in 2000](http://norvig.com/beal2000.html), and [my name got associated](https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=beal%20conjecture) with Beal's Conjecture, which means I get a lot of emails with purported proofs or counterexamples (many asking how they can collect their prize money). So far, all the emails have been wrong. This page catalogs some of the more common errors and updates my 2000 program."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"source": [
"[Andrew Wiles](https://en.wikipedia.org/wiki/Andrew_Wiles) proved Fermat's theorem in 1995, but Beal's conjecture remains unproved, and Beal has offered [one million dollars](http://www.ams.org/profession/prizes-awards/ams-supported/beal-prize) for a proof or disproof. I don't have the mathematical skills of Wiles, so I could never find a proof, but I can write a program to search for counterexamples. I first wrote [that program in 2000](http://norvig.com/beal2000.html), and [my name got associated](https://www.google.com/webhp?#q=beal conjecture) with Beal's Conjecture, which means I get a lot of emails with purported proofs or counterexamples (many asking how they can collect their prize money). So far, all the emails have been wrong. This notebook catalogs some of the more common errors, updates my 2000 program, and introduces this tool for verifying counterexamples:\n",
" \n",
"\n",
"| [**Online Beal Counterexample Checker**](http://norvig.com/bealcheck.html) |\n",
"|:---:|\n",
"\n",
"\n",
"# How to Not Win A Million Dollars\n",
"\n",
"\n",
"* A proof must show that there are no examples that satisfy the conditions. A common error is to show how a certain pattern generates an infinite collection of numbers that satisfy $A^x + B^y = C^z$ and then show that in all of these, $A, B, C$ have a common factor. But that's not good enough, unless you can also prove that no other pattern exists.\n",
"* A proof must show that there are **no** examples that satisfy the conditions. A common error is to show how a certain pattern generates an infinite number of $(A, x, B, y, C, z)$ examples, and that the conjecture holds for this entire infinite collection. But that's not good enough, unless you can also prove that the conjecture holds for every other possible pattern.\n",
"\n",
"<p>\n",
"\n",
"* It is valid to use proof by contradiction: assume the conjecture is true, and show that that leads to a contradiction. It is not valid to use proof by circular reasoning: assume the conjecture is true, put in some irrelevant steps, and show that it follows that the conjecture is true.\n",
"\n",
"\n",
"<p>\n",
"* A valid counterexample needs to satisfy all four conditions&mdash;don't leave one out.\n",
"\n",
"* A valid counterexample needs to satisfy all four conditions&mdash;don't leave one out:\n",
"\n",
"> $A, B, C, x, y, z$ are positive integers <br> \n",
"$x, y, z > 2$ <br>\n",
"$A^x + B^y = C^z$ <br>\n",
"$A, B, C$ have no common prime factor.\n",
"* One correspondent claimed that $27^4 + 162 ^ 3 = 9 ^ 7$ was a solution, because the first three conditions hold, and the common factor is 9, which isn't a prime. But of course, if $A, B, C$ have 9 as a common factor, then they also have 3, and 3 is prime. \"No common prime factor\" means the same thing as \"no common factor greater than 1.\"\n",
"\n",
"(If you think you might have a valid counterexample, before you share it with Andrew Beal or anyone else, you can check it with my [Online Beal Counterexample Checker](http://norvig.com/bealcheck.html).)\n",
"\n",
"<p>\n",
"\n",
"* One correspondent claimed that $27^4 + 162 ^ 3 = 9 ^ 7$ was a solution, because the first three conditions hold, and the common factor is 9, which isn't a prime. But of course, if $A, B, C$ have 9 as a common factor, then they also have 3, and 3 is prime. The phrase \"no common prime factor\" means the same thing as \"no common factor greater than 1.\"\n",
"\n",
"<p>\n",
"\n",
"* Another claimed that $2^3+2^3=2^4$ was a counterexample, because all the bases are 2, which is prime, and prime numbers have no prime factors. But that's not true; a prime number has itself as a factor.\n",
"\n",
"<p>\n",
"\n",
"* A creative person offered $1359072^4 - 940896^4 = 137998080^3$, which fails both because $3^3 2^5 11^2$ is a common factor, and because it has a subtraction rather than an addition (although, as Julius Jacobsen pointed out, that can be rectified by adding $940896^4$ to both sides).\n",
"* A creative person offered $ 1359072^4 - 940896^4 = 137998080^3$, which fails both because $ 3^3 2^5 11^2 $ is a common factor, and because it has a subtraction rather than an addition (although, as Julius Jacobsen pointed out, it could be rewritten as $ 137998080^3 + 940896^4 = 1359072^4 $).\n",
"\n",
"<p>\n",
"\n",
"* Mustafa Pehlivan came up with an example involving 76-million-digit numbers, which took some work to prove wrong (by using modulo arithmetic). \n",
"* Mustafa Pehlivan came up with an example involving 76-million-digit numbers, which took some work to prove wrong (using modulo arithmetic). \n",
"\n",
"<p>\n",
"\n",
"* Another Beal fan started by saying \"Let $C = 43$ and $z = 3$. Since $43 = 21 + 22$, we have $43^3 = (21^3 + 22^3).$\" But of course $(a + b)^3 \\ne (a^3 + b^3)$. This fallacy is called [the freshman's dream](https://en.wikipedia.org/wiki/Freshman%27s_dream) (although I remember having different dreams as a freshman).\n",
"* Another Beal fan started by saying \"Let $C = 43$ and $z = 3$. Since $43 = 21 + 22$, we have $43^3 = (21^3 + 22^3)$.\" But of course $(a + b)^3 \\ne (a^3 + b^3)$. This fallacy is called [the freshman's dream](https://en.wikipedia.org/wiki/Freshman%27s_dream) (although I remember having different dreams as a freshman).\n",
"\n",
"<p>\n",
"\n",
"* Multiple people proposed answers similar to this one:"
"* Multiple people proposed counterexamples similar to this one:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"button": false,
"collapsed": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
"collapsed": true
},
"outputs": [],
"source": [
@@ -108,6 +82,7 @@
"execution_count": 2,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -126,11 +101,10 @@
}
],
"source": [
"A, B, C = 60000000000000000000, 70000000000000000000, 82376613842809255677\n",
"\n",
"A, B, C = 60000000000000000001, 70000000000000000003, 82376613842809255677\n",
"x = y = z = 3.\n",
"\n",
"A ** x + B ** y == C ** z and gcd(gcd(A, B), C) == 1"
"A ** x + B ** y == C ** z and gcd(A, B) == gcd(B, C) == 1"
]
},
{
@@ -143,9 +117,9 @@
}
},
"source": [
"**WOW! The result is `True`!** Is this a real counterexample to Beal? And also a disproof of Fermat?\n",
"**WOW! The result is `True`!** The two sides of the equation are equal, and the greatest common divisor is 1. Is this a real counterexample to Beal? And also a disproof of Fermat's Last Theorem?\n",
"\n",
"Alas, it is not. Notice the decimal point in \"`3.`\", indicating a floating point number, with inexact, limited precision. Change the inexact \"`3.`\" to an exact \"`3`\" and the result changes to \"`False`\". Below we see that the two sides of the equation are the same for the first 18 digits, but differ starting with the 19th: "
"Alas, it is not. The decimal point in \"`x = y = z = 3`**`.`**\" indicates a floating point number, with inexact, limited precision. Change the inexact \"`3.`\" to an exact \"`3`\" and the two sides of the equation are no longer equal. Below we see they are the same for the first 19 digits, but differ starting with the 20th: "
]
},
{
@@ -153,6 +127,7 @@
"execution_count": 3,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -162,7 +137,7 @@
{
"data": {
"text/plain": [
"(559000000000000000000000000000000000000000000000000000000000,\n",
"(559000000000000000054900000000000000002070000000000000000028,\n",
" 559000000000000000063037470301555182935702892172500189973733)"
]
},
@@ -186,101 +161,128 @@
}
},
"source": [
"They say \"close\" only counts in horseshoes and hand grenades, and if you threw two horseshoes at a stake on the planet [Kapteyn-b](https://en.wikipedia.org/wiki/Kapteyn_b) (a possibly habitable and thus possibly horseshoe-playing exoplanet 12.8 light years from Earth) and the two paths differed in the 19th digit, the horseshoes would end up [less than an inch](https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=12.8%20light%20years%20*%201e-19%20in%20inches) apart. That's really, really close, but close doesn't count in number theory.\n",
"They say \"close\" only counts in horseshoes and hand grenades, and if you stood in your yard and threw a horseshoe at a stake on **[Kapteyn-b](https://en.wikipedia.org/wiki/Kapteyn_b)** (an exoplanet 12.8 light years from Earth that is deemed habitable and thus possibly horseshoe-playing) and the flight path differed from the perfect path in the 20th digit, then it would end up \n",
"[about a millimeter](https://www.google.com/search?q=12.8+light+years+%2F+10%5E20+in+inches&oq=12.8+light+years+%2F+10%5E19+in+inches) \n",
"from the target. That's really, really close, but close doesn't count in number theory.\n",
"\n",
"![Kapteyn-b and Homer Simpson](https://norvig.com/homer.png)\n",
"Left: [Kapteyn-b](https://www.space.com/26115-oldest-habitable-alien-planet-kapteyn-b.html). &nbsp; &nbsp; Right: [Homer Simpson](https://www.youtube.com/watch?time_continue=1&v=ReOQ300AcSU).\n",
"\n",
"\n",
"# *The Simpsons* and Fermat\n",
"\n",
"In two different [episodes of *The Simpsons*](http://www.npr.org/sections/krulwich/2014/05/08/310818693/did-homer-simpson-actually-solve-fermat-s-last-theorem-take-a-look), close counterexamples to Fermat's Last Theorem are shown: \n",
"$1782^{12} + 1841^{12} = 1922^{12}$ and $3987^{12} + 4365^{12} = 4472^{12}$. These were designed by *Simpsons* writer David X. Cohen to be correct up to the precision found in most handheld calculators. Cohen found the equations with a program that must have been something like this:"
"$3987^{12} + 4365^{12} = 4472^{12}$ and $1782^{12} + 1841^{12} = 1922^{12}$. These were designed by *Simpsons* writer David X. Cohen to be correct up to the precision of a typical handheld calculator; here we see the two sides of the second equation agree on the first ten digits, `6397665634`, and then differ:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"button": false,
"collapsed": true,
"new_sheet": false,
"run_control": {
"read_only": false
}
"collapsed": false
},
"outputs": [],
"outputs": [
{
"data": {
"text/plain": [
"(63976656349698612616236230953154487896987106,\n",
" 63976656348486725806862358322168575784124416)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from itertools import combinations\n",
"\n",
"def simpsons(bases, powers):\n",
" \"\"\"Find the integers (A, B, C, n) that come closest to solving \n",
" Fermat's equation, A ** n + B ** n == C ** n. \n",
" Let A, B range over all pairs of bases and n over all powers.\"\"\"\n",
" equations = ((A, B, iroot(A ** n + B ** n, n), n)\n",
" for A, B in combinations(bases, 2)\n",
" for n in powers)\n",
" return min(equations, key=relative_error)\n",
"\n",
"def iroot(i, n): \n",
" \"The integer closest to the nth root of i.\"\n",
" return int(round(i ** (1./n)))\n",
"\n",
"def relative_error(equation):\n",
" \"Error between LHS and RHS of equation, relative to RHS.\" \n",
" (A, B, C, n) = equation\n",
" LHS = A ** n + B ** n\n",
" RHS = C ** n\n",
" return abs(LHS - RHS) / RHS"
"3987 ** 12 + 4365 ** 12, 4472 ** 12"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Cohen must have found the equations with a program something like this (here `bases` is a sequence of integers to consider for the values of `A` and `B`; the variables `An` and `Bn` hold the `A**n` and `B**n` values; `lhs` is their sum (the left-hand-side of the equation); and the function `Cn` computes the `C**n` that is closest to that sum):"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
}
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(1782, 1841, 1922, 12)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"simpsons(range(1000, 2000), [11, 12, 13])"
"from itertools import combinations\n",
"\n",
"def simpsons(bases, n):\n",
" \"\"\"Print the (A**n + B**n = C**n) equation that minimizes the relative error,\n",
" for a given n and A, B values from the sequence of integers `bases`.\"\"\"\n",
" def Cn(lhs): return iroot(sum(lhs), n) ** n\n",
" def err(lhs): return abs(sum(lhs) - Cn(lhs)) / sum(lhs)\n",
" def show(Xn): return '{} ** {}'.format(iroot(Xn, n), n)\n",
" powers = [b ** n for b in bases]\n",
" (An, Bn) = lhs = min(combinations(powers, 2), key=err)\n",
" print('{} + {} == {} (with error {:.0g})'\n",
" .format(show(An), show(Bn), show(Cn(lhs)), err(lhs)))\n",
"\n",
"def iroot(x, n): \"integer nth root\"; return int(round(x ** (1 / n)))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
}
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(3987, 4365, 4472, 12)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"1782 ** 12 + 1841 ** 12 == 1922 ** 12 (with error 3e-10)\n",
"3987 ** 12 + 4365 ** 12 == 4472 ** 12 (with error 2e-11)\n"
]
}
],
"source": [
"simpsons(range(3000, 5000), [12])"
"simpsons(range(1000, 2000), 12)\n",
"simpsons(range(2000, 5000), 12)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"These are the same two equations that David X. Cohen found. \n",
"\n",
"Can we find other near-misses? I'll try each single-digit exponent. I want A, B, C to be 4 digits each, so I'll limit A and B to 9500 (not 9999), to try to keep C from overflowing to 5 digits. (This takes around 10 minutes to run.)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5856 ** 3 + 9036 ** 3 == 9791 ** 3 (with error 1e-12)\n",
"2396 ** 4 + 4551 ** 4 == 4636 ** 4 (with error 4e-11)\n",
"3993 ** 5 + 7767 ** 5 == 7822 ** 5 (with error 2e-11)\n",
"6107 ** 6 + 8919 ** 6 == 9066 ** 6 (with error 8e-13)\n",
"5592 ** 7 + 9079 ** 7 == 9122 ** 7 (with error 2e-11)\n",
"4749 ** 8 + 8952 ** 8 == 8959 ** 8 (with error 3e-11)\n",
"5433 ** 9 + 6725 ** 9 == 6828 ** 9 (with error 4e-11)\n"
]
}
],
"source": [
"for n in range(3, 10):\n",
" simpsons(range(1000, 9500), n)"
]
},
{
@@ -293,21 +295,24 @@
}
},
"source": [
"# Back to Beal\n",
"The equation for *n*=6 has the smallest error yet (in the 12th decimal place).\n",
"\n",
"# Back to `beal`\n",
"\n",
"In October 2015 I looked back at my original [program from 2000](http://norvig.com/beal2000.html).\n",
"I ported it from Python 1.5 to 3.5 (by putting parens around the argument to `print` and adding `long = int`). The program runs 250 times faster today than it did in 2000, a tribute to both computer hardware engineers and the developers of the Python interpreter.\n",
"I ported it from Python 1.5 to 3.5 (`print` is now a function, `long` is `int`). It runs 250 times faster today, a tribute to both computer hardware engineers and the developers of the Python interpreter.\n",
"\n",
"I found that I was a bit confused about the definition of [the problem in 2000](https://web.archive.org/web/19991127081319/http://bealconjecture.com/). I thought then that, *by definition*, $A$ and $B$ could not have a common factor, but actually, the definition of the conjecture only rules out examples where all three of $A, B, C$ share a common factor. Mark Tiefenbruck (and later Edward P. Berlin and Shen Lixing) wrote to point out that my thought was actually correct, not by definition, but by derivation: if $A$ and $B$ have a commmon prime factor $p$, then the sum of $A^x + B^y$ must also have that factor $p$, and since $A^x + B^y = C^z$, then $C^z$ and hence $C$ must have the factor $p$. So I was wrong twice, and in this case two wrongs did make a right.\n",
"I found that I had [misstated the problem](https://web.archive.org/web/19991127081319/http://bealconjecture.com/) in 2000. I thought that, *by definition*, $A$ and $B$ could not have a common factor, but actually, \n",
"the conjecture only rules out examples where all three of $A, B, C$ share a common factor. But, as [Mark Tiefenbruck](mailto:mark @tiefenbruck.org) (as well as Edward P. Berlin and Shen Lixing) pointed out, my statement is correct, not by definition, but *by derivation:* if $A$ and $B$ have a common prime factor $p$, then the sum of $A^x + B^y$ must also have that factor $p$, and hence $C^z$, and $C$, must have the factor $p$.\n",
"\n",
"Mark Tiefenbruck also suggested an optimization: only consider exponents that are odd primes, or 4. The idea is that a number like 512 can be expressed as either $2^9$ or $8^3$, and my program doesn't need to consider both. In general, any time we have a composite exponent, such as $b^{qp}$, where $p$ is prime, we should ignore $b^{(qp)}$, and instead consider only $(b^q)^p$. There's one complication to this scheme: 2 is a prime, but 2 is not a valid exponent for a Beal counterexample. So we will allow 4 as an exponent, as well as all odd primes up to `max_x`.\n",
"Mark Tiefenbruck also suggested another optimization: only consider exponents that are odd primes, or 4. The idea is that a number like 512 can be expressed as either $2^9$ or $8^3$, and my program doesn't need to consider both. In general, any time we have a composite exponent, such as $b^{qp}$, where $p$ is prime, we should ignore $A=b, x=qp$, and instead consider only $A=b^q, x=p$. There's one complication to this scheme: 2 is a prime, but 2 is not a valid exponent for a Beal counterexample. So we will allow 4 as an exponent, as well as all odd primes up to `max_x`.\n",
"\n",
"Here is the complete, updated, refactored, optimized program:"
"Here is the complete, updated program:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 8,
"metadata": {
"button": false,
"collapsed": true,
@@ -373,9 +378,10 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 9,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -386,8 +392,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 256 ms, sys: 1.44 ms, total: 257 ms\n",
"Wall time: 256 ms\n"
"CPU times: user 353 ms, sys: 4.84 ms, total: 358 ms\n",
"Wall time: 376 ms\n"
]
}
],
@@ -405,14 +411,15 @@
}
},
"source": [
"The execution time goes up roughly with the square of `max_A`, so the following should take about 100 times longer:"
"The execution time goes up roughly with the square of `max_A`, so the following should take about 25 times longer:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 10,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -423,13 +430,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 29.1 s, sys: 25.2 ms, total: 29.2 s\n",
"Wall time: 29.2 s\n"
"CPU times: user 8.97 s, sys: 56.6 ms, total: 9.03 s\n",
"Wall time: 9.12 s\n"
]
}
],
"source": [
"%time beal(1000, 100)"
"%time beal(500, 100)"
]
},
{
@@ -454,9 +461,10 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 11,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -474,7 +482,7 @@
" 6: [216, 1296, 7776, 279936]}"
]
},
"execution_count": 10,
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -499,9 +507,10 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 12,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -534,7 +543,7 @@
" 279936: 6}"
]
},
"execution_count": 11,
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
@@ -546,8 +555,10 @@
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"execution_count": 13,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
@@ -555,7 +566,7 @@
"3"
]
},
"execution_count": 12,
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
@@ -584,9 +595,10 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 14,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -632,9 +644,10 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 15,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -647,7 +660,7 @@
"{True}"
]
},
"execution_count": 14,
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
@@ -676,12 +689,12 @@
}
},
"source": [
"I get nervous having an incorrect version of `gcd` around; let's change it back, quick!"
"I get nervous having an incorrect version of `gcd` around: change it back, quick!"
]
},
{
"cell_type": "code",
"execution_count": 15,
"execution_count": 16,
"metadata": {
"button": false,
"collapsed": true,
@@ -712,9 +725,10 @@
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": 17,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -727,7 +741,7 @@
"'tests pass'"
]
},
"execution_count": 16,
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
@@ -832,7 +846,7 @@
},
{
"cell_type": "code",
"execution_count": 17,
"execution_count": 18,
"metadata": {
"button": false,
"collapsed": true,
@@ -897,8 +911,10 @@
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"execution_count": 19,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
@@ -911,7 +927,7 @@
" 6: [(216, 3), (296, 4), (776, 5), (936, 7)]}"
]
},
"execution_count": 18,
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
@@ -937,9 +953,10 @@
},
{
"cell_type": "code",
"execution_count": 19,
"execution_count": 20,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -971,7 +988,7 @@
" 936: [(6, 7)]})"
]
},
"execution_count": 19,
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
@@ -995,9 +1012,10 @@
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": 21,
"metadata": {
"button": false,
"collapsed": false,
"new_sheet": false,
"run_control": {
"read_only": false
@@ -1008,8 +1026,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 35.5 s, sys: 44.1 ms, total: 35.5 s\n",
"Wall time: 35.5 s\n"
"CPU times: user 56 s, sys: 436 ms, total: 56.4 s\n",
"Wall time: 59.2 s\n"
]
}
],
@@ -1035,15 +1053,6 @@
"\n",
"This was fun, but I can't recommend anyone spend a serious amount of computer time looking for counterexamples to the Beal Conjecture&mdash;the money you would have to spend in computer time would be more than the expected value of your prize winnings. I suggest you work on a proof rather than a counterexample, or work on some other interesting problem instead!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
@@ -1062,7 +1071,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.3"
"version": "3.6.0"
}
},
"nbformat": 4,
+34 -28
View File
@@ -18,20 +18,9 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAEACAYAAABI5zaHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XuQnHWd7/H3NwmakCEEwiZUQsjEsfTkEPFSuzJHPexE\nCHqOMiPZXY9mAiQoVm2tYzKaCArjzNRYu6ziEU92rVPUIWSVoLWrruCyVZoUO3FjES8lbojB29wS\nk2Lwwm3CQgnzO3883TN9eZ7up/t5nr48/XlVdWX6/u2ezPf5Pd/fzZxziIhIuiyodwAiIhI/JXcR\nkRRSchcRSSEldxGRFFJyFxFJISV3EZEUKpvczeweM5s2s2M5t73ezB4xs0fN7Adm9sfJhikiIpUI\n03K/F3hHwW2fAQadc28EBoHPxh2YiIhUr2xyd84dAZ4quHkWOD/z83LgdMxxiYhIBIuqfF4/8G0z\n+xxgwFviC0lERKKqtkP1L4GdzrlL8RL9vvhCEhGRqCzM2jJmtg74lnPu8sz1p51zy3Puf8Y5d37A\nc7V4jYhIFZxzVu1zw7bcLXPJOm1mfwpgZlcBvyj1ZOdc014GBwfrHkOrxt/MsSv++l+aPf6oytbc\nzex+oAtYYWYn8UbH3Az8HzNbCLwAfChyJCIiEpuyyd05tzXgLo1tFxFpUJqhWkZXV1e9Q4ikmeNv\n5thB8ddbs8cfVagO1UhvYOaSfg8RkbQxM1wNOlRFRKSJKLmLiKSQkruISAopuYuIpJCSu4hICim5\ni4ikkJK7iEgKKbmLiKSQkruISAopuYuIpJCSu4hICim5i4ikkJK7iEgKKbmLiKSQkruISAopuYuI\npFDZbfak8UxMTDEwsJ/Tp2dZs2YBIyPbWb9+Xb3DEpEGop2YmszExBSbN+9lbGwYWAqcpaNjkIMH\n+5TgRVJEOzG1mIGB/TmJHWApY2PDDAzsr2NUItJolNybzOnTs8wn9qylnDkzW49wRKRBKbk3mTVr\nFgBnC249y+rV+lWKyDxlhCYzMrKdjo5B5hO8V3MfGdlet5hEpPGoQ7UJZUfLnDkzy+rVGi0jkkZR\nO1SV3EVEGpBGy4iISBEldxGRFCqb3M3sHjObNrNjBbf3mdnjZvaYmd2RXIgiIlKpMMsP3AvsBb6U\nvcHMuoBrgdc5514ys4uSCU9ERKpRtuXunDsCPFVw818CdzjnXso85rcJxCYiIlWqtub+GuBKMztq\nZv9mZn8cZ1AiIhJNtatCLgIucM51mtmfAP8IvCq+sEREJIpqk/sp4BsAzrkfmtmsma1wzv3O78FD\nQ0NzP3d1ddHV1VXl24qIpNPo6Cijo6OxvV6oSUxm1g58yzn3usz1DwFrnHODZvYa4KBzzneKpCYx\niYhULuokprItdzO7H+gCVpjZSWAQ2Afca2aPAS8CN1QbgIiIxE/LD6SAdmYSSR+tLdPitDOTSDpp\nbZkWp52ZRMSPNsiuUqOUQrQzk4j4UXKvgl8p5OjR+pRC5ndmyk3w2plJpNWp5l6FbduGOXBgN4UJ\ntbf3Tu67bzCx9/U7WwBUcxdJocSHQkqxepRCSp0tHDzYx8DAnTk7Mymxi7Q6JfcqBJVCli17lm3b\nhhOpwwd3nHpnC0meMYhI81Fyr8LIyHaOHh3Ma0WvXdvPo48u4eTJ3SRRh1fHqYhUQjX3KhVuUv3c\nczM8+OAQldThKxlxE1Tnb2+/gfb2jZq8FFKjjHISKSdqzR3nXKIX7y3Sr6vrUw5c0WXTpk/5Pn58\nfNJ1dHzMwUzmsTOuo+Njbnx8MvTjFy260cGJueuLF+9wPT27Al+j1VX6nYvUUyZ3Vp17NV4uJsuW\nPY9Xh891lvPOe9738ZVOPlq/fh0HD/bR23snmzYN0t5+Ay+9dAuwYe75L7ywlwceaGPz5r1MTEwB\nXkt127ZhNm0aZNu24bnbW5EmfEkrUc09JmYvAQPACNmaO3yY559f6Pv4amro69evmyvxbNo0yOTk\nhoJHLAUWzHW0joxsb5jx+I1A/RbSStRyj8kzzywD/gx4P3A7cCfwcf7931/iPe/pL2o5z4+4yRV+\n8lHQ871fqZewglqqnZ39LdmKj/qdizSVKDWdMBdapObe2zvk4Paceq6bq+t6t+fXeKPWf/2eDx9z\nMOlgxvX2DgX2A8CnWrLerJq7NBNUc28MIyPbWbx4HL/T/vkTpPwa78aNL7Ny5fWsWnUD3d1DFZVL\nsjX47u4hFi/uBe4A+oCL6OgYZGRke9nWfavVmwv7LXp772zZEpWkn2ruMVm/fh3XXLOaBx8sntyU\nX/1aytjY2Uwt/NNka+E//Wnlk5DWr1/HAw98NmdY5r68Gap+4/G9vVb65mJptXpzbr+FSJppnHuM\n/JYI8DpZdwLZ1qE3Nn1y8kvUYm2abOI/dGiM6el1wAfzYkl6PRwRqY7Wlmkg2dP+7Dov5533PI8+\nOsOpUxdlHuEt6nXRRWuZnKzNqI1sS3X+wJMfy8hIX8nni0hzUnKPWeFpv9dyzl/Ua2BgP9//fm2X\n6S088GiBMZF0U1kmQUFT3aNujacp9CLppz1UG1S5BF64Nk3YBB3Hnqk6OIg0PiX3BpXUhh5RFxAr\nPjg8TlvbLWzcuIGOjnOV6EUahDpU66hUCzipqe5Brzs5uYHJyWHgLA880Mdlly3j1a++oChZ589a\nnQLuYWbmKxw9upSjRytfnkBnASINKsoMqDAXmnSG6vj45Nwsz97eoaJZjOVmO3ozVotnq/b2DkWK\nK+h1YcjnevEMzPxZq9Fi1IxPkeQQcYaqkruPMEmrXPJOKvGVXnYgN5Y9meR9m2tv3xJw0KlsmeJC\nYb6DUgdIEQmm5J6AMK3uMOu3Z5Pbpk3xJrfc121v3+Lm13TPbblf63tgyT84RGu5l/oO1KoXiUbJ\nPQFhEndSZZdcYVq+/i35Hb4JP9uCHx+fdD09u9yKFe92Cxb8hfMWNpusOAGX+g5q8f2IpFnU5K4O\nVR9BG2DnTjLyW7dl8eI+ZmbOZ2JiKnKnot+Qx9zOztyOzI0bX+ayy4Z48knj+PGfMTOzjvlNPLK8\nTtfNm/eyb991HD++kN/97qtzr71kSR/XXHM+n//8rtCx+30H2VmvN920D62dLlJHUY4MYS7UueVe\nTd03bElhfHzSdXfvdosXb6269RukVMu3VHzj45OZUk1Qp+tM4P3VtKqDSk9quYtEQ9JlGeAeYBo4\n5nPfx4BZ4MISz0/6OwgUpe4btl6eVBIrVRqqpjM3t9N1+fLry5adolLNXSSaqMk9TFnmXmAv8KXc\nG83sEmAz3mDphhS8Z2b5iURhl4YtHnc+BeznoYfG2LZtuOpx36VKQ+XG0GfXkbnyyvfx618vz7zO\nurnXaGt7iqefLn7toP1eq6G1bETqq+xKVc65I8BTPnd9HtgTe0QxyG4K/S//Moa33V3u8Sfeum/+\nhhhTeMfB3Tz99Jc4cGB33mbVlRgZ2U5Hx2DOa58tuwlHtk9gYmKK/v67+M1vLgT+L/AN4NPAF1i7\ntp/XvnY13lLE868NA5l9YOOTPUA+/PAw9903qMQuUkthmvd4zb5jOde7gf+d+XmCBirLBJckjji/\ncd/xvl+8JZqg0lC5mrt3n/+Wf93duzMln8lMvJ/K/DvpW5bRWHWR+qDWo2XMbAnwSbySzNzNpZ4z\nNDQ093NXVxddXV2Vvm1ofqUY+ADwWbxW9VImJ8+yeXP5afZhptbnlh8eemiMp5+Ob4RIbmmoMJZ9\n+67j7ruLSx7btg1nPv9n8CvdPPfcuZmW/0V4uzJlFS85XG7EjojEZ3R0lNHR0fheMMwRgJyWO7AR\neAIYx2u1/wGYBFYGPLcGx7h5/h2Rlbeo/VrHbW073BVX7AxswSbVuVpJ5+T8569utE3cn0ctf5Hq\nUItJTEA78FjAfRPABSWem+w3UMA/Id0WODokKPmUXsMleGjkpZd+JC9pXnrpRxIdFhn82MlMOco/\ngYcZDRRmMlcpGjEjUr2oyb1sWcbM7ge6gBVmdhIYdM7dm9v4p0xZxk9hmeFDH7qau+8+FHl1Qb+J\nNW1tx5iZKR4dsmzZs4Flh6ARKd7Iz+BRN879J3AHXl/1bOZ6dbLfkdcxHK7ck//5+4A7WLx4nGuu\nWc1dd82XU8KMBio1YidMySrKaCURiSjKkSHMBZ+Wu1+LbtGiG938lPloLbzCVunhw0d8W5Dd3bsD\nW8TBLfctc52QnZ078943zrLM/Hd0IvOet7lsx2e51/X7/NWURoJa3kHfZ+HrRm35i7QyalGWifQG\nPsk97LK1SazTkluGKLfwVWGJBfIPQG1tO0ospxstmXnf0Ymi0op3/URFk7FKjazp7R1ynZ0fd+3t\nW3z7E/y+t7AHMc1SFaleUyb3oCRYuARtNUmxVAde4X3FLfdJB7e7lSuvd729Q+7qqz/gvCGFexx0\nOb/FuHITVZzJzPuO/F+vkqGcQTH19OwKGDJa/sAR9iCmmrtI9aIm97osHBZUy82fU1U8NK+cUkP3\ngKL71q7t59JLP8nJk38N/Bb4AjDCk08u5cABbyEw+CDeYKFb8FuMK7fuXWohrUp539Ef8Ku1r1+/\nMXR/RFDfwdGjU0xPf5n8IaPDwJ2MjQ3T3387bW3LfWvqYRZWA81SFamrKEeGMBdqWHOvZgnanp5d\nrrd3yK1adV1Aqej2zM/hWuXlRqEUnj0E1cMPHz7iFi16R+B7hh1iGPS5V670X1/GO3uadEuW7Mj7\n/SxYsNVdeOEW1929u6Dm7p3tLF681XV371arXCQmNGNZxrngTr8oG1uUKheUKyUE3e+t+JhNYv2B\nJYbsCpErV17vVq68zvX07Aq1NZ/fQW0+eR503trs1XVoOucdJNrarnXzHbJe2eXqqLine truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x1028a2be0>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
@@ -47,8 +36,26 @@
"for (dist, climb, time) in csv.reader(open('dist-climb-time.csv')):\n",
" if miles(dist) > 30:\n",
" X.append(feet(climb) / miles(dist))\n",
" Y.append(miles(dist) / hours(time))\n",
"\n",
" Y.append(miles(dist) / hours(time))"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x105166cc0>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.plot(X, Y, 'o');"
]
},
@@ -63,14 +70,14 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAzgAAAIcCAYAAAA6xgsWAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XtgU1W6NvAnTe2FClpa2lLaBhQKXgbGcdTx8zoKM+KI\nRxG0hUodaFGwIlULKJQClTsKQgHl4ghSQfGgM+qo53gbUUaPjgg4jlBEE1oKpRcupk1D03x/lISm\n2Ul30p19y/P7S9Mka+29krDfvdb7LsOJEyecICIiIiIi0oEIpTtAREREREQkFQY4RERERESkGwxw\niIiIiIhINxjgEBERERGRbjDAISIiIiIi3WCAQ0REREREusEAh4iIiIiIdEPxAGfXrl3Izs7GpZde\nivj4eGzdutXj71arFUVFRbjsssvQu3dvXHXVVVizZo1CvSUiIiIiIjWLVLoDVqsVl112GbKzszFp\n0iSvvz/11FP49NNPsW7dOmRkZGDXrl2YMmUKEhMTce+99yrQYyIiIiIiUivFZ3CGDRuGWbNm4c47\n74TBYPD6+1dffYX77rsP1113HdLT03Hffffht7/9Lb7++msFektERERERGqmeIDTmd/97nd47733\nUFVVBQD48ssv8d1332HYsGEK94yIiIiIiNRG8SVqnVm8eDGmTp2Kyy+/HJGRkTAYDFiyZAkDHCIi\nIiIi8qL6AOf555/HV199hVdffRVpaWnYtWsXZs2ahYyMDNxyyy1Kd4+IiIiIiFRE1QGOzWZDaWkp\nNm/ejD/84Q8AgEsvvRR79+7FqlWrGOAQEREREZEHVefgnDlzBmfOnEFEhGc3jUYjWltbFeoVERER\nERGpleIBjtVqxb59+7B37160traisrIS+/btQ2VlJbp3747rrrsOc+bMwWeffQaz2Yzy8nJs27YN\nI0aMULrrsNlsumtLzmOqqKiQrS09jpXcbelxvDhWXafXcyhXWxwrbbXF8dJGOwDHSmttSU3xAGf3\n7t248cYbcfPNN8Nms2HhwoW46aabsHDhQgDAX/7yF/zmN7/Bgw8+iGuvvRYrV67ErFmzkJeXp3DP\niYiIiIhIbRTPwbn++uvR0NDg8++9evVCWVmZjD0iIiIiIiKtUnwGh4iIiIiISCoMcIiIiIiISDcY\n4BARERERkW4wwCEiIiIiIt1ggENERERERLrBAIeIiIiIiHSDAQ4REREREekGAxwiIiIiItINBjhE\n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 truncated
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAzgAAAIpCAYAAABucZ0IAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XdcU+f+B/DPCRtFQECR6cBdx89RBw7UUkfd163ItddRN72VtteqdeFCvVSw1V4XKu5Wa1ur9drWUbSt1Yta94qIMmREZARI8vuDJiUkYWZA+LxfL1/Wk5PvefLkQJ/veZaQkZGhABERERERkRkQmboARERERERE+sIEh4iIiIiIzAYTHCIiIiIiMhtMcIiIiIiIyGwwwSEiIiIiIrPBBIeIiIiIiMwGExwiohpMJpPB2dkZI0aMMHVRzEarVq3QsWNHUxeDyqG839mjR4/g7OyM+fPnG7BURFRRTHCIzMRnn30GZ2dnODs748qVK6YuDpFBnDt3Du+88w46deoEb29v1KtXD82bN8fQoUOxceNGJCQkmLqIEATB1EUwqMuXL2Pp0qUYNWoUmjVrBmdnZ7z22mulvu+7777D8OHD0bp1azRo0ADt27dHcHAwfv31V41z4+Pj8c9//hP9+vVD8+bNUb9+fbRs2RIDBw7E/v37UVBQoNfPJAiC2X9vRDWJpakLQET6sXv3bohEIigUCuzatQsdOnQwdZGI9CYzMxPvvPMOTpw4ARsbG3Tv3h1vvvkm7O3tkZaWhsuXL2PlypVYt24dTp48ifbt25u6yGbr4MGD2L59O6ytrdGsWTOkpKSU+p6PPvoIn376KVxcXPDWW2/BxcUFDx8+xHfffYevv/4amzdvxvjx41XnP3z4EF9++SU6deqE9u3bw9nZGampqTh9+jRmzZqFgwcP4ujRo3pLSr799lsmOERmhAkOkRmIjY3F7du3MXr0aFy8eBFHjx7FqlWrULt2bVMXjajS5HI5goKCcPbsWQQEBOCzzz6Du7u7xnl3797F6tWr8fLlSxOUsuYICgpCcHAwWrZsCQBwdXUt8fznz5/js88+Q/369REbG4u6deuqXrtw4QKGDBmCVatWqSU4/v7+ePz4sUasgoICDB06FOfOncPx48cxbNgwvXymhg0b6iUOEVUNHKJGZAZ27doFQRAQFBSE8ePHIysrC4cPH9Y4b/369XB2dsbWrVu1xklNTYWbmxs6deqkdlwul2PXrl3o378/fHx80KBBA3Tv3h2ffPKJxlAR5ZyOjh07QiKR4P3338drr70GV1dXbNu2DQBw//59fPzxx+jTpw/8/PxQv359tGnTBiEhIXj27JnWskmlUoSFhaFdu3Zwd3dH+/btsWrVKuTk5OicQ1KecpckOTkZixYtwuuvvw5PT0/4+PigY8eOmD59Om7duqU6Tzkuf8SIEUhISMDUqVPh5+eHBg0aoG/fvvjqq690XuPs2bMYO3Ys/Pz8UK9ePbRp0wahoaE6n45LJBKsXLkS3bp1g4eHB3x8fDBo0CAcP35c6/l5eXlYs2YN2rdvr1Z/eXl5Za6HS5cuwdnZGUFBQTrP6dGjB1xdXdWGisXExKB///5o2rQp3N3d0bp1awwbNgx79uwp03X379+Ps2fPokWLFjhw4IDW5AYAmjVrhp07d6JHjx5qx1u1agU3NzdIpVKsXLkSHTp0QL169bBkyRIAhQ3wNWvWoH///mjevDnq1auHli1bYtq0abh7967Ocm3ZsgVdu3ZF/fr10apVK3zwwQfIzMws8bMcO3YMQ4cORcOGDVG/fn106tQJK1aswKtXr8pUF0qZmZlYsWIFOnfuDHd3d/j6+mLYsGE4efKkxrlF78vU1FTMnTtXNeyre/fuOHDgQLmu3bZtW7z22muwsLAo0/lPnjyBQqFAx44d1ZIboPB+cXBwQGpqqtpxS0vtz18tLS0xaNAgKBQKPHjwoEzXP3v2rGq+zN27dxEUFIQmTZrA2dkZt2/fBqB7Dk5mZiY+/PBDtG7dGu7u7ujSpQs+++wzKBQKndeTSqXYtGkTevfuDS8vL3h6eqJv376Ijo7Wev63336LoUOHokWLFqhfvz5atGiBgQMH4pNPPinT5yMiTezBIarmMjIy8PXXX8Pb2xs9e/aEt7c31q9fj+joaEyZMkXt3HHjxmH16tXYv38/ZsyYoRHr0KFDkMlkmDBhgupYQUEBJkyYgNOnT6N58+YYPXo0bGxscP78eSxduhTnz5/H4cOHNYZ3SKVSDB48GDk5ORgwYABsbGxUDdNjx45h9+7d6NmzJ7p27QorKyvcvHkTu3fvxqlTp/DTTz+hfv36qlgKhQITJ07EmTNn4Ofnh2nTpiE/Px8xMTG4efOm1nqpaLmLy87ORmBgIOLj4xEQEIABAwYAABISEvDjjz+iT58+qifZSmlpaejfvz/c3NwwefJkpKWl4ejRo/j73/+OdevWYdq0aWrnr1+/HmFhYXBxccGbb74JNzc3/PHHH9i2bRtOnjyJ//73v2r1kZCQgMGDB0MsFqN79+7o168fsrKycOrUKQQHB2PRokV477331K4RFBSE77//Ho0bN8a0adOQl5eHvXv34saNGyV+/qK6du2Kxo0b4/vvv0d6ejqcnZ3VXr9x4wb++OMP9OnTB56engCAJUuWIDIyEg0bNsTw4cPh6OiIpKQkXLt2DYcPHy4xWVKKjo6GIAiYN28ebGxsSj1fJFJ/dqf8jidOnIhbt26hX79+qFu3Lho3bgwAOH/+PKKiotCjRw+0bdsWtWrVwoMHD/DVV1/h5MmTOHXqFFq1aqUWc8GCBdi+fTsaNGiAKVOmwMrKCidOnMCVK1eQn58PW1tbjXLNnz8fu3fvhpeXF4YNG4Y6derg8uXL2LhxI06fPo2TJ0/C3t6+1M+XkZGBAQMG4O7du2jfvj1mzpyJjIwMHDt2DOPHj8eSJUvw7rvvarwvPT0dgYGBsLe3x4gRI5Cbm4ujR49i5syZsLS0xKhRo0q9dkX4+fnBysoKV65cQVpamlqS8/PPPyMzMxMDBw4sUyy5XI7Tp09DEIQyzfsp6t69e3jjjTfQqlUrTJgwARKJRPU9afs9IJVKMWTIEMTFxaFNmzYYM2YMJBIJ1q1bh/Pnz2u9RmZmJoYOHYq4uDi0b98eEydOhFwuxw8//ICQkBBcuXJFLXHZtm0bQkND4e7ujgEDBsDV1RUvXrzA7du3sWvXLi5iQFRBTHCIqrl9+/YhNzdXlZQ0bNgQ3bt3R2xsLP73v/+pzUXw8vJCr169cPbsWdy6dUujYb5//36IRCKMHTtWdSw8PFw17n3lypWqhoBCocC8efMQExODXbt2aSRTz549Q6tWrbBnzx6NRunEiRMxf/58WFlZqR0/c+YMRo8ejQ0bNmDdunWq4zExMThz5gz8/f1x7Ngx1dPdf/3rX+jbt6/WxklFy13cDz/8gCdPnmDevHlYtmyZ2mtyuVzrk/fr169j9OjRaj1l8+fPR+/evbFkyRK89dZb8PDwAFA4aT4sLAzdunXDoUOH1IYV7tu3D7Nnz8bChQuxfft21fHp06fjyZMn2LVrF4YOHao6/vLlSwwaNAirV6/G4MGD0bx5cwCF3+v333+PLl264Pjx47C2tlbVX58+fco192DChAkICwvDkSNHNBK1ffv2QRAEtQR5165d8PLywqVLlzTug/T09FKvl5+fj6tXrwIAevbsWeZyFieTyZCSkoLY2Fg4Ojqqvda3b1/cvXtXI7m4fv06BgwYgOXLl6v1csTGxmL79u1o0qQJzpw5o4q3ePFiDB48GCkpKXBwcFCLFRMTg927d2PEiBHYsmWL6jsAgLVr12LNmjUIDw/Hxx9/XOpnWbJkCe7cuYMpU6Zg48aNquMLFixAnz59sHLlSvTr1w9t27ZVe9+1a9cwZcoUrF+/XvWdT58+HT179kRERITBEhwXFxd8/PHHWLJkCbp06YK33noLdevWxcOHD3Hy5En06dNHZ2/Fixcv8J///AdAYQ/zDz/8gMePH2P8+PF48803y1WOS5cu4f3338eHH35YpvMjIiIQFxeHYcOGYdeuXarjISEh6N27t9afm/fffx9xcXFYuXIlZs2apTqel5eHCRMmYM+ePRg6dCj69esHoPDnw87ODrGxsRoPDMry80FE2nGIGlE1Fx0dDZFIhHHjxqmOTZw4UbXYQHETJkyAQqHAvn371I7fvHkT169fR69evVRP3+VyOT7//HM0aNBALUkACp94Ll++HAqFAgcPHtRatpUrV2p94t6gQQON5AYA+vXrh2bNmuHHH39UO75//34IgoBFixapDV1xdHTEggULNIaLVLbc2mj7HCKRCHXq1NE4bmlpqRr+pNSoUSNMmTIFUqkUR44cUR3/7LPPIAgCIiIiNOZMTZgwAa1atcLXX3+NnJwcAIWN1NjYWAwbNkwtuQGAOnXq4IMPPoBMJlMbohgTEwNBELB48WK1hrWTkxPee++9EofbFDdu3DgIgqBx/8hkMhw5cgQODg4YPHiw2mvW1tYavSoANBp02qSmpqqGEyqTwqIuXLiANWvWqP35+uuvtcZavHixRnIDFM4h0dZz0qZNG/j7++P8+fNqdbR3714IgoD33ntPLZ61tTUWLVqk9dqffvoprK2tERERofYdAEBoaCicnJxw6NAhre8tKi8vD4cPH4aDg4PGPebp6YmQkBDI5XKtw/9q166NFStWqP08tGrVCp07d8atW7cglUpLvX5FzZ49G9HR0SgoKMDu3bsRERGB48ePw9PTE6NHj4abm5vW96WkpGDdunUIDw/Hjh078OTJE7z77ruIjIwsdxkaNGiABQsWlPn8mJgYWFhYYOnSpWrHfXx8MG3aNI2fm9TUVBw+fBgdOnRQS26Awntj8eLFWn/vWFpaVvjng4i0Yw8OUTV28eJF3L17Fz179oSvr6/q+LBhw/D+++/jyy+/RFhYGGrVqqV6bciQIXBwcMDhw4exbNky1f9YlY3gok/f79y5g4yMDLi6umLt2rUa11coFLC1tcWLine truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x108dd82e8>"
"<matplotlib.figure.Figure at 0x10b1d1390>"
]
},
"metadata": {},
@@ -90,12 +97,13 @@
" plt.style.use('fivethirtyeight')\n",
" plt.plot(X, Y, 'o')\n",
" X1 = list(range(int(max(X))))\n",
" plt.plot(X1, [F(x) for x in X1], 'o')\n",
" plt.plot(X1, [F(x) for x in X1], '-')\n",
" plt.ylabel('Speed (mph)')\n",
" plt.xlabel('Grade (feet/mile)')\n",
" plt.minorticks_on()\n",
" plt.grid(True, which='major')\n",
" plt.grid(True, which='minor', alpha=0.2)\n",
" plt.grid(True, which='minor', alpha=0.4)\n",
" plt.title('Average speed vs Grade on {} rides'.format(len(X)))\n",
" \n",
"show(X, Y) "
]
@@ -104,17 +112,15 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"So, I average about 14 mph when the road is fairly flat, with a lot of variability from 12 to 16 mph, depending more on my level of effort than on the grade of the road. But from 60 ft/mile and up, speed falls off quickly at 1 mph for every 20 ft/mile, and by 140 ft/mile, I'm down around 8 mph. Note that 140 ft/mile is only 2.7% grade, but if you figure a typical route is 1/3 up, 1/3 down, and 1/3 flatish, then that's 8% grade on the up part.\n",
"So, I average about 14 mph when the road is fairly flat, with a lot of variability from 12 to 16 mph, depending more on my level of effort than on the grade of the road. But from 60 ft/mile and up, speed falls off quickly at 1 mph for every 20 ft/mile, and by 120 ft/mile, I'm down around 10 mph, and 8.5mph at 140 ft/mile. Note that 140 ft/mile is only 2.7% grade, but if you figure a typical route is 1/3 up, 1/3 down, and 1/3 flat-ish, then that's 8% grade on the up part.\n",
"\n",
"I can use the polynomial `F` to estimate the duration of a route:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def duration(dist, climb, F=F):\n",
@@ -131,16 +137,16 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6.7314081227852185"
"6.2641828504384378"
]
},
"execution_count": 4,
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
+1 -1
View File
@@ -17,7 +17,7 @@
"\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",
"This logic puzzle has been [making the rounds](https://www.google.com/webhp?#q=cheryl%27s+birthday) (and [not always favorably](https://www.newyorker.com/cartoons/daily-cartoon/daily-cartoon-thursday-april-16th-cheryl-singapore-math)):\n",
"\n",
"\n",
"1. Albert and Bernard just became friends with Cheryl, and they want to know when her birthday is. Cheryl gave them a list of 10 possible dates:\n",
+840 -796
View File
File diff suppressed because it is too large. Load diff
+1126 -2327
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -48,7 +48,7 @@
"N = 5000 # Default size of the population\n",
"MU = 100. # Default mean of the population\n",
"\n",
"population = [random.gauss(mu=MU, sigma=MU/5) for actor in range(N)]"
"population = [random.gauss(mu=MU, sigma=MU/5) for _ in range(N)]"
]
},
{
@@ -69,7 +69,7 @@
"<tr><td>Switzerland <td> 0.337\n",
"<tr><td>United States<td> 0.408\n",
"<tr><td>Chile <td> 0.521\n",
"<tr><td>South Africe <td> 0.631\n",
"<tr><td>South Africa <td> 0.631\n",
"</table>\n",
"\n",
"\n",
@@ -182,7 +182,7 @@
"outputs": [],
"source": [
"def random_split(A, B):\n",
" \"Take all the money uin the pot and divide it randomly between the two actors.\"\n",
" \"Take all the money in the pot and divide it randomly between the two actors.\"\n",
" pot = A + B\n",
" share = random.uniform(0, pot)\n",
" return share, pot - share"
@@ -326,7 +326,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# SImulation Visualization\n",
"# Simulation Visualization\n",
"\n",
"If we want to do larger simulations we'll need a better way to visualize the results.\n",
"The function `show` does that:"
+163
View File
@@ -0,0 +1,163 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Euler's Sum of Powers Conjecture\n",
"\n",
"In 1769, Leonhard Euler [conjectured that](https://en.wikipedia.org/wiki/Euler%27s_sum_of_powers_conjecture) for all integers *n* and *k* greater than 1, if the sum of *n* *k*th powers of positive integers is itself a *k*th power, then *n* is greater than or equal to *k*. For example, this would mean that no sum of a pair of cubes (a<sup>3</sup> + b<sup>3</sup>) can be equal to another cube (c<sup>3</sup>), but a sum of three cubes can, as in 3<sup>3</sup> + 4<sup>3</sup> + 5<sup>3</sup> = 6<sup>3</sup>. \n",
"\n",
"It took 200 years to disprove the conjecture: in 1966 L. J. Lander and T. R. Parkin published a refreshingly short [article](https://projecteuclid.org/download/pdf_1/euclid.bams/1183528522) giving a counterexample of four fifth-powers that summed to another fifth power. They found it via a program that did an exhaustive search. Can we duplicate their work and find integers greater than 1 such that \n",
"*a*<sup>5</sup> + *b*<sup>5</sup> + *c*<sup>5</sup> + *d*<sup>5</sup> = *e*<sup>5</sup> ?\n",
"\n",
"## Algorithm\n",
"\n",
"An exhaustive *O*(*m*<sup>4</sup>) algorithm woud be to look at all values of *a, b, c, d* < *m* and check if *a*<sup>5</sup> + *b*<sup>5</sup> + *c*<sup>5</sup> + *d*<sup>5</sup> is a fifth power. But we can do better: a sum of four numbers is a sum of two pairs of numbers, so we\n",
"are looking for\n",
"\n",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*pair*<sub>1</sub> + *pair*<sub>2</sub> = *e*<sup>5</sup> &nbsp;&nbsp; **where** &nbsp; *pair*<sub>1</sub> = *a*<sup>5</sup> + *b*<sup>5</sup> **and** *pair*<sub>2</sub> = *c*<sup>5</sup> + *d*<sup>5</sup>.\n",
"\n",
"We will define *pairs* be a dict of `{`*a*<sup>5</sup> + *b*<sup>5</sup>`: (`*a*<sup>5</sup>`, ` *b*<sup>5</sup>`)}` entries for all *a* ≤ *b* < *m*; for example, for *a*=2 and *b*=10, the entry is `{100032: (32, 100000)}`.\n",
"Then we can ask for each *pair*<sub>1</sub>, and for each *e*, whether there is a *pair*<sub>2</sub> in the `dict` that makes the equation work. There are *O*(*m*<sup>2</sup>) pairs and *O*(*m*) values of *e*, and `dict` lookup is *O*(1), so the whole algorithm is *O*(*m*<sup>3</sup>):"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import itertools\n",
"\n",
"def euler(m):\n",
" \"\"\"Yield tuples (a, b, c, d, e) such that a^5 + b^5 + c^5 + d^5 = e^5,\n",
" where all are integers, and 1 < a ≤ b ≤ c ≤ d < e < m.\"\"\"\n",
" powers = [e**5 for e in range(2, m)] \n",
" pairs = {sum(pair): pair \n",
" for pair in itertools.combinations_with_replacement(powers, 2)}\n",
" for pair1 in pairs:\n",
" for e5 in powers:\n",
" pair2 = e5 - pair1\n",
" if pair2 in pairs:\n",
" yield fifthroots(pairs[pair1] + pairs[pair2] + (e5,))\n",
" \n",
"def fifthroots(nums): \n",
" \"Sorted integer fifth roots of a collection of numbers.\" \n",
" return tuple(sorted(int(round(x ** (1/5))) for x in nums))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's look for a solution (arbitrarily choosing *m*=500):"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.07 s, sys: 21.4 ms, total: 1.09 s\n",
"Wall time: 1.11 s\n"
]
},
{
"data": {
"text/plain": [
"(27, 84, 110, 133, 144)"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%time next(euler(500))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That was easy, and it turns out this is the same answer that Lander and Parkin got: 27<sup>5</sup> + 84<sup>5</sup> + 110<sup>5</sup> + 133<sup>5</sup> = 144<sup>5</sup>.\n",
"\n",
"We can keep going, collecting all the solutions up to `*m*=1000`:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1min 53s, sys: 706 ms, total: 1min 54s\n",
"Wall time: 1min 57s\n"
]
},
{
"data": {
"text/plain": [
"{(27, 84, 110, 133, 144),\n",
" (54, 168, 220, 266, 288),\n",
" (81, 252, 330, 399, 432),\n",
" (108, 336, 440, 532, 576),\n",
" (135, 420, 550, 665, 720),\n",
" (162, 504, 660, 798, 864)}"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%time set(euler(1000))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All the answers are multiples of the first one (this is easiest to see in the middle column: 110, 220, 330, ...).\n",
"Since 1966 other mathematicians have found [other solutions](https://en.wikipedia.org/wiki/Euler%27s_sum_of_powers_conjecture), but all we need is one to disprove Euler's conjecture."
]
}
],
"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.6.0"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+155 -186
View File
@@ -18,13 +18,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The [June 15, 2015 post on Bike Snob NYC](http://bikesnobnyc.blogspot.com/2015/06/lets-get-this-show-on-road-once-we-all.html) leads with \"*Let's talk about bike locks.*\" Here's what I want to talk about: in my local bike shop, I saw a combination lock called *WordLock&reg;*,\n",
"The [June 15, 2015 post](http://bikesnobnyc.blogspot.com/2015/06/lets-get-this-show-on-road-once-we-all.html) on *Bike Snob NYC* leads with \"*Let's talk about bike locks.*\" Here's what I want to talk about: in my local bike shop, I saw a combination lock called *WordLock&reg;*,\n",
"which replaces digits with letters. I classified this as a Fred lock,\n",
"\"[Fred](http://bikesnobnyc.blogspot.com/2014/06/a-fred-too-far.html)\" being the term for an amateurish cyclist with the wrong equipment.\n",
"\"[Fred](http://bikesnobnyc.blogspot.com/2014/06/a-fred-too-far.html)\" being the term for an amateurish cyclist with inappropriate equipment.\n",
"I tried the combination \"FRED,\" and was amused with the result:\n",
"\n",
"![](http://norvig.com/ipython/fredbuns.jpg)\n",
"\n",
"FRED BUNS! Naturally I set all the other locks on the rack to FRED BUNS as well. But my curiosity was raised ... \n",
"\n",
"# Questions\n",
@@ -36,14 +34,14 @@
"Could different letters make words in every horizontal line?\n",
"5. Is it a coincidence that the phrase \"FRED BUNS\" appears, or was it planted there by mischievous WordLock&reg; designers? \n",
"\n",
"Vocabulary\n",
"===\n",
"# Vocabulary\n",
"\n",
"\n",
"Before we can answer the questions, we'll need to be clear about the vocabulary of the problem and how to represent concepts in code:\n",
"\n",
"* **Lock**: For our purposes a lock can be modeled as a `list` of 4 **tumblers**. \n",
"* **Tumbler:** Each tumbler has 10 distinct letters. I will represent a tumbler as a `str` of 10 letters.\n",
"* **Combination**: Choosing a letter from each tumbler gives a combination, such as \"FRED\" or \"BUNS\". There are 10<sup>4</sup> = 10,000 combinations.\n",
"* **Combination**: Choosing a letter from each tumbler gives a combination, such as \"FRED\" or \"BUNS\". There are 4<sup>10</sup> = 10,000 combinations.\n",
"* **Word**: Some combinations (such as \"BUNS\") are *words*; others (such as \"SOMN\") are not words. We'll need a collection of dictionary words.\n",
"\n",
"Now on to the code! First the imports I will need and the vocabulary concepts:"
@@ -133,7 +131,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"I would prefer to deal with the string `'BUNS'` rather than the tuple `('B', 'U', 'N', 'S')`, so I will define a function, `combinations`, that takes a lock as input and returns a list of strings representing the combinations:"
"I would prefer to deal with the string `'BUNS'` rather than the tuple `('B', 'U', 'N', 'S')`, so I will define a function, `combinations`, that takes a lock as input and returns a set of strings representing the combinations:"
]
},
{
@@ -146,7 +144,7 @@
"source": [
"def combinations(lock):\n",
" \"Return a list of all combinations that can be made by this lock.\"\n",
" return [Word(combo) for combo in itertools.product(*lock)]"
" return {Word(combo) for combo in itertools.product(*lock)}"
]
},
{
@@ -159,22 +157,22 @@
{
"data": {
"text/plain": [
"['FRED',\n",
" 'FRES',\n",
" 'FRND',\n",
" 'FRNS',\n",
" 'FUED',\n",
" 'FUES',\n",
" 'FUND',\n",
" 'FUNS',\n",
" 'BRED',\n",
"{'BRED',\n",
" 'BRES',\n",
" 'BRND',\n",
" 'BRNS',\n",
" 'BUED',\n",
" 'BUES',\n",
" 'BUND',\n",
" 'BUNS']"
" 'BUNS',\n",
" 'FRED',\n",
" 'FRES',\n",
" 'FRND',\n",
" 'FRNS',\n",
" 'FUED',\n",
" 'FUES',\n",
" 'FUND',\n",
" 'FUNS'}"
]
},
"execution_count": 5,
@@ -286,7 +284,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"So that means that no lock could ever make more than 4,360 words. Let's define `words_from(lock)`:"
"So that means that no lock could ever make more than 4,360 words. Let's define `words_from(lock)` to be the intersection of the dictionary words and the words that can be made with a lock:"
]
},
{
@@ -299,7 +297,7 @@
"source": [
"def words_from(lock): \n",
" \"A list of words that can be made by lock.\"\n",
" return [c for c in combinations(lock) if c in WORDS]"
" return WORDS & combinations(lock)"
]
},
{
@@ -312,7 +310,7 @@
{
"data": {
"text/plain": [
"['FRED', 'FUND', 'FUNS', 'BRED', 'BUND', 'BUNS']"
"{'BRED', 'BUND', 'BUNS', 'FRED', 'FUND', 'FUNS'}"
]
},
"execution_count": 11,
@@ -328,8 +326,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"*Note*: An alternative is to represent a collection of words as a `set`; then `words_from` could do `return combinations(lock) & WORDS`. \n",
"\n",
"I will also introduce the function `show` to print out a lock and its words:"
]
},
@@ -442,7 +438,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Random Locks"
"# Baseline: Random Locks"
]
},
{
@@ -494,7 +490,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Wow, that's not very many words. Let's repeat 100 times and take the best one, with \"best\" determined by the function `word_count`:"
"Wow, that's not very many words. Let's repeat 100 times and take the best one, with \"best\" meaning the maximum `word_count`:"
]
},
{
@@ -542,7 +538,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Question 2: More Words (via Greedy Locks)"
"# Question 2: More Words (via Greedy Lock)"
]
},
{
@@ -563,8 +559,8 @@
"data": {
"text/plain": [
"[('S', 373),\n",
" ('T', 268),\n",
" ('P', 268),\n",
" ('T', 268),\n",
" ('B', 267),\n",
" ('D', 251),\n",
" ('C', 248),\n",
@@ -641,7 +637,7 @@
" # Then update words to only include the ones that can be made with this tumbler\n",
" counter = Counter(word[i] for word in words)\n",
" tumbler = Tumbler(L for (L, _) in counter.most_common(c))\n",
" words = [w for w in words if w[i] in tumbler]\n",
" words = {w for w in words if w[i] in tumbler}\n",
" lock.append(tumbler)\n",
" return lock"
]
@@ -657,7 +653,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Lock: STPBDCLMAR OAIEURLHYN RNALEOTISM SETADNLKPY\n",
"Lock: SPTBDCLMAR OAIEURLHYN RNALEOTISM SETADNLKPY\n",
"Count: 1177\n",
"Words: AALS AEON AERY AHED AHIS AHOY AILA AILS AIMS AINS AIRN AIRS AIRT AIRY AITS ALAE ALAN ALAS ALEA ALEE ALEK ALES ALIA ALIT ALLS ALLY ALMA ALME ALMS ALOE ALTS ANAL ANAS ANAY ANES ANIL ANIS ANNA ANNE ANOA ANON ANSA ANTA ANTE ANTS ARAK AREA ARES ARIA ARID ARIE ARIL ARMS ARMY ARON ARSE ARTS ARTY AULD AUNT AURA AYAN AYES AYIN AYLA BAAL BAAS BAIL BAIT BALD BALE BALK BALL BALS BAMS BAND BANE BANK BANS BARD BARE BARK BARN BARS BASE BASK BASS BAST BATE BATS BATT BEAD BEAK BEAN BEAT BEEN BEEP BEES BEET BELL BELS BELT BEMA BEND BENE BENS BENT BERK BEST BETA BETS BIAS BILE BILK BILL BIMA BIND BINE BINS BINT BIOS BIRD BIRK BIRL BISE BISK BITE BITS BITT BLAE BLAT BLED BLET BLIN BLIP BLOT BOAS BOAT BOIL BOLA BOLD BOLE BOLL BOLT BOND BONE BONK BONY BOOK BOON BOOS BOOT BORA BORE BORK BORN BORT BOSK BOSS BOTA BOTS BOTT BRAD BRAE BRAN BRAS BRAT BRAY BREA BRED BREE BREN BRIA BRIE BRIN BRIS BRIT BROS BULK BULL BUMP BUMS BUNA BUND BUNK BUNN BUNS BUNT BUOY BURA BURD BURL BURN BURP BURS BURY BUSK BUSS BUST BUSY BUTE BUTS BUTT BYES BYRE BYRL BYTE CAEL CAID CAIN CALE CALK CALL CAME CAMP CAMS CANE CANS CANT CARA CARD CARE CARK CARL CARN CARP CARS CART CASA CASE CASK CAST CATE CATS CEES CEIL CELL CELS CELT CENT CERE CESS CETE CHAD CHAP CHAT CHAY CHIA CHID CHIN CHIP CHIS CHIT CHON CHOP CIAN CINE CION CIRE CIST CITE CITY CLAD CLAN CLAP CLAY CLIP CLOD CLON CLOP CLOT CLOY COAL COAT COED COEN COIL COIN COLA COLD COLE COLS COLT COLY COMA COME COMP CONE CONK CONN CONS CONY COOK COOL COON COOP COOS COOT CORA CORD CORE CORK CORN CORS CORY COSS COST COSY COTE COTS CRAP CRED CRIS CRIT CROP CUED CUES CULL CULT CUNT CURD CURE CURL CURN CURS CURT CUSK CUSP CUSS CUTE CUTS CYAN CYMA CYME CYST DAIS DALE DALS DAME DAMN DAMP DAMS DANA DANE DANK DANS DARE DARK DARN DART DATA DATE DEAD DEAL DEAN DEED DEEP DEES DEET DEIL DELE DELL DELS DELT DEME DEMY DENE DENS DENT DENY DEON DERE DESK DHAK DHAL DIAL DIED DIEL DIES DIET DILL DIME DIMS DINA DINE DINK DINS DINT DIOL DION DIRE DIRK DIRL DIRT DISK DISS DITA DITE DITS DOAT DOES DOIT DOLE DOLL DOLS DOLT DOME DOMS DONA DONE DONS DORE DORK DORP DORS DORY DOSE DOSS DOST DOTE DOTS DOTY DRAT DRAY DREE DREK DRIP DROP DUAD DUAL DUEL DUES DUET DUIT DULL DULY DUMA DUMP DUNE DUNK DUNS DUNT DUOS DURA DURE DURN DUSK DUST DUTY DYAD DYED DYES DYNE LAID LAIN LALL LAMA LAME LAMP LAMS LANA LAND LANE LANK LARA LARD LARK LARS LASE LASS LAST LATE LATS LEAD LEAK LEAL LEAN LEAP LEAS LEEK LEES LEET LEIA LEIS LELA LENA LEND LENS LENT LEON LESS LEST LETS LIAN LIED LIEN LIES LILA LILT LILY LIMA LIME LIMN LIMP LIMY LINA LINE LINK LINN LINS LINT LINY LION LIRA LIRE LISA LISP LIST LITE LITS LOAD LOAN LOID LOIN LOLA LOLL LONE LOOK LOON LOOP LOOS LOOT LORD LORE LORN LORY LOSE LOSS LOST LOTA LOTS LUES LUIS LULL LUMA LUMP LUMS LUNA LUNE LUNK LUNT LUNY LURE LURK LUST LUTE LYES LYLA LYLE LYRA LYRE LYSE MAES MAIA MAID MAIL MAIN MALE MALL MALT MAMA MANA MANE MANS MANY MARA MARE MARK MARL MARS MART MARY MASA MASK MASS MAST MATE MATS MATT MEAD MEAL MEAN MEAT MEED MEEK MEET MELD MELL MELS MELT MEME MEMS MEND MERE MERK MERL MESA MESS META METE MHOS MIEN MILA MILD MILE MILK MILL MILS MILT MIME MINA MIND MINE MINK MINT MIRA MIRE MIRK MIRS MIRY MISE MISS MIST MITE MITT MITY MOAN MOAS MOAT MOIL MOLA MOLD MOLE MOLL MOLS MOLT MOLY MOME MOMS MONA MONK MONS MONY MOOD MOOL MOON MOOS MOOT MORA MORE MORN MORS MORT MOSK MOSS MOST MOTE MOTS MOTT MULE MULL MUMP MUMS MUNS MUON MURA MURE MURK MUSA MUSE MUSK MUSS MUST MUTE MUTS MUTT MYLA MYNA MYRA PAID PAIK PAIL PAIN PALE PALL PALP PALS PALY PAMS PANE PANS PANT PARA PARD PARE PARK PARS PART PASE PASS PAST PATE PATS PATY PEAK PEAL PEAN PEAS PEAT PEED PEEK PEEL PEEN PEEP PEES PEIN PELE PELT PEND PENS PENT PEON PERE PERK PERP PERT PEST PETS PHAT PHIS PHON PHOT PIAL PIAN PIAS PIED PIES PILE PILL PILY PIMA PIMP PINA PINE PINK PINS PINT PINY PION PIRN PISS PITA PITS PITY PLAN PLAT PLAY PLEA PLED PLIE PLOD PLOP PLOT PLOY POET POIS POLE POLL POLS POLY POME POMP POMS POND PONE PONS PONY POOD POOL POON POOP POOS PORE PORK PORN PORT POSE POST POSY POTS PRAT PRAY PREE PREP PREY PROA PROD PROP PROS PULA PULE PULL PULP PULS PUMA PUMP PUNA PUNK PUNS PUNT PUNY PURE PURL PURS PUSS PUTS PUTT PYAS PYES PYIN PYRE RAIA RAID RAIL RAIN RAIS RALE RAMP RAMS RAND RANK RANT RARE RASE RASP RATE RATS READ REAL REAP REED REEK REEL REES REID REIN REIS RELY REMS REMY REND RENE RENT REST RETE RETS RHEA RHOS RIAL RIAN RIAS RIEL RILE RILL RIME RIMS RIMY RIND RINK RINS RIOT RISE RISK RITA RITE ROAD ROAN ROES ROIL ROLE ROLL ROME ROMP ROMS ROOD ROOK ROOT RORY ROSA ROSE ROSS ROSY ROTA ROTE ROTL ROTS RUED RUES RUIN RULE RULY RUMP RUMS RUNE RUNS RUNT RUSE RUSK RUST RUTS RYAN RYAS RYES RYND RYOT SAAD SAID SAIL SAIN SALE SALL SALP SALS SALT SAME SAMP SANA SAND SANE SANK SANS SARA SARD SARK SASS SATE SEAL SEAN SEAS SEAT SEED SEEK SEEL SEEN SEEP SEES SEIS SELL SELS SEME SEND SENE SENT SERA SERE SERS SETA SETS SETT SHAD SHAE SHAT SHAY SHEA SHED SHES SHIN SHIP SHIT SHOD SHOE SHOP SHOT SIAL SILD SILK SILL SILT SIMA SIMP SIMS SINE SINK SINS SIRE SIRS SITE Line truncated
]
@@ -742,7 +738,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Lock: STPBDCLMWF OAIEURLHYP RNALEOTISC SETADNLKHY\n",
"Lock: SPTBDCLMWF OAIEURLHYP RNALEOTISC SETADNLKHY\n",
"Count: 1240\n",
"Words: BAAL BAAS BACH BACK BAIL BAIT BALD BALE BALK BALL BALS BAND BANE BANK BANS BARD BARE BARK BARN BARS BASE BASH BASK BASS BAST BATE BATH BATS BATT BEAD BEAK BEAN BEAT BECK BEEN BEES BEET BELL BELS BELT BEND BENE BENS BENT BERK BEST BETA BETH BETS BIAS BICE BILE BILK BILL BIND BINE BINS BINT BIOS BIRD BIRK BIRL BISE BISK BITE BITS BITT BLAE BLAH BLAT BLED BLET BLIN BLOT BOAS BOAT BOCK BOIL BOLA BOLD BOLE BOLL BOLT BOND BONE BONK BONY BOOK BOON BOOS BOOT BORA BORE BORK BORN BORT BOSH BOSK BOSS BOTA BOTH BOTS BOTT BRAD BRAE BRAN BRAS BRAT BRAY BREA BRED BREE BREN BRIA BRIE BRIN BRIS BRIT BROS BUCK BULK BULL BUNA BUND BUNK BUNN BUNS BUNT BUOY BURA BURD BURL BURN BURS BURY BUSH BUSK BUSS BUST BUSY BUTE BUTS BUTT BYES BYRE BYRL BYTE CACA CAEL CAID CAIN CALE CALK CALL CANE CANS CANT CARA CARD CARE CARK CARL CARN CARS CART CASA CASE CASH CASK CAST CATE CATS CECA CEES CEIL CELL CELS CELT CENT CERE CESS CETE CHAD CHAT CHAY CHIA CHID CHIN CHIS CHIT CHON CIAN CINE CION CIRE CIST CITE CITY CLAD CLAN CLAY CLOD CLON CLOT CLOY COAL COAT COCA COCK COED COEN COIL COIN COLA COLD COLE COLS COLT COLY CONE CONK CONN CONS CONY COOK COOL COON COOS COOT CORA CORD CORE CORK CORN CORS CORY COSH COSS COST COSY COTE COTS CRED CRIS CRIT CUED CUES CULL CULT CUNT CURD CURE CURL CURN CURS CURT CUSK CUSS CUTE CUTS CYAN CYST DACE DAIS DALE DALS DANA DANE DANK DANS DARE DARK DARN DART DASH DATA DATE DEAD DEAL DEAN DECK DEED DEES DEET DEIL DELE DELL DELS DELT DENE DENS DENT DENY DEON DERE DESK DHAK DHAL DIAL DICE DICK DIED DIEL DIES DIET DILL DINA DINE DINK DINS DINT DIOL DION DIRE DIRK DIRL DIRT DISH DISK DISS DITA DITE DITS DOAT DOCK DOCS DOES DOIT DOLE DOLL DOLS DOLT DONA DONE DONS DORE DORK DORS DORY DOSE DOSS DOST DOTE DOTH DOTS DOTY DRAT DRAY DREE DREK DUAD DUAL DUCE DUCK DUCT DUEL DUES DUET DUIT DULL DULY DUNE DUNK DUNS DUNT DUOS DURA DURE DURN DUSK DUST DUTY DYAD DYED DYES DYNE FACE FACT FAIL FAIN FALL FANE FANS FARD FARE FARL FART FASH FAST FATE FATS FEAL FEAT FECK FEED FEEL FEES FEET FELL FELT FEND FENS FEOD FERE FERN FESS FEST FETA FETE FETS FIAT FICE FILA FILE FILL FILS FIND FINE FINK FINN FINS FIRE FIRN FIRS FISH FIST FITS FLAK FLAN FLAT FLAY FLEA FLED FLEE FLEY FLIT FLOE FOAL FOES FOIL FOIN FOLD FOLK FOND FONS FONT FOOD FOOL FOOT FORA FORD FORE FORK FORT FOSS FRAE FRAT FRAY FRED FREE FRET FRIT FROE FUCK FUEL FULL FUND FUNK FUNS FURL FURS FURY FUSE FUSS FYCE LACE LACK LACS LACY LAID LAIN LALL LANA LAND LANE LANK LARA LARD LARK LARS LASE LASH LASS LAST LATE LATH LATS LEAD LEAH LEAK LEAL LEAN LEAS LECH LEEK LEES LEET LEIA LEIS LELA LENA LEND LENS LENT LEON LESS LEST LETS LIAN LICE LICH LICK LIED LIEN LIES LILA LILT LILY LINA LINE LINK LINN LINS LINT LINY LION LIRA LIRE LISA LIST LITE LITS LOAD LOAN LOCA LOCH LOCK LOID LOIN LOLA LOLL LONE LOOK LOON LOOS LOOT LORD LORE LORN LORY LOSE LOSS LOST LOTA LOTH LOTS LUCA LUCE LUCK LUCY LUES LUIS LULL LUNA LUNE LUNK LUNT LUNY LURE LURK LUSH LUST LUTE LYCH LYES LYLA LYLE LYRA LYRE LYSE MACE MACH MACK MACS MACY MAES MAIA MAID MAIL MAIN MALE MALL MALT MANA MANE MANS MANY MARA MARE MARK MARL MARS MART MARY MASA MASH MASK MASS MAST MATE MATH MATS MATT MEAD MEAL MEAN MEAT MEED MEEK MEET MELD MELL MELS MELT MEND MERE MERK MERL MESA MESH MESS META METE METH MHOS MIAH MICA MICE MICK MICS MIEN MILA MILD MILE MILK MILL MILS MILT MINA MIND MINE MINK MINT MIRA MIRE MIRK MIRS MIRY MISE MISS MIST MITE MITT MITY MOAN MOAS MOAT MOCK MOCS MOIL MOLA MOLD MOLE MOLL MOLS MOLT MOLY MONA MONK MONS MONY MOOD MOOL MOON MOOS MOOT MORA MORE MORN MORS MORT MOSH MOSK MOSS MOST MOTE MOTH MOTS MOTT MUCH MUCK MULE MULL MUNS MUON MURA MURE MURK MUSA MUSE MUSH MUSK MUSS MUST MUTE MUTS MUTT MYAH MYCS MYLA MYNA MYRA MYTH PACA PACE PACK PACS PACT PACY PAID PAIK PAIL PAIN PALE PALL PALS PALY PANE PANS PANT PARA PARD PARE PARK PARS PART PASE PASH PASS PAST PATE PATH PATS PATY PEAK PEAL PEAN PEAS PEAT PECH PECK PECS PEED PEEK PEEL PEEN PEES PEIN PELE PELT PEND PENS PENT PEON PERE PERK PERT PEST PETS PHAT PHIS PHON PHOT PIAL PIAN PIAS PICA PICE PICK PICS PIED PIES PILE PILL PILY PINA PINE PINK PINS PINT PINY PION PIRN PISH PISS PITA PITH PITS PITY PLAN PLAT PLAY PLEA PLED PLIE PLOD PLOT PLOY POCK POET POIS POLE POLL POLS POLY POND PONE PONS PONY POOD POOH POOL POON POOS PORE PORK PORN PORT POSE POSH POST POSY POTS PRAT PRAY PREE PREY PROA PROD PROS PUCE PUCK PULA PULE PULL PULS PUNA PUNK PUNS PUNT PUNY PURE PURL PURS PUSH PUSS PUTS PUTT PYAS PYES PYIN PYRE SAAD SACK SACS SAID SAIL SAIN SALE SALL SALS SALT SANA SAND SANE SANK SANS SARA SARD SARK SASH SASS SATE SEAL SEAN SEAS SEAT SECS SECT SEED SEEK SEEL SEEN SEES SEIS SELL SELS SEND SENE SENT SERA SERE SERS SETA SETH SETS SETT SHAD SHAE SHAH SHAT SHAY SHEA SHED SHES SHIN SHIT SHOD SHOE SHOT SIAL SICE SICK SICS SILD SILK SILL SILT SINE SINH SINK SINS SIRE SIRS SITE SITH SITS SLAT SLAY SLED SLID SLIT SLOE SLOT SOAK SOCA SOCK SOIL SOLA SOLD SOLE SOLS SONE SONS SOOK SOON SOOT SORA SORD SORE SORN SORT SOTH SOTS SPAE SPAN SPAS SPAT SPAY SPED SPIK SPIN SPIT SPOT SPRY SRIS SUCH Line truncated
]
@@ -756,7 +752,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"How about starting from a random lock? "
"We got up to 1240 words, an improvement of 5%, but can we go beyond that? I'll improve 50 random locks (this will take around 7 minutes):"
]
},
{
@@ -770,41 +766,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Lock: TMSLBPFWCD AEHYLORIWU CNATELISRO LDSYANKETH\n",
"Count: 1240\n",
"Words: BAAL BAAS BACH BACK BAIL BAIT BALD BALE BALK BALL BALS BAND BANE BANK BANS BARD BARE BARK BARN BARS BASE BASH BASK BASS BAST BATE BATH BATS BATT BEAD BEAK BEAN BEAT BECK BEEN BEES BEET BELL BELS BELT BEND BENE BENS BENT BERK BEST BETA BETH BETS BIAS BICE BILE BILK BILL BIND BINE BINS BINT BIOS BIRD BIRK BIRL BISE BISK BITE BITS BITT BLAE BLAH BLAT BLED BLET BLIN BLOT BOAS BOAT BOCK BOIL BOLA BOLD BOLE BOLL BOLT BOND BONE BONK BONY BOOK BOON BOOS BOOT BORA BORE BORK BORN BORT BOSH BOSK BOSS BOTA BOTH BOTS BOTT BRAD BRAE BRAN BRAS BRAT BRAY BREA BRED BREE BREN BRIA BRIE BRIN BRIS BRIT BROS BUCK BULK BULL BUNA BUND BUNK BUNN BUNS BUNT BUOY BURA BURD BURL BURN BURS BURY BUSH BUSK BUSS BUST BUSY BUTE BUTS BUTT BYES BYRE BYRL BYTE CACA CAEL CAID CAIN CALE CALK CALL CANE CANS CANT CARA CARD CARE CARK CARL CARN CARS CART CASA CASE CASH CASK CAST CATE CATS CECA CEES CEIL CELL CELS CELT CENT CERE CESS CETE CHAD CHAT CHAY CHIA CHID CHIN CHIS CHIT CHON CIAN CINE CION CIRE CIST CITE CITY CLAD CLAN CLAY CLOD CLON CLOT CLOY COAL COAT COCA COCK COED COEN COIL COIN COLA COLD COLE COLS COLT COLY CONE CONK CONN CONS CONY COOK COOL COON COOS COOT CORA CORD CORE CORK CORN CORS CORY COSH COSS COST COSY COTE COTS CRED CRIS CRIT CUED CUES CULL CULT CUNT CURD CURE CURL CURN CURS CURT CUSK CUSS CUTE CUTS CYAN CYST DACE DAIS DALE DALS DANA DANE DANK DANS DARE DARK DARN DART DASH DATA DATE DEAD DEAL DEAN DECK DEED DEES DEET DEIL DELE DELL DELS DELT DENE DENS DENT DENY DEON DERE DESK DHAK DHAL DIAL DICE DICK DIED DIEL DIES DIET DILL DINA DINE DINK DINS DINT DIOL DION DIRE DIRK DIRL DIRT DISH DISK DISS DITA DITE DITS DOAT DOCK DOCS DOES DOIT DOLE DOLL DOLS DOLT DONA DONE DONS DORE DORK DORS DORY DOSE DOSS DOST DOTE DOTH DOTS DOTY DRAT DRAY DREE DREK DUAD DUAL DUCE DUCK DUCT DUEL DUES DUET DUIT DULL DULY DUNE DUNK DUNS DUNT DUOS DURA DURE DURN DUSK DUST DUTY DYAD DYED DYES DYNE FACE FACT FAIL FAIN FALL FANE FANS FARD FARE FARL FART FASH FAST FATE FATS FEAL FEAT FECK FEED FEEL FEES FEET FELL FELT FEND FENS FEOD FERE FERN FESS FEST FETA FETE FETS FIAT FICE FILA FILE FILL FILS FIND FINE FINK FINN FINS FIRE FIRN FIRS FISH FIST FITS FLAK FLAN FLAT FLAY FLEA FLED FLEE FLEY FLIT FLOE FOAL FOES FOIL FOIN FOLD FOLK FOND FONS FONT FOOD FOOL FOOT FORA FORD FORE FORK FORT FOSS FRAE FRAT FRAY FRED FREE FRET FRIT FROE FUCK FUEL FULL FUND FUNK FUNS FURL FURS FURY FUSE FUSS FYCE LACE LACK LACS LACY LAID LAIN LALL LANA LAND LANE LANK LARA LARD LARK LARS LASE LASH LASS LAST LATE LATH LATS LEAD LEAH LEAK LEAL LEAN LEAS LECH LEEK LEES LEET LEIA LEIS LELA LENA LEND LENS LENT LEON LESS LEST LETS LIAN LICE LICH LICK LIED LIEN LIES LILA LILT LILY LINA LINE LINK LINN LINS LINT LINY LION LIRA LIRE LISA LIST LITE LITS LOAD LOAN LOCA LOCH LOCK LOID LOIN LOLA LOLL LONE LOOK LOON LOOS LOOT LORD LORE LORN LORY LOSE LOSS LOST LOTA LOTH LOTS LUCA LUCE LUCK LUCY LUES LUIS LULL LUNA LUNE LUNK LUNT LUNY LURE LURK LUSH LUST LUTE LYCH LYES LYLA LYLE LYRA LYRE LYSE MACE MACH MACK MACS MACY MAES MAIA MAID MAIL MAIN MALE MALL MALT MANA MANE MANS MANY MARA MARE MARK MARL MARS MART MARY MASA MASH MASK MASS MAST MATE MATH MATS MATT MEAD MEAL MEAN MEAT MEED MEEK MEET MELD MELL MELS MELT MEND MERE MERK MERL MESA MESH MESS META METE METH MHOS MIAH MICA MICE MICK MICS MIEN MILA MILD MILE MILK MILL MILS MILT MINA MIND MINE MINK MINT MIRA MIRE MIRK MIRS MIRY MISE MISS MIST MITE MITT MITY MOAN MOAS MOAT MOCK MOCS MOIL MOLA MOLD MOLE MOLL MOLS MOLT MOLY MONA MONK MONS MONY MOOD MOOL MOON MOOS MOOT MORA MORE MORN MORS MORT MOSH MOSK MOSS MOST MOTE MOTH MOTS MOTT MUCH MUCK MULE MULL MUNS MUON MURA MURE MURK MUSA MUSE MUSH MUSK MUSS MUST MUTE MUTS MUTT MYAH MYCS MYLA MYNA MYRA MYTH PACA PACE PACK PACS PACT PACY PAID PAIK PAIL PAIN PALE PALL PALS PALY PANE PANS PANT PARA PARD PARE PARK PARS PART PASE PASH PASS PAST PATE PATH PATS PATY PEAK PEAL PEAN PEAS PEAT PECH PECK PECS PEED PEEK PEEL PEEN PEES PEIN PELE PELT PEND PENS PENT PEON PERE PERK PERT PEST PETS PHAT PHIS PHON PHOT PIAL PIAN PIAS PICA PICE PICK PICS PIED PIES PILE PILL PILY PINA PINE PINK PINS PINT PINY PION PIRN PISH PISS PITA PITH PITS PITY PLAN PLAT PLAY PLEA PLED PLIE PLOD PLOT PLOY POCK POET POIS POLE POLL POLS POLY POND PONE PONS PONY POOD POOH POOL POON POOS PORE PORK PORN PORT POSE POSH POST POSY POTS PRAT PRAY PREE PREY PROA PROD PROS PUCE PUCK PULA PULE PULL PULS PUNA PUNK PUNS PUNT PUNY PURE PURL PURS PUSH PUSS PUTS PUTT PYAS PYES PYIN PYRE SAAD SACK SACS SAID SAIL SAIN SALE SALL SALS SALT SANA SAND SANE SANK SANS SARA SARD SARK SASH SASS SATE SEAL SEAN SEAS SEAT SECS SECT SEED SEEK SEEL SEEN SEES SEIS SELL SELS SEND SENE SENT SERA SERE SERS SETA SETH SETS SETT SHAD SHAE SHAH SHAT SHAY SHEA SHED SHES SHIN SHIT SHOD SHOE SHOT SIAL SICE SICK SICS SILD SILK SILL SILT SINE SINH SINK SINS SIRE SIRS SITE SITH SITS SLAT SLAY SLED SLID SLIT SLOE SLOT SOAK SOCA SOCK SOIL SOLA SOLD SOLE SOLS SONE SONS SOOK SOON SOOT SORA SORD SORE SORN SORT SOTH SOTS SRIS SUCH SUCK SUED SUES SUET SUIT SULK SUNK SUNN SUNS SURA SURD Line truncated
"CPU times: user 6min 50s, sys: 1.44 s, total: 6min 51s\n",
"Wall time: 6min 53s\n"
]
}
],
"source": [
"show(improved_lock(random_lock()))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We got up to 1240 words, but can we go beyond? I'll improve 50 random locks, taking 6000 steps each rather than just 3000 (this will take around 15 minutes):"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 15min 48s, sys: 9.03 s, total: 15min 57s\n",
"Wall time: 19min 40s\n"
]
}
],
"source": [
"%time improved_locks = [improved_lock(random_lock(seed=i), 6000) for i in range(50)]"
"%time improved_locks = [improved_lock(random_lock(seed=i)) for i in range(50)]"
]
},
{
@@ -816,7 +784,7 @@
},
{
"cell_type": "code",
"execution_count": 28,
"execution_count": 27,
"metadata": {
"collapsed": false
},
@@ -824,10 +792,10 @@
{
"data": {
"text/plain": [
"Counter({1232: 3, 1235: 5, 1240: 42})"
"Counter({1232: 6, 1235: 5, 1237: 8, 1240: 31})"
]
},
"execution_count": 28,
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
@@ -840,13 +808,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"So 42/50 locks got a score of 1240.\n",
"My first reaction to this was that if I discovered 42 different locks with score 1240, then probably there is at least one undiscovered lock with a score above 1240. But a discussion with [Matt Chisholm](https://blog.glyphobet.net/faq) changed my thinking. The key is to realize that some locks that look different are actually the same; they just have the letters in a different order. I define the function `alock` to put each tumbler into alphabetical order (and make the lock be a tuple rather than a list, so that it can be an entry in a `dict` or `set`):"
"So 31/50 locks got a score of 1240.\n",
"My first reaction to this was that if I discovered 31 different locks with score 1240, then probably there is at least one undiscovered lock with a score above 1240. But a discussion with [Matt Chisholm](https://blog.glyphobet.net/faq) changed my thinking. The key is to realize that some locks that look different are actually the same; they just have the letters in a different order. I define the function `alock` to put each tumbler into alphabetical order (and make the lock be a tuple rather than a list, so that it can be an entry in a `dict` or `set`):"
]
},
{
"cell_type": "code",
"execution_count": 29,
"execution_count": 28,
"metadata": {
"collapsed": true
},
@@ -866,7 +834,7 @@
},
{
"cell_type": "code",
"execution_count": 30,
"execution_count": 47,
"metadata": {
"collapsed": false
},
@@ -877,10 +845,15 @@
"{('BCDFHLMPST', 'AEHILORUWY', 'ACELMNORST', 'ADEKLNPSTY'): 1232,\n",
" ('BCDFLMPSTW', 'AEHILOPRUY', 'ACEILNORST', 'ADEHKLNSTY'): 1240,\n",
" ('BCDFLMPSTW', 'AEHILORUWY', 'ACEILNORST', 'ADEHKLNSTY'): 1240,\n",
" ('BCDGLMPSTW', 'AEHILORUWY', 'AEILMNORST', 'ADEKLNPSTY'): 1235}"
" ('BCDGLMPSTW', 'AEHILORUWY', 'AEILMNORST', 'ADEKLNPSTY'): 1235,\n",
" ('BCDHLMPSTW', 'AEHILOPRUY', 'ACEILNPRST', 'ADEHKLNSTY'): 1232,\n",
" ('BCDHLMPSTW', 'AEHILORUWY', 'ACEILNORST', 'ADEHKLNSTY'): 1237,\n",
" ('BCDHLMPSTW', 'AEHILORUWY', 'ACEILNPRST', 'ADEHKLNSTY'): 1232,\n",
" ('BCDLMPRSTW', 'AEHILOPRUY', 'ACEILNPRST', 'ADEHKLNSTY'): 1232,\n",
" ('BCDLMPRSTW', 'AEHILORUWY', 'ACEILNORST', 'ADEHKLNSTY'): 1237}"
]
},
"execution_count": 30,
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
@@ -898,12 +871,12 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"So out of the 50 `improved_locks` there are actually only 4 distinct ones. And only two have a score of 1240:"
"So out of the 50 `improved_locks` there are actually only 9 distinct ones. And only two have a score of 1240:"
]
},
{
"cell_type": "code",
"execution_count": 31,
"execution_count": 48,
"metadata": {
"collapsed": false
},
@@ -915,14 +888,13 @@
" ('BCDFLMPSTW', 'AEHILORUWY', 'ACEILNORST', 'ADEHKLNSTY')}"
]
},
"execution_count": 31,
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"peaks = {alock(lock) for lock in improved_locks if word_count(lock) == 1240}\n",
"peaks"
"{L for L in _ if word_count(L) == 1240}"
]
},
{
@@ -931,48 +903,14 @@
"source": [
"These two differ in just one letter (a `P` or a `W` in the second tumbler). \n",
"\n",
"This discovery changes my whole thinking about the space of scores for locks. Previously I imagined a spiky \"porcupine-shaped\" landscape, with 42 different peaks hitting a height of 1240. But now I have a different picture of the landscape: a single peak containing the two locks (one with a `P` and one with a `W`), surrounded by rolling hills. To verify this picture, I'll look at all the neighbors of the two peak locks, and see which ones are at least 1240:"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"{('BCDFLMPSTW', 'AEHILOPRUY', 'ACEILNORST', 'ADEHKLNSTY'): 1240,\n",
" ('BCDFLMPSTW', 'AEHILORUWY', 'ACEILNORST', 'ADEHKLNSTY'): 1240}"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alphabetset = set(alphabet)\n",
"\n",
"def neighborhood(lock): \n",
" \"Yield all locks resulting from a change of one letter in one tumbler.\"\n",
" for i in range(len(lock)):\n",
" for new in alphabetset - set(lock[i]):\n",
" for old in lock[i]:\n",
" lock2 = Lock(lock)\n",
" lock2[i] = lock2[i].replace(old, new)\n",
" yield lock2\n",
" \n",
"unique_locks(lock for peak in peaks for lock in neighborhood(peak) if word_count(lock) >= 1240)"
"This discovery changes my whole thinking about the space of scores for locks. Previously I imagined a spiky \"porcupine-shaped\" landscape, with 31 different peaks hitting a height of 1240 (and probably other peaks that we haven't discovered yet). But now I have a different picture of the landscape: a single peak containing the two locks (one with a `P` and one with a `W`), surrounded by rolling hills."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nothing new. This doesn't prove there is no lock with score over 1240, but it does mean we have looked hard to find one and failed.\n",
"\n",
"\n",
"# Question 3: Simultaneous Words"
]
@@ -981,14 +919,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Can we make a lock that spells 10 words simultaneously? One possible approach would be to start with any lock and randomly change it (just as we did with `improved_lock`), but measure improvements by the number of words formed. My intuition is that this approach would work, eventually, but that progress would be very slow, because most random changes to a letter would not make a word.\n",
"Can we make a lock that spells 10 words simultaneously? One possible approach would be to start with any lock and randomly change it (just as we did with `improved_lock`), but measuring improvements by the number of horizontal words formed. My intuition is that this approach would work, eventually, but that progress would be slow, because most random changes to a letter would not make a word.\n",
"\n",
"An alternative approach is to think of the lock not as a list of 4 vertical tumblers (each with 10 letters), but rather as a list of 10 horizontal words (each with 4 letters). I'll call this the *word list* representation, and note that a lock and a word list are *[matrix transposes](http://en.wikipedia.org/wiki/Transpose)* of each other&mdash;they swap rows for columns. There is an [old trick](https://books.google.com/books?id=eH6jBQAAQBAJ&pg=PA574&lpg=PA574&dq=lisp+transpose+matrix&source=bl&ots=Yixwj8m3k4&sig=KoeuJnFhRnJsiD06_Cx56rUOetQ&hl=en&sa=X&ved=0CB4Q6AEwAGoVChMIyM-WiriLxgIVD6OICh2QcwBK#v=onepage&q=transpose%20matrix&f=false) to compute the transpose of a matrix `M` with the expression `zip(*M)`. But `zip` returns tuples; we want strings, so we can define `transpose` as:"
]
},
{
"cell_type": "code",
"execution_count": 33,
"execution_count": 49,
"metadata": {
"collapsed": false
},
@@ -1006,7 +944,7 @@
},
{
"cell_type": "code",
"execution_count": 34,
"execution_count": 50,
"metadata": {
"collapsed": false
},
@@ -1026,7 +964,7 @@
" 'BIKE']"
]
},
"execution_count": 34,
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
@@ -1058,7 +996,7 @@
},
{
"cell_type": "code",
"execution_count": 35,
"execution_count": 51,
"metadata": {
"collapsed": false
},
@@ -1103,7 +1041,7 @@
},
{
"cell_type": "code",
"execution_count": 36,
"execution_count": 52,
"metadata": {
"collapsed": false
},
@@ -1111,19 +1049,19 @@
{
"data": {
"text/plain": [
"['KNUR',\n",
" 'FLAG',\n",
" 'APES',\n",
" 'EVIL',\n",
" 'COZY',\n",
" 'TICK',\n",
" 'PHON',\n",
" 'LATU',\n",
" 'DUPE',\n",
" 'REFT']"
"['RAID',\n",
" 'ANSH',\n",
" 'OYER',\n",
" 'UPON',\n",
" 'KUDU',\n",
" 'GLUM',\n",
" 'JIMP',\n",
" 'IFFY',\n",
" 'NEVE',\n",
" 'FOAL']"
]
},
"execution_count": 36,
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
@@ -1136,12 +1074,12 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"That was easy! Can we go to 11?"
"That was easy! Can we [go to 11](https://www.youtube.com/watch?v=4xgx4k83zzc)?"
]
},
{
"cell_type": "code",
"execution_count": 37,
"execution_count": 53,
"metadata": {
"collapsed": false
},
@@ -1149,20 +1087,20 @@
{
"data": {
"text/plain": [
"['BRAS',\n",
" 'EMIR',\n",
" 'NIPA',\n",
" 'PESO',\n",
" 'ONYX',\n",
" 'WYNN',\n",
" 'CUKE',\n",
" 'MOLY',\n",
" 'FLOC',\n",
" 'THEM',\n",
" 'HARL']"
"['AMAH',\n",
" 'HARM',\n",
" 'UPDO',\n",
" 'LIVE',\n",
" 'GWEN',\n",
" 'FELT',\n",
" 'MUFF',\n",
" 'ROTI',\n",
" 'SCUP',\n",
" 'EDGY',\n",
" 'BROS']"
]
},
"execution_count": 37,
"execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
@@ -1199,7 +1137,7 @@
},
{
"cell_type": "code",
"execution_count": 38,
"execution_count": 54,
"metadata": {
"collapsed": false
},
@@ -1227,7 +1165,7 @@
},
{
"cell_type": "code",
"execution_count": 39,
"execution_count": 55,
"metadata": {
"collapsed": false
},
@@ -1246,7 +1184,7 @@
},
{
"cell_type": "code",
"execution_count": 40,
"execution_count": 56,
"metadata": {
"collapsed": false
},
@@ -1267,7 +1205,7 @@
},
{
"cell_type": "code",
"execution_count": 41,
"execution_count": 57,
"metadata": {
"collapsed": false
},
@@ -1275,19 +1213,19 @@
{
"data": {
"text/plain": [
"['UMAR',\n",
" 'DISH',\n",
" 'TEMP',\n",
" 'JUDE',\n",
" 'WORM',\n",
" 'STOW',\n",
" 'BHUT',\n",
" 'YAGI',\n",
" 'ACTA',\n",
" 'GYPS']"
"['TUBA',\n",
" 'PROF',\n",
" 'AXAL',\n",
" 'SCUD',\n",
" 'YAWN',\n",
" 'FLEY',\n",
" 'EGGS',\n",
" 'BILK',\n",
" 'CHIT',\n",
" 'LEHR']"
]
},
"execution_count": 41,
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
@@ -1310,29 +1248,18 @@
"There is still one unanswered question: did the designers of WordLock&reg; deliberately put \"FRED BUNS\" in, or was it a coincidence? Hacker News reader [emhart](https://news.ycombinator.com/user?id=emhart) (aka the competitive lockpicker Schyler Towne) astutely commented that he had found the [patent](https://www.google.com/patents/US6621405) assigned to WordLock; it describes an algorithm similar to my `greedy_lock`.\n",
"After seeing that, I'm inclined to believe that \"FRED BUNS\" is the coincidental result of running the algorithm. On\n",
"the other hand, there is a [followup patent](https://www.google.com/patents/US20080053167) that discusses a refinement\n",
"\"wherein the letters on the wheels are configured to spell a first word displayed on a first row of letters and a second word displayed on a second row of letters.\" So the possibility of a two-word phrase was somthing that Wordlock LLc. was aware of.\n",
"\"wherein the letters on the wheels are configured to spell a first word displayed on a first row of letters and a second word displayed on a second row of letters.\" So the possibility of a two-word phrase was something that Wordlock LLc. was aware of.\n",
"\n",
"We see below that the procedure described in the [patent](https://www.google.com/patents/US6621405) is not quite as good as `greedy_lock`, because the patent states that at each tumbler position \"*the entire word list is scanned*\" to produce the letter frequencies, whereas `greedy_lock` scans only the words that are consistent with the previous tumblers. Because of that difference, `greedy_lock` produces more words, 1177 to 1161:"
]
},
{
"cell_type": "code",
"execution_count": 42,
"execution_count": 58,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(1177, 1161)"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"def greedy_lock_patented(t=4, c=10, words=WORDS):\n",
" \"Make a lock with t tumblers, each consisting of the c letters that cover the most words at that position.\"\n",
@@ -1342,11 +1269,53 @@
" # Then update words to only include the ones that can be made with this tumbler\n",
" counter = Counter(word[i] for word in words)\n",
" tumbler = Tumbler(L for (L, _) in counter.most_common(c))\n",
" # words = [w for w in words if w[i] in tumbler] # <<<< The patent does not update the word list\n",
" # words = {w for w in words if w[i] in tumbler} # <<<< The patent does not update the word list\n",
" lock.append(tumbler)\n",
" return lock\n",
"\n",
"word_count(greedy_lock()), word_count(greedy_lock_patented())"
" return lock"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"1177"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"word_count(greedy_lock())"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"1161"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"word_count(greedy_lock_patented())"
]
},
{
@@ -1366,7 +1335,7 @@
},
{
"cell_type": "code",
"execution_count": 43,
"execution_count": 61,
"metadata": {
"collapsed": false
},
@@ -1377,7 +1346,7 @@
"'pass'"
]
},
"execution_count": 43,
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
@@ -1394,9 +1363,9 @@
" assert len(WORDS) == 4360\n",
" \n",
" assert fredbuns == ['FB', 'RU', 'EN', 'DS']\n",
" assert combinations(fredbuns) == ['FRED','FRES','FRND','FRNS','FUED','FUES','FUND','FUNS',\n",
" 'BRED','BRES','BRND','BRNS','BUED','BUES','BUND','BUNS']\n",
" assert words_from(fredbuns) == ['FRED', 'FUND', 'FUNS', 'BRED', 'BUND', 'BUNS']\n",
" assert combinations(fredbuns) == {'FRED','FRES','FRND','FRNS','FUED','FUES','FUND','FUNS',\n",
" 'BRED','BRES','BRND','BRNS','BUED','BUES','BUND','BUNS'}\n",
" assert words_from(fredbuns) == {'FRED', 'FUND', 'FUNS', 'BRED', 'BUND', 'BUNS'}\n",
" assert \"FRED\" in words_from(wordlock) and \"BUNS\" in words_from(wordlock)\n",
"\n",
" assert wordlock == ['SPHMTWDLFB', 'LEYHNRUOAI', 'ENMLRTAOSK', 'DSNMPYLKTE']\n",
@@ -1427,7 +1396,7 @@
},
{
"cell_type": "code",
"execution_count": 48,
"execution_count": 62,
"metadata": {
"collapsed": false
},
@@ -1438,7 +1407,7 @@
"{7, 15, 23, 28, 31, 39, 47, 55, 60, 63, 71, 79, 87, 92, 95}"
]
},
"execution_count": 48,
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
@@ -1447,7 +1416,7 @@
"# Numbers up to 100 that are not the sum of 3 squares\n",
"nums = range(11)\n",
"sums = {A**2 + B**2 + C**2 for A in nums for B in nums for C in nums}\n",
"set(range(max(nums)**2)) - sums"
"set(range(101)) - sums"
]
}
],
@@ -1467,7 +1436,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.1"
"version": "3.6.0"
}
},
"nbformat": 4,
+27 -30
View File
@@ -13,7 +13,7 @@
"source": [
"This problem by Solomon Golomb was presented by Gary Antonik in his 14/4/14 New York Times [Numberplay column](http://wordplay.blogs.nytimes.com/2014/04/14/rectangle):\n",
"\n",
"><i>Say you’re given the following challenge: create a set of five rectangles that have sides of length 1, 2, 3, 4, 5, 6, 7, 8, 9 and 10 units. You can combine sides in a variety of ways: for example, you could create a set of rectangles with dimensions 1 x 3, 2 x 4, 5 x 7, 6 x 8 and 9 x 10.\n",
">Say you’re given the following challenge: create a set of five rectangles that have sides of length 1, 2, 3, 4, 5, 6, 7, 8, 9 and 10 units. You can combine sides in a variety of ways: for example, you could create a set of rectangles with dimensions 1 x 3, 2 x 4, 5 x 7, 6 x 8 and 9 x 10.\n",
">\n",
">1. How many different sets of five rectangles are possible?\n",
">\n",
@@ -21,7 +21,7 @@
">\n",
">3. What other values for the total areas of the five rectangles are possible?\n",
">\n",
">4. Which sets of rectangles may be assembled to form a square?</i>\n",
">4. Which sets of rectangles may be assembled to form a square?\n",
"\n",
"To me, these are interesting questions because, first, I have a (slight) personal connection to Solomon Golomb (my former colleague at USC) and to Nelson Blachman (the father of my colleague Nancy Blachman), who presented the problem to Antonik, and second, I find it interesting that the problems span the range from mathematical to computational. Let's answer them."
]
@@ -645,7 +645,7 @@
"\n",
"In Way 1, we could pre-sort the rectangles (say, biggest first). Then we try to put the biggest rectangle in all possible positions on the grid, and for each position that fits, try putting the second biggest rectangle in all remaining positions, and so on. As a rough estimate, assume there are on average about 10 ways to place a rectangle. Then this way will look at about 10<sup>5</sup> = 100,000 combinations.\n",
"\n",
"In Way 2, we consider the positions in some fixed order; say top-to-bottom, left-to right. Take the first empty position (say, the upper left corner). Try putting each of the rectangles there, and for each one that fits, try all possible rectangles in the next empty position, and so on. There are only 5! permutations of rectangles, and each rectangle can go either horizontally or vertically, so we would have to consider 5! &times; 2<sup>5</sup> = 3840 combinations. Since 3840 &lt; 100,000, I'll go with Way 2. Here is a more precise description:\n",
"In Way 2, we consider the positions in some fixed order; say top-to-bottom, left-to right. Take the first empty position (say, the upper left corner). Try putting each of the rectangles there, and for each one that fits, try all possible rectangles in the next empty position, and so on. There are only 5! permutations of rectangles, and each rectangle can go either horizontaly or vertically, so we would have to consider 5! &times; 2<sup>5</sup> = 3840 combinations. Since 3840 &lt; 100,000, I'll go with Way 2. Here is a more precise description:\n",
"\n",
"> Way 2: To `pack` a set of rectangles onto a grid, find the first empty cell on the grid. Try in turn all possible placements of any rectangle (in either orientation) at that position. For each one that fits, try to `pack` the remaining rectangles, and return the resulting grid if one of these packings succeeds. "
]
@@ -669,7 +669,7 @@
" return solution\n",
"\n",
"def rectangle_placements(rectangles, grid, pos):\n",
" \"Yield all (rectangles2, grid2) pairs that are the result of placing any rectangle at pos on grid.\"\n",
" \"Yield all (rect, grid) pairs that result from placing a rectangle at pos on grid.\"\n",
" for (w, h) in rectangles:\n",
" for rect in [(w, h), (h, w)]:\n",
" grid2 = place_rectangle_at(rect, grid, pos)\n",
@@ -728,7 +728,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"It would be nicer to have a graphical display of colored rectangles. I will define the function `show` which displays a grid as colored rectangles, by calling upon `html_table`, which formats any grid into HTML text."
"It would be nicer to have a graphical display of colored rectangles. I will define the function `show` which displays a grid as colored rectangles, by calling upon `html_table`, which formats any grid into HTML text. (*Note:* Github is conservative in the javascript and even CSS that it allows, so if you don't see colors in the grids below, look at this file on [nbviewer](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Golomb-Puzzle.ipynb); same file, but the rendering will definitely show the colors.)"
]
},
{
@@ -745,7 +745,8 @@
" display(html_table(grid, colored_cell))\n",
" \n",
"def html_table(grid, cell_function='<td>{}'.format):\n",
" \"Return an HTML <table>, where each cell's contents comes from calling cell_function(grid[y][x])\"\n",
" \"\"\"Return an HTML <table>, where each cell's contents comes from calling \n",
" cell_function(grid[y][x])\"\"\"\n",
" return HTML('<table>{}</table>'\n",
" .format(cat('\\n<tr>' + cat(map(cell_function, row)) \n",
" for row in grid)))\n",
@@ -754,7 +755,8 @@
" x, y = sorted(rect)\n",
" return '<td style=\"background-color:{}\">{}{}'.format(colors[x], x%10, y%10)\n",
"\n",
"colors = 'lightgrey yellow plum chartreuse cyan coral red olive slateblue lightgrey wheat'.split()\n",
"colors = ('lightgrey yellow plum chartreuse cyan coral red olive slateblue lightgrey wheat'\n",
" .split())\n",
"\n",
"cat = ''.join"
]
@@ -931,17 +933,15 @@
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"collapsed": true
},
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"\n",
"def pack(rectangles, grid, animation=False): \n",
" \"\"\"Find a way to pack all rectangles onto grid and return the packed grid,\n",
" or return None if not possible. \n",
" Pause `animation` seconds between displaying each rectangle placement if `animation` is not false.\"\"\"\n",
" or return None if not possible. Pause `animation` seconds between \n",
" displaying each rectangle placement if `animation` is not false.\"\"\"\n",
" if animation: \n",
" clear_output()\n",
" show(grid)\n",
@@ -1073,7 +1073,7 @@
{
"data": {
"text/plain": [
"'857 + 349 == 1206'"
"'325 + 764 == 1089'"
]
},
"execution_count": 29,
@@ -1094,8 +1094,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 29 s, sys: 42.2 ms, total: 29.1 s\n",
"Wall time: 29.1 s\n"
"CPU times: user 30 s, sys: 56.5 ms, total: 30 s\n",
"Wall time: 30.1 s\n"
]
},
{
@@ -1136,9 +1136,7 @@
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": true
},
"metadata": {},
"outputs": [],
"source": [
"def compile_formula(formula, verbose=False):\n",
@@ -1173,13 +1171,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"lambda Y,M,E,O,U: Y and M and ((100*Y+10*O+U) == (10*M+E)**2)\n"
"lambda M,U,E,O,Y: M and Y and ((100*Y+10*O+U) == (10*M+E)**2)\n"
]
},
{
"data": {
"text/plain": [
"(<function __main__.<lambda>>, 'YMEOU')"
"(<function __main__.<lambda>>, 'MUEOY')"
]
},
"execution_count": 32,
@@ -1200,13 +1198,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"lambda L,A,Y,P,M,E,R,U,B,N: P and B and N and ((100*N+10*U+M) + (100*B+10*E+R) == (1000*P+100*L+10*A+Y))\n"
"lambda U,A,R,E,L,Y,N,B,M,P: B and N and P and ((100*N+10*U+M) + (100*B+10*E+R) == (1000*P+100*L+10*A+Y))\n"
]
},
{
"data": {
"text/plain": [
"(<function __main__.<lambda>>, 'LAYPMERUBN')"
"(<function __main__.<lambda>>, 'UARELYNBMP')"
]
},
"execution_count": 33,
@@ -1221,15 +1219,14 @@
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"collapsed": true
},
"metadata": {},
"outputs": [],
"source": [
"def faster_solve_all(formula):\n",
" \"\"\"Given a formula like 'ODD + ODD == EVEN', fill in digits to solve it.\n",
" Input formula is a string; output is a digit-filled-in string or None.\n",
" Capital letters are variables. This version precompiles the formula; only one eval per formula.\"\"\"\n",
" Capital letters are variables. This version precompiles the formula; \n",
" only one eval per formula.\"\"\"\n",
" fn, letters = compile_formula(formula)\n",
" for digits in itertools.permutations((1,2,3,4,5,6,7,8,9,0), len(letters)):\n",
" try:\n",
@@ -1239,7 +1236,7 @@
" pass\n",
" \n",
"def replace_all(text, olds, news):\n",
" \"Replace each occurrence of each old in text with the corresponding new.\"\n",
" \"Replace each occurence of each old in text with the corresponding new.\"\n",
" # E.g. replace_all('A + B', ['A', 'B'], [1, 2]) == '1 + 2'\n",
" for (old, new) in zip(olds, news):\n",
" text = text.replace(str(old), str(new))\n",
@@ -1254,7 +1251,7 @@
{
"data": {
"text/plain": [
"'857 + 349 = 1206'"
"'325 + 764 = 1089'"
]
},
"execution_count": 35,
@@ -1275,8 +1272,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.75 s, sys: 3.29 ms, total: 1.76 s\n",
"Wall time: 1.76 s\n"
"CPU times: user 1.77 s, sys: 4.05 ms, total: 1.77 s\n",
"Wall time: 1.77 s\n"
]
},
{
+455
View File
@@ -0,0 +1,455 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"text-align: right\">Peter Norvig<br>13 March 2018</div> \n",
"\n",
"# Maze Generation\n",
"\n",
"Let's make some mazes! I'm thinking of mazes like this one, which is a rectangular grid of squares, with walls on some of the sides of squares, and openings on other sides:\n",
"\n",
"![Wikipedia](https://upload.wikimedia.org/wikipedia/commons/thumb/8/88/Maze_simple.svg/475px-Maze_simple.svg.png)\n",
"\n",
"The main constraint is that there should be a path from entrance to exit, and it should be ***fun*** to solve the maze with pencil, paper, and brain power&mdash;not too easy, but also not impossible. \n",
"\n",
"As I think about how to model a maze on the computer, it seems like a **graph** is the right model: the nodes of\n",
"the graph are the squares of the grid, and the edges of the graph are the openings between adjacent squares. So what properties of a graph make a good maze?\n",
"- There must be a path from entrance to exit.\n",
"- There must not be too such many paths; maybe it is best if there is only one. \n",
"- Probably the graph should be *singly connected*&mdash;there shouldn't be islands of squares that are unreachable from the start. And maybe we want exactly one path between any two squares.\n",
"- The path should have many twists; it would be too easy if it was mostly straight.\n",
"\n",
"I know that a **tree** has all these properties except the last one. So my goal has become: *Superimpose a tree over the grid, covering every square, and make sure the paths are twisty.* Here's how I'll do it:\n",
"\n",
"- Start with a grid with no edges (every square is surrounded by walls on all sides). \n",
"- Add edges (that is, knock down walls) for the entrance at upper left and exit at lower right.\n",
"- Place the root of the tree in some square.\n",
"- Then repeat until the tree covers the whole grid:\n",
" * Select some node already in the tree.\n",
" * Randomly select a neighbor that hasn't been added to the tree yet.\n",
" * Add an edge (knock down the wall) from the node to the neighbor.\n",
" \n",
"In the example below, the root, `A`, has been placed in the upper-left corner, and two branches,\n",
"`A-B-C-D` and `A-b-c-d`, have been randomly chosen (well, not actually random; they are starting to create the same maze as in the diagram above):\n",
"\n",
" o o--o--o--o--o--o--o--o--o--o\n",
" | A b c| | | | | | | |\n",
" o o--o o--o--o--o--o--o--o--o\n",
" | B| | d| | | | | | | |\n",
" o o--o--o--o--o--o--o--o--o--o\n",
" | C D| | | | | | | | |\n",
" o--o--o--o--o--o--o--o--o--o--o\n",
" | | | | | | | | | | |\n",
" o--o--o--o--o--o--o--o--o--o--o\n",
" | | | | | | | | | | |\n",
" o--o--o--o--o--o--o--o--o--o o\n",
" \n",
"Next I select node `d` and extend it to `e` (at which point there are no available neighbors, so `e` will not be selected in the future), and then I select `D` and extend from there all the way to `N`, at each step selecting the node I just added:\n",
"\n",
" o o--o--o--o--o--o--o--o--o--o\n",
" | A b c| | | | | | | |\n",
" o o--o o--o--o--o--o--o--o--o\n",
" | B| e d| | N| | | | | |\n",
" o o--o--o--o o--o--o--o--o--o\n",
" | C D| | | M| | | | | |\n",
" o--o o--o--o o--o--o--o--o--o\n",
" | F E| | K L| | | | | |\n",
" o o--o--o o--o--o--o--o--o--o\n",
" | G H I J| | | | | | |\n",
" o--o--o--o--o--o--o--o--o--o o\n",
" \n",
"Continue like this until every square in the grid has been added to the tree. \n",
"\n",
"\n",
"## Implementing Random Trees\n",
"\n",
"I'll make the following implementation choices:\n",
"\n",
"- A tree will be represented as a list of edges.\n",
"- An `Edge` is a tuple of two nodes. I'll keep them sorted so that `Edge(A, B)` is the same as `Edge(B, A)`.\n",
"- A node in a tree can be anything: a number, a letter, a square, ...\n",
"- The algorithm for `random_tree(nodes, neighbors, pop)` is as follows:\n",
" * We will keep track of three collections:\n",
" - `tree`: a list of edges that constitutes the tree.\n",
" - `nodes`: the set of nodes that have not yet been added to the tree, but will be.\n",
" - `frontier`: a queue of nodes in the tree that are eligible to have an edge added.\n",
" * On each iteration:\n",
" - Use `pop` to pick a `node` from the frontier, and find the neighbors that are not already in the tree.\n",
" - If there are any neighbors, randomly pick one (`nbr`), add `Edge(node, nbr)` to `tree`, remove the\n",
" neighbor from `nodes`, and keep both the node and the neighbor on the frontier. If there are no neighbors,\n",
" drop the node from the frontier.\n",
" * When no `nodes` remain, return `tree`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import random\n",
"from collections import deque, namedtuple\n",
"\n",
"def Edge(node1, node2): return tuple(sorted([node1, node2]))\n",
"\n",
"def random_tree(nodes: set, neighbors: callable, pop: callable) -> [Edge]:\n",
" \"Repeat: pop a node and add Edge(node, nbr) until all nodes have been added to tree.\"\n",
" tree = []\n",
" root = nodes.pop()\n",
" frontier = deque([root])\n",
" while nodes:\n",
" node = pop(frontier)\n",
" nbrs = neighbors(node) & nodes\n",
" if nbrs:\n",
" nbr = random.choice(list(nbrs))\n",
" tree.append(Edge(node, nbr))\n",
" nodes.remove(nbr)\n",
" frontier.extend([node, nbr])\n",
" return tree"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Implementing Random Mazes\n",
"\n",
"Now let's use `random_tree` to implement `random_maze`. Some more choices:\n",
"\n",
"* A `Maze` is a named tuple with three fields: the `width` and `height` of the grid, and a list of `edges` between squares. \n",
"* A square is denoted by an `(x, y)` tuple of integer coordinates.\n",
"* The function `neighbors4` gives the four surrounding squares."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"Maze = namedtuple('Maze', 'width, height, edges')\n",
"\n",
"def neighbors4(square):\n",
" \"The 4 neighbors of an (x, y) square.\"\n",
" (x, y) = square\n",
" return {(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)}\n",
"\n",
"def squares(width, height): \n",
" \"All squares in a grid of these dimensions.\"\n",
" return {(x, y) for x in range(width) for y in range(height)}\n",
"\n",
"def random_maze(width, height, pop=deque.pop):\n",
" \"Use random_tree to generate a random maze.\"\n",
" nodes = squares(width, height)\n",
" tree = random_tree(nodes, neighbors4, pop)\n",
" return Maze(width, height, tree)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Maze(width=10, height=5, edges=[((6, 3), (7, 3)), ((6, 3), (6, 4)), ((6, 4), (7, 4)), ((7, 4), (8, 4)), ((8, 3), (8, 4)), ((8, 2), (8, 3)), ((7, 2), (8, 2)), ((7, 1), (7, 2)), ((7, 0), (7, 1)), ((7, 0), (8, 0)), ((8, 0), (8, 1)), ((8, 1), (9, 1)), ((9, 0), (9, 1)), ((9, 1), (9, 2)), ((9, 2), (9, 3)), ((9, 3), (9, 4)), ((6, 0), (7, 0)), ((5, 0), (6, 0)), ((5, 0), (5, 1)), ((5, 1), (6, 1)), ((6, 1), (6, 2)), ((5, 2), (6, 2)), ((4, 2), (5, 2)), ((3, 2), (4, 2)), ((3, 2), (3, 3)), ((2, 3), (3, 3)), ((2, 2), (2, 3)), ((2, 1), (2, 2)), ((2, 0), (2, 1)), ((1, 0), (2, 0)), ((0, 0), (1, 0)), ((0, 0), (0, 1)), ((0, 1), (1, 1)), ((1, 1), (1, 2)), ((0, 2), (1, 2)), ((0, 2), (0, 3)), ((0, 3), (1, 3)), ((1, 3), (1, 4)), ((0, 4), (1, 4)), ((1, 4), (2, 4)), ((2, 4), (3, 4)), ((3, 4), (4, 4)), ((4, 3), (4, 4)), ((4, 3), (5, 3)), ((5, 3), (5, 4)), ((2, 0), (3, 0)), ((3, 0), (4, 0)), ((4, 0), (4, 1)), ((3, 1), (4, 1))])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"random_maze(10,5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That's not very pretty to look at. I'm going to need a way to visualize a maze.\n",
"\n",
"# Printing a maze"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"o o--o--o--o--o--o--o--o--o--o\n",
"| | | |\n",
"o--o--o--o o--o o o o--o o\n",
"| | | | |\n",
"o o o--o--o o--o--o--o--o o\n",
"| | | | | | |\n",
"o o--o o o--o o o--o o--o\n",
"| | | | | | |\n",
"o o o--o--o--o--o o o--o o\n",
"| | |\n",
"o--o--o--o--o--o--o--o--o--o o\n"
]
}
],
"source": [
"def print_maze(maze, dot='o', lin='--', bar='|', sp=' '):\n",
" \"Print maze in ASCII.\"\n",
" exit = Edge((maze.width-1, maze.height-1), (maze.width-1, maze.height))\n",
" edges = set(maze.edges) | {exit}\n",
" print(dot + sp + lin.join(dot * maze.width)) # Top line, including entrance\n",
" def vert_wall(x, y): return (' ' if Edge((x, y), (x+1, y)) in edges else bar)\n",
" def horz_wall(x, y): return (sp if Edge((x, y), (x, y+1)) in edges else lin)\n",
" for y in range(maze.height):\n",
" print(bar + cat(sp + vert_wall(x, y) for x in range(maze.width)))\n",
" print(dot + cat(horz_wall(x, y) + dot for x in range(maze.width)))\n",
" \n",
"cat = ''.join\n",
" \n",
"print_maze(random_maze(10, 5))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Much better!* But can I do better still?\n",
"\n",
"# Plotting a maze\n",
"\n",
"I'll use `matplotlib` to plot lines where the edges aren't:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"\n",
"def plot_maze(maze):\n",
" \"Plot a maze by drawing lines between adjacent squares, except for pairs in maze.edges\"\n",
" plt.figure(figsize=(8, 4))\n",
" plt.axis('off')\n",
" plt.gca().invert_yaxis()\n",
" w, h = maze.width, maze.height\n",
" exits = {Edge((0, 0), (0, -1)), Edge((w-1, h-1), (w-1, h))}\n",
" edges = set(maze.edges) | exits\n",
" for sq in squares(w, h):\n",
" for nbr in neighbors4(sq):\n",
" if Edge(sq, nbr) not in edges:\n",
" plot_wall(sq, nbr)\n",
" plt.show()\n",
"\n",
"def plot_wall(s1, s2):\n",
" \"Plot a thick black line between squares s1 and s2.\"\n",
" (x1, y1), (x2, y2) = s1, s2\n",
" if x1 == x2: # horizontal wall\n",
" y = max(y1, y2)\n",
" X, Y = [x1, x1+1], [y, y]\n",
" else: # vertical wall\n",
" x = max(x1, x2)\n",
" X, Y = [x, x], [y1, y1+1]\n",
" plt.plot(X, Y, 'k-', linewidth=4.0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's compare the two visualization functions:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAd0AAAD8CAYAAAAyun5JAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAABNxJREFUeJzt2sFuIjEUAMF4xf//svcaJSsIAdr2UnXj9uSZUct6jDnn\nBwDwen9WDwAA70J0ASAiugAQEV0AiIguAEREFwAiogsAEdEFgIjoAkBEdAEgIroAEBFdAIiILgBE\nRBcAIqILABHRBYCI6AJA5LJ6gGvGGPPz7znnWDXLv3ydD4Cz1F1x0wWAiOgCQER0ASCy9U53d7vt\nmHlfu///AXax+r84broAEBFdAIiILgBERBcAIqILABHRBYCI6AJARHQBICK6ABARXQCIiC4AREQX\nACKiCwAR0QWAiOgCQER0ASAiugAQEV0AiIguAEREFwAiogsAEdEFgIjoAkBEdAEgIroAEBFdAIiI\nLgBERBcAIqILABHRBYCI6AJARHQBICK6ABARXQCIiC4ARC6rBzjZGGOunuFec86xeoavTjvHHc9w\nd6c9Y57Dt/Kdmy4AREQXACKiCwARO90n2nF/ccIubcdz++yEMzzN7s+c3/Gt3OamCwAR0QWAiOgC\nQER0ASAiugAQEV0AiIguAEREFwAiogsAEdEFgIjoAkBEdAEgIroAEBFdAIiILgBERBcAIqILABHR\nBYCI6AJARHQBICK6ABARXQCIiC4AREQXACKiCwAR0QWAiOgCQER0ASAiugAQEV0AiIguAEREFwAi\nogsAEdEFgMhl9QC0xhhz9Qy3zDnH6hl4Le/h75xwblznpgsAEdEFgIjoAkDETvfN7Lin4v14D5/D\nOZ7HTRcAIqILABHRBYCI6AJARHQBICK6ABARXQCIiC4AREQXACKiCwAR0QWAiOgCQER0ASAiugAQ\nEV0AiIguAEREFwAiogsAEdEFgIjoAkBEdAEgIroAEBFdAIiILgBERBcAIqILABHRBYCI6AJARHQB\nICK6ABARXQCIiC4AREQXACKiCwAR0QWAyGX1ADDGmKtnuMdp8/IznuvjnOFtbroAEBFdAIiILgBE\n7HQfMOccq2f4HzjHx9mlPZ/38nE7nuHqb8VNFwAiogsAEdEFgIjoAkBEdAEgIroAEBFdAIiILgBE\nRBcAIqILABHRBYCI6AJARHQBICK6ABARXQCIiC4AREQXACKiCwAR0QWAiOgCQER0ASAiugAQEV0A\niIguAEREFwAiogsAEdEFgIjoAkBEdAEgIroAEBFdAIiILgBERBcAIqILAJHL6gHuMcaYq2e4Zs45\nVs9wy+5neIITnjOP863wCm66ABARXQCIiC4ARI7a6dql3c+ZPe6E3d7uz3n3+T4+zpiR87npAkBE\ndAEgIroAEBFdAIiILgBERBcAIqILABHRBYCI6AJARHQBICK6ABARXQCIiC4AREQXACKiCwAR0QWA\niOgCQER0ASAiugAQEV0AiIguAEREFwAiogsAEdEFgIjoAkBEdAEgIroAEBFdAIiILgBERBcAIqIL\nABHRBYCI6AJARHQBICK6ABC5rB7gHmOMuXoG8B7Cz8w5x+oZduOmCwAR0QWAiOgCQGTMaT0FAAU3\nXQCIiC4AREQXACKiCwAR0QWAiOgCQER0ASAiugAQEV0AiIguAEREFwAiogsAEdEFgIjoAkBEdAEg\nIroAEBFdAIiILgBERBcAIqILABHRBYCI6AJARHQBICK6ABARXQCIiC4AREQXACKiCwAR0QWAiOgC\nQER0ASDyFyUScgM3r2rXAAAAAElFTkSuQmCC\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x10dd4dd68>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"o o--o--o--o--o--o--o--o--o--o\n",
"| | |\n",
"o o--o o--o o o--o--o--o o\n",
"| | | | | | | |\n",
"o o o--o o o o--o o--o o\n",
"| | | | | | | |\n",
"o o o o--o--o o o--o o--o\n",
"| | | | | | |\n",
"o--o o--o--o o o o--o--o o\n",
"| | |\n",
"o--o--o--o--o--o--o--o--o--o o\n"
]
}
],
"source": [
"M = random_maze(10, 5)\n",
"plot_maze(M) \n",
"print_maze(M)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# `pop` strategies\n",
"\n",
"Now I want to compare how the maze varies based on theree different choices for the `pop` parameter. \n",
"\n",
"(1) The default is `deque.pop`\n",
"which means that the tree is created **depth-first**; we always select the `node` at the end of the `frontier`, so the tree follows a single branch along a randomly-twisted path until the path doubles back on itself and there are no more neighbors; at that point we select the most recent square for which there are neighbors:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x1176e5e80>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_maze(random_maze(40, 20, deque.pop))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The maze with `deque.pop` looks pretty good. Reminds me of those [cyber brain](https://www.vectorstock.com/royalty-free-vector/cyber-brain-vector-3071965) images.\n",
"\n",
"(2) An alternative is `queue.popleft`, which creates the maze roughly **breadth-first**&mdash;we start at some root square , add an edge to it, and from then on we always select first a parent edge before we select a child edge. The net result is a design that appears to radiate out in concentric layers from the root (which is chosen by `random_tree` and is not necessarily the top-left square; below it looks like the root is in the upper-left quadrant). "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x1176fcf60>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_maze(random_maze(40, 20, deque.popleft))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `deque.popleft` maze is interesting as a design, but to me it doesn't work well as a maze.\n",
"\n",
"(3) We can select a cell at random by shuffling the frontier before popping an element off of it:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "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\nxnjoO1WPSQgAAAAASUVORK5CYII=\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x117710550>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def poprandom(seq):\n",
" \"Shuffle a mutable sequence (deque or list) and then pop an element.\"\n",
" random.shuffle(seq)\n",
" return seq.pop()\n",
"\n",
"plot_maze(random_maze(40, 20, poprandom))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is an interesting compromise: it has some structure, but still works nicely as a maze, in my opinion.\n",
"\n",
"What other variations can you come up with to generate interesting mazes?"
]
}
],
"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.6.0"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+422
View File
@@ -0,0 +1,422 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Scheduling a Doubles Pickleball Tournament\n",
"\n",
"My friend Steve asked for help in creating a schedule for a round-robin doubles pickleball tournament with 8 or 9 players on 2 courts. ([Pickleball](https://en.wikipedia.org/wiki/Pickleball) is a paddle/ball/net game played on a court that is smaller than tennis but larger than ping-pong.) \n",
"\n",
"To generalize: given *P* players and *C* available courts, we would like to create a **schedule**: a table where each row is a time period (a round of play), each column is a court, and each cell contains a game, which consists of two players partnered together and pitted against two other players. The preferences for the schedule are:\n",
"\n",
"- Each player should partner with each other player exactly once (or as close to that as possible).\n",
"- Fewer rounds are better (in other words, try to fill all the courts each round).\n",
"- Each player should play against each other player twice, or as close to that as possible.\n",
"- A player should not be scheduled to play two games at the same time.\n",
"\n",
"For example, here's a perfect schedule for *P*=8 players on *C*=2 courts:\n",
"\n",
" [([[1, 6], [2, 4]], [[3, 5], [7, 0]]),\n",
" ([[1, 5], [3, 6]], [[2, 0], [4, 7]]),\n",
" ([[2, 3], [6, 0]], [[4, 5], [1, 7]]),\n",
" ([[4, 6], [3, 7]], [[1, 2], [5, 0]]),\n",
" ([[1, 0], [6, 7]], [[3, 4], [2, 5]]),\n",
" ([[2, 6], [5, 7]], [[1, 4], [3, 0]]),\n",
" ([[2, 7], [1, 3]], [[4, 0], [5, 6]])]\n",
" \n",
"This means that in the first round, players 1 and 6 partner against 2 and 4 on one court, while 3 and 5 partner against 7 and 0 on the other. There are 7 rounds.\n",
"\n",
"My strategy for finding a good schedule is to use **hillclimbing**: start with an initial schedule, then repeatedly alter the schedule by swapping partners in one game with partners in another. If the altered schedule is better, keep it; if not, discard it. Repeat. \n",
"\n",
"## Coding it up\n",
"\n",
"The strategy in more detail:\n",
"\n",
"- First form all pairs of players, using `all_pairs(P)`.\n",
"- Put pairs together to form a list of games using `initial_games`.\n",
"- Use `Schedule` to create a schedule; it calls `one_round` to create each round and `scorer` to evaluate the schedule.\n",
"- Use `hillclimb` to improve the initial schedule: call `alter` to randomly alter a schedule, `Schedule` to re-allocate the games to rounds and courts, and `scorer` to check if the altered schedule's score is better.\n",
"\n",
"\n",
"\n",
"(Note: with *P* players there are *P &times; (P - 1) / 2* pairs of partners; this is an even number when either *P* or *P - 1* is divisible by 4, so everything works out when, say, *P*=4 or *P*=9, but for, say, *P*=10 there are 45 pairs, and so `initial_games` chooses to create 22 games, meaning that one pair of players never play together, and thus play one fewer game than everyone else.)"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"from itertools import combinations\n",
"from collections import Counter\n",
"\n",
"#### Types\n",
"\n",
"Player = int # A player is an int: `1`\n",
"Pair = list # A pair is a list of two players who are partners: `[1, 2]`\n",
"Game = list # A game is a list of two pairs: `[[1, 2], [3, 4]]`\n",
"Round = tuple # A round is a tuple of games: `([[1, 2], [3, 4]], [[5, 6], [7, 8]])`\n",
"\n",
"class Schedule(list):\n",
" \"\"\"A Schedule is a list of rounds (augmented with a score and court count).\"\"\"\n",
" def __init__(self, games, courts=2):\n",
" games = list(games)\n",
" while games: # Allocate games to courts, one round at a time\n",
" self.append(one_round(games, courts))\n",
" self.score = scorer(self)\n",
" self.courts = courts\n",
" \n",
"#### Functions\n",
" \n",
"def hillclimb(P, C=2, N=100000):\n",
" \"Schedule games for P players on C courts by randomly altering schedule N times.\"\n",
" sched = Schedule(initial_games(all_pairs(P)), C)\n",
" for _ in range(N):\n",
" sched = max(alter(sched), sched, key=lambda s: s.score)\n",
" return sched\n",
"\n",
"def all_pairs(P): return list(combinations(range(P), 2))\n",
"\n",
"def initial_games(pairs):\n",
" \"\"\"An initial list of games: [[[1, 2], [3, 4]], ...].\n",
" We try to have every pair play every other pair once, and\n",
" have each game have 4 different players, but that isn't always true.\"\"\"\n",
" random.shuffle(pairs)\n",
" games = []\n",
" while len(pairs) >= 2:\n",
" A = pairs.pop()\n",
" B = first(pair for pair in pairs if disjoint(pair, A)) or pairs[0]\n",
" games.append([A, B])\n",
" pairs.remove(B)\n",
" return games\n",
"\n",
"def disjoint(A, B): \n",
" \"Do A and B have disjoint players in them?\"\n",
" return not (players(A) & players(B))\n",
"\n",
"def one_round(games, courts):\n",
" \"\"\"Place up to `courts` games into `round`, all with disjoint players.\"\"\"\n",
" round = []\n",
" while True:\n",
" G = first(g for g in games if disjoint(round, g))\n",
" if not G or not games or len(round) == courts:\n",
" return Round(round)\n",
" round.append(G)\n",
" games.remove(G)\n",
"\n",
"def players(x): \n",
" \"All distinct players in a pair, game, or sequence of games.\"\n",
" return {x} if isinstance(x, Player) else set().union(*map(players, x))\n",
"\n",
"def first(items): return next(items, None)\n",
"\n",
"def pairing(p1, p2): return tuple(sorted([p1, p2]))\n",
" \n",
"def scorer(sched):\n",
" \"Score has penalties for a non-perfect schedule.\"\n",
" penalty = 50 * len(sched) # More rounds are worse (avoid empty courts)\n",
" penalty += 1000 * sum(len(players(game)) != 4 # A game should have 4 players!\n",
" for round in sched for game in round)\n",
" penalty += 1 * sum(abs(c - 2) ** 3 + 8 * (c == 0) # Try to play everyone twice\n",
" for c in opponents(sched).values())\n",
" return -penalty\n",
" \n",
"def opponents(sched):\n",
" \"A Counter of {(player, opponent): times_played}.\"\n",
" return Counter(pairing(p1, p2) \n",
" for round in sched for A, B in round for p1 in A for p2 in B)\n",
" \n",
"def alter(sched):\n",
" \"Modify a schedule by swapping two pairs.\"\n",
" games = [Game(game) for round in sched for game in round] \n",
" G = len(games)\n",
" i, j = random.sample(range(G), 2) # index into games\n",
" a, b = random.choice((0, 1)), random.choice((0, 1)) # index into each game\n",
" games[i][a], games[j][b] = games[j][b], games[i][a]\n",
" return Schedule(games, sched.courts)\n",
"\n",
"def report(sched):\n",
" \"Print information about this schedule.\"\n",
" for i, round in enumerate(sched, 1):\n",
" print('Round {}: {}'.format(i, '; '.join('{} vs {}'.format(*g) for g in round)))\n",
" games = sum(sched, ())\n",
" P = len(players(sched))\n",
" print('\\n{} games in {} rounds for {} players'.format(len(games), len(sched), P))\n",
" opp = opponents(sched)\n",
" fmt = ('{:2X}|' + P * ' {}' + ' {}').format\n",
" print('Number of times each player plays against each opponent:\\n')\n",
" print(' |', *map('{:X}'.format, range(P)), ' Total')\n",
" print('--+' + '--' * P + ' -----')\n",
" for row in range(P):\n",
" counts = [opp[pairing(row, col)] for col in range(P)]\n",
" print(fmt(row, *[c or '-' for c in counts], sum(counts) // 2))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 8 Player Tournament\n",
"\n",
"I achieved (in a previous run) a perfect schedule for 8 players: the 14 games fit into 7 rounds, each player partners with each other once, and plays each individual opponent twice:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Round 1: [1, 6] vs [2, 4]; [3, 5] vs [7, 0]\n",
"Round 2: [1, 5] vs [3, 6]; [2, 0] vs [4, 7]\n",
"Round 3: [2, 3] vs [6, 0]; [4, 5] vs [1, 7]\n",
"Round 4: [4, 6] vs [3, 7]; [1, 2] vs [5, 0]\n",
"Round 5: [1, 0] vs [6, 7]; [3, 4] vs [2, 5]\n",
"Round 6: [2, 6] vs [5, 7]; [1, 4] vs [3, 0]\n",
"Round 7: [2, 7] vs [1, 3]; [4, 0] vs [5, 6]\n",
"\n",
"14 games in 7 rounds for 8 players\n",
"Number of times each player plays against each opponent:\n",
"\n",
" | 0 1 2 3 4 5 6 7 Total\n",
"--+---------------- -----\n",
" 0| - 2 2 2 2 2 2 2 7\n",
" 1| 2 - 2 2 2 2 2 2 7\n",
" 2| 2 2 - 2 2 2 2 2 7\n",
" 3| 2 2 2 - 2 2 2 2 7\n",
" 4| 2 2 2 2 - 2 2 2 7\n",
" 5| 2 2 2 2 2 - 2 2 7\n",
" 6| 2 2 2 2 2 2 - 2 7\n",
" 7| 2 2 2 2 2 2 2 - 7\n"
]
}
],
"source": [
"report([\n",
" ([[1, 6], [2, 4]], [[3, 5], [7, 0]]),\n",
" ([[1, 5], [3, 6]], [[2, 0], [4, 7]]),\n",
" ([[2, 3], [6, 0]], [[4, 5], [1, 7]]),\n",
" ([[4, 6], [3, 7]], [[1, 2], [5, 0]]),\n",
" ([[1, 0], [6, 7]], [[3, 4], [2, 5]]),\n",
" ([[2, 6], [5, 7]], [[1, 4], [3, 0]]),\n",
" ([[2, 7], [1, 3]], [[4, 0], [5, 6]]) ])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 9 Player Tournament\n",
"\n",
"For 9 players, I can fit the 18 games into 9 rounds, but some players play each other 1 or 3 times:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Round 1: [1, 7] vs [4, 0]; [3, 5] vs [2, 6]\n",
"Round 2: [2, 7] vs [1, 3]; [4, 8] vs [6, 0]\n",
"Round 3: [5, 0] vs [1, 6]; [7, 8] vs [3, 4]\n",
"Round 4: [7, 0] vs [5, 8]; [1, 2] vs [4, 6]\n",
"Round 5: [3, 8] vs [1, 5]; [2, 0] vs [6, 7]\n",
"Round 6: [1, 4] vs [2, 5]; [3, 6] vs [8, 0]\n",
"Round 7: [5, 6] vs [4, 7]; [1, 8] vs [2, 3]\n",
"Round 8: [1, 0] vs [3, 7]; [2, 8] vs [4, 5]\n",
"Round 9: [3, 0] vs [2, 4]; [6, 8] vs [5, 7]\n",
"\n",
"18 games in 9 rounds for 9 players\n",
"Number of times each player plays against each opponent:\n",
"\n",
" | 0 1 2 3 4 5 6 7 8 Total\n",
"--+------------------ -----\n",
" 0| - 2 1 2 2 1 3 3 2 8\n",
" 1| 2 - 3 3 2 2 1 2 1 8\n",
" 2| 1 3 - 3 3 2 2 1 1 8\n",
" 3| 2 3 3 - 1 1 1 2 3 8\n",
" 4| 2 2 3 1 - 2 2 2 2 8\n",
" 5| 1 2 2 1 2 - 3 2 3 8\n",
" 6| 3 1 2 1 2 3 - 2 2 8\n",
" 7| 3 2 1 2 2 2 2 - 2 8\n",
" 8| 2 1 1 3 2 3 2 2 - 8\n"
]
}
],
"source": [
"report([\n",
" ([[1, 7], [4, 0]], [[3, 5], [2, 6]]),\n",
" ([[2, 7], [1, 3]], [[4, 8], [6, 0]]),\n",
" ([[5, 0], [1, 6]], [[7, 8], [3, 4]]),\n",
" ([[7, 0], [5, 8]], [[1, 2], [4, 6]]),\n",
" ([[3, 8], [1, 5]], [[2, 0], [6, 7]]),\n",
" ([[1, 4], [2, 5]], [[3, 6], [8, 0]]),\n",
" ([[5, 6], [4, 7]], [[1, 8], [2, 3]]),\n",
" ([[1, 0], [3, 7]], [[2, 8], [4, 5]]),\n",
" ([[3, 0], [2, 4]], [[6, 8], [5, 7]]) ])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 10 Player Tournament\n",
"\n",
"With *P*=10 there is an odd number of pairings (45), so two players necessarily play one game less than the other players. Let's see what kind of schedule we can come up with:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Round 1: (6, 7) vs (0, 5); (3, 4) vs (2, 8)\n",
"Round 2: (1, 8) vs (0, 3); (7, 9) vs (4, 5)\n",
"Round 3: (3, 6) vs (1, 7); (0, 9) vs (2, 5)\n",
"Round 4: (2, 9) vs (6, 8); (1, 3) vs (4, 7)\n",
"Round 5: (0, 8) vs (5, 7); (4, 6) vs (2, 3)\n",
"Round 6: (2, 4) vs (3, 5); (1, 6) vs (8, 9)\n",
"Round 7: (6, 9) vs (3, 7); (1, 2) vs (5, 8)\n",
"Round 8: (1, 4) vs (5, 9); (0, 7) vs (3, 8)\n",
"Round 9: (1, 5) vs (2, 7); (3, 9) vs (0, 6)\n",
"Round 10: (7, 8) vs (4, 9); (0, 1) vs (2, 6)\n",
"Round 11: (4, 8) vs (5, 6); (0, 2) vs (1, 9)\n",
"\n",
"22 games in 11 rounds for 10 players\n",
"Number of times each player plays against each opponent:\n",
"\n",
" | 0 1 2 3 4 5 6 7 8 9 Total\n",
"--+-------------------- -----\n",
" 0| - 2 2 2 - 2 2 2 2 2 8\n",
" 1| 2 - 3 2 1 2 2 2 2 2 9\n",
" 2| 2 3 - 2 2 3 2 - 2 2 9\n",
" 3| 2 2 2 - 3 - 3 3 2 1 9\n",
" 4| - 1 2 3 - 3 1 2 2 2 8\n",
" 5| 2 2 3 - 3 - 1 3 2 2 9\n",
" 6| 2 2 2 3 1 1 - 2 2 3 9\n",
" 7| 2 2 - 3 2 3 2 - 2 2 9\n",
" 8| 2 2 2 2 2 2 2 2 - 2 9\n",
" 9| 2 2 2 1 2 2 3 2 2 - 9\n",
"CPU times: user 2min 39s, sys: 661 ms, total: 2min 40s\n",
"Wall time: 2min 43s\n"
]
}
],
"source": [
"%time report(hillclimb(P=10))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this schedule several players never play each other; it may be possible to improve on that (in another run that has better luck with random numbers).\n",
"\n",
"# 16 Player Tournament\n",
"\n",
"Let's jump to 16 players on 4 courts (this will take a while):"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Round 1: (0, 12) vs (9, 13); (5, 10) vs (11, 15); (6, 8) vs (1, 3); (2, 7) vs (4, 14)\n",
"Round 2: (5, 12) vs (0, 10); (6, 11) vs (3, 9); (8, 15) vs (2, 14)\n",
"Round 3: (12, 15) vs (4, 6); (10, 13) vs (1, 9); (2, 5) vs (8, 11)\n",
"Round 4: (11, 14) vs (0, 9); (3, 13) vs (7, 10); (2, 15) vs (4, 12)\n",
"Round 5: (10, 11) vs (0, 15); (12, 14) vs (5, 13); (1, 8) vs (6, 9); (3, 7) vs (2, 4)\n",
"Round 6: (3, 11) vs (8, 13); (7, 9) vs (5, 15); (1, 6) vs (4, 10); (2, 12) vs (0, 14)\n",
"Round 7: (3, 10) vs (7, 12); (1, 14) vs (5, 11); (6, 13) vs (4, 8)\n",
"Round 8: (4, 5) vs (0, 8); (6, 10) vs (2, 11); (1, 13) vs (9, 15)\n",
"Round 9: (3, 5) vs (2, 9); (10, 15) vs (1, 7); (0, 11) vs (6, 12); (8, 14) vs (4, 13)\n",
"Round 10: (1, 10) vs (3, 8); (6, 7) vs (5, 9); (11, 12) vs (4, 15)\n",
"Round 11: (4, 7) vs (1, 11); (9, 14) vs (10, 12); (0, 6) vs (2, 13)\n",
"Round 12: (10, 14) vs (5, 8); (9, 12) vs (2, 3); (4, 11) vs (7, 13)\n",
"Round 13: (7, 8) vs (0, 13); (3, 12) vs (1, 5); (14, 15) vs (4, 9)\n",
"Round 14: (0, 5) vs (1, 4); (13, 14) vs (3, 15); (9, 10) vs (2, 8)\n",
"Round 15: (0, 3) vs (1, 15); (2, 6) vs (5, 7)\n",
"Round 16: (7, 11) vs (8, 12); (3, 4) vs (5, 14); (6, 15) vs (0, 2)\n",
"Round 17: (3, 14) vs (9, 11); (8, 10) vs (0, 4); (5, 6) vs (7, 15); (1, 2) vs (12, 13)\n",
"Round 18: (0, 1) vs (7, 14); (13, 15) vs (3, 6)\n",
"Round 19: (11, 13) vs (2, 10); (0, 7) vs (8, 9); (6, 14) vs (1, 12)\n",
"\n",
"60 games in 19 rounds for 16 players\n",
"Number of times each player plays against each opponent:\n",
"\n",
" | 0 1 2 3 4 5 6 7 8 9 A B C D E F Total\n",
"--+-------------------------------- -----\n",
" 0| - 2 2 - 2 2 2 2 3 2 2 2 3 2 2 2 15\n",
" 1| 2 - - 3 2 2 3 2 2 2 3 1 2 2 2 2 15\n",
" 2| 2 - - 2 2 2 3 2 2 2 2 2 3 2 2 2 15\n",
" 3| - 3 2 - 1 2 2 2 2 3 2 2 2 3 2 2 15\n",
" 4| 2 2 2 1 - 2 2 3 3 - 1 2 2 2 3 3 15\n",
" 5| 2 2 2 2 2 - 2 3 2 2 2 2 2 - 3 2 15\n",
" 6| 2 3 3 2 2 2 - 2 2 2 1 2 2 2 - 3 15\n",
" 7| 2 2 2 2 3 3 2 - 2 2 2 2 1 2 1 2 15\n",
" 8| 3 2 2 2 3 2 2 2 - 2 3 2 - 3 2 - 15\n",
" 9| 2 2 2 3 - 2 2 2 2 - 2 2 2 2 3 2 15\n",
" A| 2 3 2 2 1 2 1 2 3 2 - 3 2 2 1 2 15\n",
" B| 2 1 2 2 2 2 2 2 2 2 3 - 2 2 2 2 15\n",
" C| 3 2 3 2 2 2 2 1 - 2 2 2 - 2 3 2 15\n",
" D| 2 2 2 3 2 - 2 2 3 2 2 2 2 - 2 2 15\n",
" E| 2 2 2 2 3 3 - 1 2 3 1 2 3 2 - 2 15\n",
" F| 2 2 2 2 3 2 3 2 - 2 2 2 2 2 2 - 15\n",
"CPU times: user 15min 6s, sys: 2.67 s, total: 15min 9s\n",
"Wall time: 15min 16s\n"
]
}
],
"source": [
"%time report(hillclimb(P=16, C=4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We get a pretty good schedule, although it takes 19 rounds rather than the 15 it would take if every court was filled, and again there are some players who never face each other."
]
}
],
"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
}
+1815 -1960
View File
File diff suppressed because it is too large. Load diff
+17748 -8336
View File
File diff suppressed because it is too large. Load diff
+67 -66
View File
@@ -8,7 +8,7 @@
"\n",
"# Bad Grade, Good Experience\n",
"\n",
"Recently I was asked a question I hadn't thought about before: \n",
"Recently I was asked a question I hadn't thought about in decades: \n",
"\n",
"> *As a student, did you ever get a bad grade on a programming assignment?* \n",
"\n",
@@ -20,20 +20,20 @@
"\n",
"After studying Snobol a bit, I realized that the expected solution was along these lines:\n",
"\n",
"1. Create an empty `dict` (Snobol calls these \"tables\") whose keys will be words and values will be lists of line numbers.\n",
"1. Create an empty hash table whose keys will be words and values will be lists of line numbers.\n",
"2. Read the lines of text (tracking the line numbers), split them into words, and build up the list of line numbers for each word.\n",
"3. Convert the table into a two-dimensional `array` where each row has the two columns `[word, line_numbers]`.\n",
"3. Convert the hash 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` is not built-in to Snobol).\n",
"5. Write a function to print the array.\n",
"\n",
"That would be around 40 to 60 lines of code; an easy task. But I noticed three interesting things about Snobol:\n",
"\n",
"* There is an *indirection* operator, `$`, so if the variable `'X'` has the value `\"A\"`, then `'$X = i'` is the same as `'A = i'`.\n",
"* Uninitialized variables are treated as the empty string, so `'A += \"text\"'` works even if we haven't seen `'A'` before.\n",
"* When the program ends, the Snobol interpreter automatically\n",
"prints the values of every variable, sorted alphabetically, as a debugging aid.\n",
"* '`$`' is an *indirection* operator, so if the variable `'word'` has the value `\"A\"`, then `'$word = x'` is the same as `'A = x'`.\n",
"* Uninitialized variables are treated as the empty string, so `'A = A + \"text\"'` works even if we haven't seen `'A'` before.\n",
"* When the program ends, the Snobol interpreter \n",
"prints out each variable (in sorted order), with its value, as a debugging aid.\n",
"\n",
"That means I could do away with the `dict` and `array` data structures, eliminating steps 1, 3, 4, and 5, and just do step 2! \n",
"That means I could use `$` to do away with the hash table and array data structures, eliminating steps 1, 3, 4, and 5, and just do step 2! \n",
"\n",
"# The Concordance Solution\n",
"\n",
@@ -50,8 +50,8 @@
"source": [
"program = \"\"\"\n",
"for i, line in enumerate(input):\n",
" for word in re.findall(r\"\\w+\", line.upper()):\n",
" $word += str(i) + ', '\n",
" for word in re.findall(\"[A-Z]+\", line.upper()):\n",
" $word = $word + i + \", \"\n",
"\"\"\""
]
},
@@ -61,38 +61,43 @@
"source": [
"That's just 3 lines, not 40 to 60! \n",
"\n",
"To test the program, I'll write a mock Snobol/Python interpreter, which at heart is just a call to the Python interpreter, `exec(program)`, except that it handles the three things I noticed about the Snobol interpreter:\n",
"To test the program, I'll write a mock Snobol/Python interpreter, which at heart is just a call to the Python interpreter, `exec(program)`, except that it handles the three things I mentioned about the Snobol interpreter, plus one more:\n",
"\n",
"* `$word` gets translated as `_context[word]`.\n",
"* It calls `exec(program, _context)`, where `_context` is a `defaultdict(str)`, so variables default to `''`.\n",
"* After the `exec` completes, the user-defined variables (but not the built-in ones) are printed."
"1. `$word` gets translated as `_globals[word]`.\n",
"2. The interpreter calls `exec(program, _globals)`, where `_globals` is a `defaultdict` that makes variables default to the empty string.\n",
"3. After the `exec` completes, the user-defined variables (but not the built-in ones) are printed.\n",
"4. Concatenating a string with an integer coerces the `int` to `str` automatically. I'll handle that with a `Str` class.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"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) $word indirection; (2) variables default to ''; (3) post-mortem dump.\"\"\"\n",
" program = re.sub(r'\\$(\\w+)', r'_context[\\1]', program) # (1) \n",
" _context = defaultdict(str, vars(__builtins__)) # (2) \n",
" _context.update(re=re, input=data.splitlines(), _context=_context)\n",
" builtins = set(_context)\n",
" \"\"\"A Python interpreter with four Snobol-ish features:\n",
" 1. $word indirection; 2. variables default to empty string; \n",
" 3. post-mortem dump; 4. automatic coercing to string\"\"\"\n",
" program = re.sub(r'\\$(\\w+)', r'_globals[\\1]', program) # 1. \n",
" _globals = defaultdict(Str, vars(__builtins__)) # 4., 2.\n",
" _globals.update(re=re, input=data.splitlines(), _globals=_globals)\n",
" builtins = set(_globals) | {'__builtins__'}\n",
" try:\n",
" exec(program, _context)\n",
" exec(program, _globals)\n",
" finally:\n",
" print('-' * 79) # (3)\n",
" for name in sorted(_context):\n",
" if not (name in builtins or name == '__builtins__'):\n",
" print('{:10} = {}'.format(name, _context[name]))"
" print('-' * 79) # 3. \n",
" for name in sorted(_globals):\n",
" if name not in builtins:\n",
" print('{:10} = {}'.format(name, _globals[name]))\n",
" \n",
"class Str(str):\n",
" \"String class with automatic coercion for +\"\n",
" def __add__(self, other): return Str(str(self) + str(other))\n",
" def __radd__(self, other): return Str(str(other) + str(self))\n"
]
},
{
@@ -105,9 +110,7 @@
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"data = \"\"\"\n",
@@ -115,17 +118,17 @@
"Singin' \"Do wah diddy diddy dum diddy do\"\n",
"Snappin' her fingers and shufflin' her feet, \n",
"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",
"She looked good (looked good), \n",
"She looked fine (looked fine)\n",
"She looked good, she looked fine \n",
"And I nearly lost my mind\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
@@ -133,24 +136,24 @@
"text": [
"-------------------------------------------------------------------------------\n",
"A = 1, \n",
"AND = 3, 6, \n",
"AND = 3, 8, \n",
"DIDDY = 2, 2, 2, 4, 4, 4, \n",
"DO = 2, 2, 4, 4, \n",
"DOWN = 1, \n",
"DUM = 2, 4, \n",
"FEET = 3, \n",
"FINE = 5, 5, 6, \n",
"FINE = 6, 6, 7, \n",
"FINGERS = 3, \n",
"GOOD = 5, 5, 6, \n",
"GOOD = 5, 5, 7, \n",
"HER = 3, 3, \n",
"I = 6, \n",
"I = 8, \n",
"JUST = 1, \n",
"LOOKED = 5, 5, 5, 5, 6, 6, \n",
"LOST = 6, \n",
"MIND = 6, \n",
"MY = 6, \n",
"NEARLY = 6, \n",
"SHE = 1, 5, 5, 6, 6, \n",
"LOOKED = 5, 5, 6, 6, 7, 7, \n",
"LOST = 8, \n",
"MIND = 8, \n",
"MY = 8, \n",
"NEARLY = 8, \n",
"SHE = 1, 5, 6, 7, 7, \n",
"SHUFFLIN = 3, \n",
"SINGIN = 2, 4, \n",
"SNAPPIN = 3, \n",
@@ -160,8 +163,8 @@
"WAH = 2, 4, \n",
"WALKIN = 1, \n",
"WAS = 1, \n",
"i = 6\n",
"line = She looked good, she looked fine and I nearly lost my mind\n",
"i = 8\n",
"line = And I nearly lost my mind\n",
"word = MIND\n"
]
}
@@ -174,15 +177,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Oops! The post-mortem printout includes the variables `i`, `line`, and `word`. Reluctantly, I increased the program's line count by 33%:"
"**Oops!** The post-mortem printout includes the variables `i`, `line`, and `word`. Reluctantly, I'll increase the program's line count by 33%:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
@@ -190,24 +191,24 @@
"text": [
"-------------------------------------------------------------------------------\n",
"A = 1, \n",
"AND = 3, 6, \n",
"AND = 3, 8, \n",
"DIDDY = 2, 2, 2, 4, 4, 4, \n",
"DO = 2, 2, 4, 4, \n",
"DOWN = 1, \n",
"DUM = 2, 4, \n",
"FEET = 3, \n",
"FINE = 5, 5, 6, \n",
"FINE = 6, 6, 7, \n",
"FINGERS = 3, \n",
"GOOD = 5, 5, 6, \n",
"GOOD = 5, 5, 7, \n",
"HER = 3, 3, \n",
"I = 6, \n",
"I = 8, \n",
"JUST = 1, \n",
"LOOKED = 5, 5, 5, 5, 6, 6, \n",
"LOST = 6, \n",
"MIND = 6, \n",
"MY = 6, \n",
"NEARLY = 6, \n",
"SHE = 1, 5, 5, 6, 6, \n",
"LOOKED = 5, 5, 6, 6, 7, 7, \n",
"LOST = 8, \n",
"MIND = 8, \n",
"MY = 8, \n",
"NEARLY = 8, \n",
"SHE = 1, 5, 6, 7, 7, \n",
"SHUFFLIN = 3, \n",
"SINGIN = 2, 4, \n",
"SNAPPIN = 3, \n",
@@ -223,8 +224,8 @@
"source": [
"program = \"\"\"\n",
"for i, line in enumerate(input):\n",
" for word in re.findall(r\"\\w+\", line.upper()):\n",
" $word += str(i) + ', '\n",
" for word in re.findall(\"[A-Z]+\", line.upper()):\n",
" $word = $word + i + \", \"\n",
"del i, line, word\n",
"\"\"\"\n",
"\n",
@@ -235,7 +236,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Looks good to me! \n",
"## Looks good to me! \n",
"\n",
"But sadly, the grader for the course 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), and if the requirements\n",
"changed, *then* I would write the necessary 40 or 60 lines, but there's no sense doing that until then. The grader was not impressed with my arguments and I got points taken off. \n",
@@ -268,7 +269,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.0"
"version": "3.5.3"
}
},
"nbformat": 4,
+1533 -3661
View File
File diff suppressed because it is too large. Load diff
+645 -497
View File
File diff suppressed because it is too large. Load diff
+462
View File
@@ -0,0 +1,462 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"text-align:right\">Peter Norvig<br>21 March 2018</div>\n",
"\n",
"# `xkcd` Name Dominoes\n",
"\n",
"The March 21, 2018 `xkcd` comic (#1970) was [Name Dominoes](https://xkcd.com/1970/): domino tiles laid out as if in a game, but the tiles contain names of famous poeple rather than numbers. \n",
"In [dominoes](https://en.wikipedia.org/wiki/Dominoes) each tile has two halves, and a tile can be legally placed only if one half is adjacent to another tile half with the same number/name, and neither half is adjacent to a tile half with a different number/name. (Exception: the very first tile can be placed anywhere.) \n",
"\n",
"I will write a function to lay out tiles in a random, legal array. First, the key data structures:\n",
"\n",
"- **`Tile`**: a tile is a 2-element tuple of names, like `('TIM', 'COOK')`.\n",
"The tile `('FRANK LLOYD', 'WRIGHT')` has a space in the first name.\n",
"- **`Name`**: a name (first name or last name) is a string.\n",
"- **`Board(width, height)`**: a `Board` represents\n",
"a width × height array of squares, initially `empty`, but when we put a tile on the board,\n",
"the first name covers one location and the last name covers an adjacent location, e.g. `board[0, 0], board[0, 1] = ('TIM', 'COOK')`. Implemented as a subtype of `dict` that keeps track of `width` and `height`.\n",
"- **`Location`**: a location is an `(x, y)` pair of coordinates for a square on the board.\n",
"\n",
"Now I need a strategy to fill the board with tiles. I will randomly place tiles one at a time, and to make things easier I will *not* consider removing a tile from the board and backtracking. Some more concepts and functions:\n",
"\n",
"- **`frontier`**: I'll maintain a *frontier*, a set of locations that are adjacent to tiles on the board, and thus are candidates for placing new tiles.\n",
"- **`dominoes(tiles)`**: makes a board and places tiles on it, one at a time, until no more can be placed. Chooses a random tile for the first tile, then repeatedly calls `try_one` to legally place an additional tile, stopping when either there is no `frontier` left (meaning no place to legally place a tile) or no `tiles` left to place.\n",
"- **`try_one(tiles, board, frontier)`**: pop a location off the frontier, and try to find some tile that can legally put one of its halves there; when found, `put` the tile there, and remove it from `tiles`.\n",
"- **`legal(name, loc, board)`**: a name can be placed if the location is empty, and there are no conflicts with any neighboring location.\n",
"- **`neighbors(loc, board)`**: returns the (up to 4) neighbors of a location that are on the board.\n",
"- **`put_tile(board, loc0, loc1, tile, frontier)`**: places a tile on the board across `loc0` and `loc1`; update the `frontier` to say that the just-covered locations are no longer in the frontier, but the empty neighbors of the tile are.\n",
"- **`shuffle(items)`**: used to randomize lists; calls `random.shuffle` and returns the result.\n",
"\n",
"# The Code"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"\n",
"empty = None # An empty square\n",
"\n",
"class Board(dict):\n",
" \"A mapping from location to name.\"\n",
" def __init__(self, width, height): self.width, self.height = width, height\n",
" def __missing__(self, loc): return empty\n",
"\n",
"def dominoes(tiles, width=16, height=24):\n",
" \"Place as many tiles on board as possible (legally and randomly).\"\n",
" tiles = shuffle(list(tiles))\n",
" board = Board(width, height)\n",
" frontier = set()\n",
" m = min(width, height) // 2\n",
" put_tile(board, (m, m), (m, m + 1), tiles.pop(), frontier) # Place first tile\n",
" while tiles and frontier:\n",
" try_one(tiles, board, frontier)\n",
" return board\n",
" \n",
"def try_one(tiles, board, frontier):\n",
" \"Pop a frontier location, and try to place a tile on that location.\"\n",
" loc0 = frontier.pop()\n",
" for tile in shuffle(tiles):\n",
" for (name0, name1) in [tile, tile[::-1]]:\n",
" if legal(name0, loc0, board):\n",
" for loc1 in shuffle(neighbors(loc0, board)):\n",
" if legal(name1, loc1, board):\n",
" put_tile(board, loc0, loc1, (name0, name1), frontier)\n",
" tiles.remove(tile)\n",
" return tile\n",
" \n",
"def legal(name, loc, board):\n",
" \"Is it legal to place this name on this location on board?\"\n",
" return (board[loc] is empty and\n",
" all(board[nbr] is empty or board[nbr] == name\n",
" for nbr in neighbors(loc, board)))\n",
"\n",
"def neighbors(loc, board):\n",
" \"Neighbors of this location on the board.\"\n",
" x, y = loc\n",
" return [(x+dx, y+dy) for (dx, dy) in ((0, 1), (1, 0), (0, -1), (-1, 0))\n",
" if 0 <= x+dx < board.width and 0 <= y+dy < board.height]\n",
"\n",
"def put_tile(board, loc0, loc1, tile, frontier): \n",
" \"Place the tile across the two locations, and update frontier.\"\n",
" board[loc0], board[loc1] = tile\n",
" frontier -= {loc0, loc1}\n",
" frontier |= {loc for loc in neighbors(loc0, board) + neighbors(loc1, board)\n",
" if board[loc] is empty}\n",
" \n",
"def shuffle(items): random.shuffle(items); return items"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{(0, 0): 'RY',\n",
" (1, 0): 'KE',\n",
" (1, 1): 'KE',\n",
" (2, 1): 'JO',\n",
" (2, 2): 'JO',\n",
" (3, 2): 'GR',\n",
" (3, 3): 'GR',\n",
" (3, 4): 'KE',\n",
" (4, 4): 'KE',\n",
" (4, 5): 'JA'}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tiles8 = [('BO', 'JA'), ('JA', 'PO'), ('JA', 'RY'), ('RY', 'KE'), \n",
" ('GR', 'KE'), ('GR', 'JO'), ('JA', 'KE'), ('KE', 'JO')]\n",
"\n",
"dominoes(tiles8, 6, 6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Pretty Output\n",
"\n",
"Technically, this is a legal solution, but there are two problems with it: One, it is not visual. Two, it doesn't say where each tile is: when three names come together, which of the outside names goes with the middle name? To fix those two problems I will:\n",
"\n",
"- Define `plot_board(board)` to use `matplotlib` to plot the board, the names, and the tiles.\n",
"- Modify the `Board` class and the `put` function so that the board maintains a list of `boxes` that surround each two-location rectangle that a tile occupies: `[loc0, loc1]`. The constant ϵ is the distance between two adjacent tiles; we want them to not-quite touch (as in the xkcd comic)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"\n",
"ϵ = 0.06 # A small amount; the space between adjacent lines\n",
"\n",
"class Board(dict):\n",
" \"A mapping from location to name.\"\n",
" def __init__(self, width=16, height=24): \n",
" self.width, self.height, self.boxes = width, height, []\n",
" def __missing__(self, loc): return empty\n",
" \n",
"def plot_board(board):\n",
" \"Plot the box and name for every tile, plus a big box around the board.\"\n",
" plt.figure(figsize=(board.width, board.height))\n",
" plt.subplots_adjust(left=ϵ, right=1-ϵ, top=1-ϵ, bottom=ϵ)\n",
" plt.axis('off') \n",
" plt.axis('equal')\n",
" for box in board.boxes:\n",
" plot_box(box)\n",
" plot_box([(-2*ϵ, -2*ϵ), (board.width - 1 + 2*ϵ, board.height - 1 + 2*ϵ)])\n",
" for (x, y) in board:\n",
" plt.text(x + 0.5, y + 0.5, board[x, y], \n",
" va='center', ha='center', fontsize=8)\n",
"\n",
"def plot_box(box):\n",
" \"Plot a box, which is a [loc0, loc1] pair.\"\n",
" Xs, Ys = {loc[0] for loc in box}, {loc[1] for loc in box}\n",
" x0, x1 = min(Xs), max(Xs) + 1 - ϵ\n",
" y0, y1 = min(Ys), max(Ys) + 1 - ϵ\n",
" plt.plot([x0, x1, x1, x0, x0], \n",
" [y0, y0, y1, y1, y0], 'k-')\n",
"\n",
"def put_tile(board, loc0, loc1, tile, frontier): \n",
" \"Place the tile across the two locations, and update frontier and boxes.\"\n",
" board[loc0], board[loc1] = tile\n",
" frontier -= {loc0, loc1}\n",
" frontier |= {loc for loc in neighbors(loc0, board) + neighbors(loc1, board)\n",
" if board[loc] is empty}\n",
" board.boxes.append([loc0, loc1]) "
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAaAAAAGdCAYAAABU0qcqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAEFdJREFUeJzt3X/I7nV9x/HXW06bISjnZLa0tQ0nk6g/GkjtCDqUapkQ5bJlev4Yts1ibP9sI4SonX8CSdpgk5BFM0a0qS0o4eT0aDtr0f4xGTKChWNrMhce2xDW+vHeH/d17O4sPfdt59zv+3vdjwfceJ/rluPrvu7L63l/P/cNV3V3AGCnnTU9AIC9SYAAGCFAAIwQIABGCBAAIwQIgBECBMAIAQJghAABMEKAABghQACMECAARggQACMECIARAgTACAECYIQAATBCgAAYIUAAjBAgAEYIEAAjBAiAEQIEwAgBAmCEAAEwQoAAGCFAAIwQIABGCBAAIwQIgBECBMAIAQJghAABMEKAABghQACMECAARggQACMECIARAgTACAECYIQAATBCgAAYIUAAjBAgAEYIEAAjBAiAEQIEwAgBAmCEAAEwQoAAGCFAAIwQIABGCBAAIwQIgBECBMCIfdMDdpuqeirJ/ukdACvHu/vA9Igzobp7esOuUlXd3TW9AyBZ7+ckR3AAjBAgAEYIEAAjBAiAEQIEwAgBAmCEAAEwQoAAGCFAAIwQIABGCBAAIwQIgBECBMAIAQJghAABMEKAABghQACMECAARggQACMECIARAgTACAECYIQAATBCgAAYIUAAjBAgAEYIEAAjBAiAEQIEwAgBAmCEAAEwQoAAGCFAAIwQIABGCBAAIwQIgBECBMAIAQJghAABMEKAABghQACMECAARggQACMECIARAgTACAECYIQAATBCgAAYIUAAjBAgAEYIEAAjBAiAEfumByxBVT2VZP/0jj3meHcfmB7x41roY+fZ+36h+5dsLR73W1XdPb1hV6mq7u461W2cWetyny/x89i8eYn7l2yvPf84ggNghAABMEKAABghQACMECAARggQACMECIARAgTACAECYIQAATBCgAAYIUAAjBAgAEYIEAAjBAiAEQI0pKqurKrDq/fvrKo7pjdtxWr341X1YFX9bVX9QlW9uKo+XlVHq+qBqvrF6Z3r6sTjpqp+rqruW30d/qqqXjK9bSuWvP+k/2evq6q7q+rJ1efwYFXdPL1xabwi6qyuqrOSvDzL+mbgru7+QFW9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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x110568940>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_board(dominoes(tiles8, 6, 6))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# All the Names\n",
"\n",
"Now let's try all the names from the comic, courtesy of \n",
" [explainxkcd](http://www.explainxkcd.com/wiki/index.php/1970:_Name_Dominoes), with a few typos corrected:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def name_tiles(text):\n",
" \"For each line of text, create a tile of ('First Name(s)', 'Lastname').\"\n",
" return [name.strip().rpartition(' ')[0::2]\n",
" for name in text.upper().split(',')]\n",
" \n",
"xkcdtiles = name_tiles(\"\"\"Christian Campbell, Neve Campbell, Joe McCarthy, Eugene McCarthy, \n",
" Gene Vincent, Gene Kelly, Kate Hudson, Rock Hudson, Gordon Brown, James Brown, Jon Brown, \n",
" John Howard, Columbo, Chris Columbus, Christopher Columbus, Naomi Campbell, Joseph Campbell, \n",
" Joseph Smith, Frank Vincent, John Kelly, Katherine Johnson, The Rock, Chris Rock, Chris Isaac, \n",
" James Newton Howard, John Wayne, Howard Stern, Howard Hunt, Chris Hughes, Naomi Watts, \n",
" Naomi Klein, Kevin Kline, Francis Bacon, Francis Drake, Lyndon Johnson, Oscar The Grouch, \n",
" Oscar Isaac, Isaac Hayes, Isaac Newton, Wayne Newton, Wayne Knight, Helen Hunt, Helen Hughes, \n",
" James Watt, James Watt, Kevin Costner, Kevin Bacon, Kevin Love, Lisa Frank, Frank Drake, Drake, \n",
" Oscar de la Renta, Oscar de la Hoya, Sean Hayes, Wallace Shawn, Wayne Howard, Wayne Brady, \n",
" James Brady, Tom Brady, Helen Thomas, Tom Hanks, Hank Aaron, Aaron Carter, Stephen James, \n",
" Will Smith, Kevin Smith, Kevin James, Garfield, James Garfield, Warren Buffett, Jimmy Buffett, \n",
" Warren Beatty, Elizabeth Warren, Earl Warren, Elizabeth Kolbert, Stephen Colbert, \n",
" Charles Wallace, James Monroe, Marilyn Monroe, Hank Williams, William C. Williams, Steve Harvey,\n",
" Domino Harvey, Harvey Milk, James Saint James, Etta James, Jim Jones, James Earl Jones, \n",
" Charlie Parker, Ray Parker, Ray Charles, Charles Manson, Marilyn Manson, Robin Williams, \n",
" Billy D. Williams, Will Wright, Fats Domino, Bill Clinton, Jimmy John, Tom Jones, Tommy John, \n",
" Quincy Jones, James Earl Ray, Man Ray, Rachel Ray, Ray Allen, Tim Allen, Tim Cook, Tim Howard, \n",
" Robin Wright, Wilbur Wright, Fatty Arbuckle, Fat Joe, George Clinton, John Kerry, \n",
" Kerry Washington, John Irving, John Quincy Adams, John Adams, Amy Adams, Aimee Mann, Super Man, \n",
" Bat Man, Ayn Rand, Lily Allen, Paul Allen, Ron Howard, Howard Hughes, Joe Kennedy, George Bush, \n",
" George Washington, Washington Irving, Martha Washington, Ma Rainey, Jack Ma, Super Grover, \n",
" Jack Black, Rand Paul, Paul Ryan, Paul Simon, Ron Paul, John Hughes, Langston Hughes, \n",
" John F. Kennedy, Little Richard, Rich Little, Martha Stewart, Yo Yo Ma, Ma Bell, \n",
" Grover Cleveland Alexander, Grover Cleveland, Jack White, Jack Ryan, Debby Ryan, Carly Simon, \n",
" Carly Hughes, Charles Evans Hughes, John Williams, Little John, Stuart Little, Potter Stewart, \n",
" Kristen Stewart, Kristen Bell, Kristen Hooks, Alexander Graham Bell, Franklin Graham, \n",
" Lloyd Alexander, Meg White, Meg Ryan, Debbie Reynolds, John Reynolds, Carly Fiorina, \n",
" Grace Lee Boggs, Wade Boggs, William Safire, Prince William, Little Prince, Harry Potter, \n",
" James Potter, James Hook, James Dean, Aretha Franklin, Frank Lloyd Wright, Barry White, \n",
" Walter White, Walt Whitman, John Kelly, Grace Lee, Nancy Grace, Garnet, Prince, Prince Fielder, \n",
" Prince Harry, Harry Styles, John Dean, Benjamin Franklin, Harold Lloyd, Harold Ford, \n",
" Betty White, Meg Whitman, Christine Todd Whitman, Megyn Kelly, Grace Kelly, Grace Jones, \n",
" Jack Nicholson, Jack Ruby, Jack Russell, Harry Fielder, Harry Truman, Harry Jon Benjamin, \n",
" John Edward, Benjamin Harrison, Harrison Ford, Henry Ford, Betty Ford, Betty Friedan, \n",
" Chris Christie, Chris Pratt, Maggie Grace, Grace Hopper, Russell Crowe, Russ Smith, John Smith, \n",
" Justin Long, John Bel Edwards, John Candy, John Henry, Henry James, Bill James, Chris Cooper, \n",
" Chris Hemsworth, Chris Evans, Topher Grace, Van Morrison, Sheryl Crow, Sheryl Sandberg, \n",
" Cameron Crowe, Long John Silver, Olivia Newton John, Huey Long, John Edwards, Candy Crowley, \n",
" Aleister Crowley, James Fenimore Cooper, James Cook, Robert Frost, Bob Evans, Evan Taylor Jones,\n",
" James Cameron, Cam Newton, Cameron Diaz, Huey Newton, Huey Lewis, John Lewis, Jenny Lewis, \n",
" Ryan Lewis, Burt Reynolds, Alistair Cooke, Alistair Cookie, Cokie Roberts, John Roberts, \n",
" Robert Johnson, Robert E. Lee, Tommy Lee, Tommy Lee Jones, Etta James, John Oliver, \n",
" Ryan Reynolds, Alastair Reynolds, George Wallace\"\"\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"270"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(xkcdtiles)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('JAMES EARL', 'RAY'),\n",
" ('BURT', 'REYNOLDS'),\n",
" ('CHRISTOPHER', 'COLUMBUS'),\n",
" ('KRISTEN', 'STEWART'),\n",
" ('FAT', 'JOE'),\n",
" ('HAROLD', 'LLOYD'),\n",
" ('ISAAC', 'HAYES'),\n",
" ('PAUL', 'RYAN'),\n",
" ('GENE', 'KELLY'),\n",
" ('MEGYN', 'KELLY')]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"random.sample(xkcdtiles, 10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Approximate and Partial Matches\n",
"\n",
"Two tile halves match if they are an exact match, like \"ADAMS\" and \"ADAMS\", or if they are an **approximate match**, like \"AMY\" and \"AIMEE\". To accomodate this, you can manually define allowable approximate matches by making the global variable `synsets` (synonym sets) be a mapping from a name to the set of approximate matches it should match, which can be done like this:\n",
"\n",
" synsets = synonyms(\"AMY=AIMEE, COOK=COOKE=COOKIE=COKIE, ...\")\n",
"\n",
"Another issue is a **partial match**: in the comic, some tiles, like \"FRANK LLOYD WRIGHT\" are 3 squares wide, and some, like \"PRINCE\" are only one square wide. For simplicity, I chose to have all my tiles be 2 squares wide, but I still want `'LLOYD'` to match the first name of `('FRANK LLOYD', 'WRIGHT')`. To accomplish this, the second argument to `synonyms` is a list of tiles; the function will go through these and add synset entries for parts of first names,\n",
"so that both `'FRANK'` and `'LLOYD'` will match `'FRANK LLOYD'`. As for \"PRINCE\", he gets represented as the tile `('', 'PRINCE')`. "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import collections\n",
"\n",
"def synonyms(text='', tiles=()): \n",
" \"synonyms('AMY=AIMEE') => dict(AMY={'AMY', 'AIMEE'}, AIMEE={'AMY', 'AIMEE'})\"\n",
" synsets = collections.defaultdict(set)\n",
" # Process `text`\n",
" for text1 in text.upper().split(','):\n",
" synset = set(text1.strip().split('='))\n",
" for s in synset:\n",
" synsets[s] |= synset\n",
" # Process `tiles`\n",
" for (first, last) in tiles:\n",
" for part in first.split():\n",
" synsets[part].add(first)\n",
" synsets[first].add(part)\n",
" return synsets\n",
"\n",
"synsets = synonyms(\"\"\"AMY=AIMEE, COOK=COOKE=COOKIE=COKIE, ALASTAIR=ALISTAIR, \n",
" COLUMBO=COLUMBUS, SAFIRE=SAPPHIRE=GARNET, GARNET=RUBY, CHARLIE=CHARLES, SEAN=SHAWN,\n",
" JIMMY=JAMES, MAN=MANN, JACK=JOHN, TOM=TOMMY, WILL=WILLIAM=WILLIAMS, ROBERT=ROBERTS=BOB, \n",
" CAM=CAMERON, OLIVER=OLIVIA, EDWARD=EDWARDS, RICH=RICHARD, CHRIS=CHRISTOPHER=TOPHER, \n",
" FAT=FATS=FATTY, WALT=WALTER, HANK=HANKS, CROW=CROWE, COLBERT=KOLBERT\"\"\", xkcdtiles)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"To make this work, I update `legal` to consult the `synsets` global variable for an approximate or partial matches, and while I'm changing `legal`, I'll also disallow a match between the empty first name of \"PRINCE\" with the empty first name of \"DRAKE\"."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def legal(name, loc, board):\n",
" \"Is it legal to place this value on this location on board?\"\n",
" return (board[loc] is empty and \n",
" all(board[nbr] is empty \n",
" or board[nbr] == name != '' \n",
" or board[nbr] in synsets[name]\n",
" for nbr in neighbors(loc, board)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Final Result (with Random Restart)\n",
"\n",
"The program sometimes gets stuck after placing relatively few tiles. I could modify the program to *back up* in this case, but it will be easier to just *give up* and restart with an empty board. I can restart multiple times, and accept the best board (the one on which the most tiles were placed):"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x11058c668>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def best(boards): return max(boards, key=lambda board: len(board.boxes))\n",
"\n",
"plot_board(best(dominoes(xkcdtiles) for _ in range(100)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Note:* I used a 16×24 square board while the xkcd comic used 27×35, but if I tried to fit 27 squares across then each tile would be smaller, and many names would overflow the sides of the dominoes too much. Here is the original xkcd comic:\n",
"\n",
"[![](https://imgs.xkcd.com/comics/name_dominoes_2x.png)](https://xkcd.com/1970/)\n",
"\n",
"# What's Next?\n",
"\n",
"I'm happy with the results, but here are some ideas for improvements, if you want something to work on:\n",
"- Allow tiles that are 1 or 3 squares wide, like `('PRINCE',)` or `('FRANK', 'LLOYD', 'WRIGHT')`.\n",
"- Print names vertically in tiles that are placed vertically, and upside down for tiles that are placed horizontally, but with the first name on the right.\n",
"- Print shorter names with a bigger font and longer names with a smaller font.\n",
"- Download a bunch of names from Wikipedia and fill a 200 × 300 board.\n",
"- If you like xkcd try [regex golf](https://github.com/norvig/pytudes/blob/master/ipynb/xkcd1313.ipynb), and [part 2](https://github.com/norvig/pytudes/blob/master/ipynb/xkcd1313-part2.ipynb).\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+1 -1
View File
@@ -1592,7 +1592,7 @@
}
},
"source": [
"An *exhaustive search* considers *every* possible choice of parts, and selects the best solution. On each iteration exhaustive search picks a part (just like greedy search), but then it considers *both* using the part and not using the part. You can see that exhaustive search is almost identical to greedy search, except that it has *two* recursive calls (on lines 7 and 8) instead of *one* (on line 7). (*If you are viewing this in a iPython notebook, not just a web page, you can toggle line numbers by pressing 'ctrl-M L' within a cell.*) How do we choose between the results of the two calls? We need a cost function that we are trying to minimize. (For regex golf the cost of a solution is the length of the string.)"
"An *exhaustive search* considers *every* possible choice of parts, and selects the best solution. On each iteration exhaustive search picks a part (just like greedy search), but then it considers *both* using the part and not using the part. You can see that exhaustive search is almost identical to greedy search, except that it has *two* recursive calls (on lines 7 and 8) instead of *one* (on line 7). (*If you are viewing this in a IPython notebook, not just a web page, you can toggle line numbers by pressing 'ctrl-M L' within a cell.*) How do we choose between the results of the two calls? We need a cost function that we are trying to minimize. (For regex golf the cost of a solution is the length of the string.)"
]
},
{
-428
View File
@@ -1,428 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def partition(covers):\n",
" # covers: {w: {r,...}}\n",
" # invcovers: {r: {w,...}}\n",
" pass\n",
"\n",
"def connected(w, covers, invcovers, result):\n",
" if w not in result:\n",
" result.add(w)\n",
" for r in covers[w]:\n",
" for w2 in invcovers[r]:\n",
" connected(w2, covers, invcovers, result)\n",
" return result\n",
"\n",
"for (W, L, legend) in ALL:\n",
" covers = eliminate_dominated(regex_covers(W, L))\n",
" invcovers = invert_multimap(covers)\n",
" start = list(covers)[2]\n",
" P = connected(start, covers, invcovers, set())\n",
" print legend, len(P), len(covers), len(covers)-len(P)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finding Shorter Regexes: Trying Multiple Times\n",
"----\n",
" \n",
"Why run just two versions of `findregex`? Why not run 1000 variations, and then pick the best solution? Of course, I don't want to write 1000 different functions by hand; I want an automated way of varying each run. I can think of three easy things to vary:\n",
" \n",
"* The number '4' in the `score` function. That is, vary the tradeoff between number of winners matched and number of characters.\n",
"* The tie-breaker. In case of a tie, Python's `max` function always picks the first one. Let's make it choose a different 'best' regex from among all those that tie.\n",
"* The greediness. Don't be so greedy (picking the best) every time. Occasionally pick a not-quite-best component, and see if that works out better.\n",
" \n",
"The first of these is easy; we just use the `random.choice` function to choose an integer, `K`, to serve as the tradeoff factor. \n",
"\n",
"The second is easy too. We could write an alternative to the `max` function, say `max_random_tiebreaker`. That would work, but an easier approach is to build the tiebreaker into the `score` function. In addition to awarding points for matching winners and the number of characters, we will have add in a tiebreaker: a random number between 0 and 1. Since all the scores are otherwise integers, this will not change the order of the scores, but it will break ties.\n",
"\n",
"The third we can accomplish by allowing the random factor to be larger than 1 (allowing us to pick a component that is not the shortest) or even larger than `K` (allowing us to pick a component that does not cover the most winners). \n",
" \n",
"I will factor out the function `greedy_search` to do a single computation oof a covering regex, while keeping the name `findregex` for the top level function that now calls `greedy_search` 1000 times and chooses the best (shortest length) result."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"def findregex(winners, losers, tries=1000):\n",
" \"Find a regex that matches all winners but no losers (sets of strings).\"\n",
" # Repeatedly call 'findregex1' the given number of tries; pick the shortest result\n",
" covers = regex_covers(winners, losers)\n",
" results = [greedy_search(winners, covers) for _ in range(tries)]\n",
" return min(results, key=len)\n",
"\n",
"def greedy_search(winners, covers):\n",
" # On each iteration, add the 'best' component in covers to 'result',\n",
" # remove winners covered by best, and remove from 'pool' any components\n",
" # that no longer match any remaining winners.\n",
" winners = set(winners) # Copy input so as not to modify it.\n",
" pool = set(covers)\n",
" result = []\n",
" \n",
" def matches(regex, strings): return {w for w in covers[regex] if w in strings}\n",
" \n",
" K = random.choice((2, 3, 4, 4, 5, 6))\n",
" T = random.choice((1., 1.5, 2., K+1., K+2.))\n",
" def score(c): \n",
" return K * len(matches(c, winners)) - len(c) + random.uniform(0., T)\n",
" \n",
" while winners:\n",
" best = max(pool, key=score)\n",
" result.append(best)\n",
" winners -= covers[best]\n",
" pool -= {c for c in pool if covers[c].isdisjoint(winners)}\n",
" return OR(result)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def factorial1(n):\n",
" if (n <= 1):\n",
" return 1\n",
" else:\n",
" return n * factorial1(n-1)\n",
"\n",
"def factorial2(n, partial_solution=1):\n",
" if (n <= 1):\n",
" return partial_solution\n",
" else:\n",
" return factorial2(n-1, n * partial_solution)\n",
" \n",
"assert factorial1(6) == factorial2(6) == 720"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def findregex(winners, losers, calls=100000):\n",
" \"Find the shortest disjunction of regex components that covers winners but not losers.\"\n",
" covers = regex_covers(winners, losers)\n",
" best = '^(' + OR(winners) + ')$'\n",
" state = Struct(best=best, calls=calls)\n",
" return bb_search('', covers, state).best\n",
"\n",
"def bb_search(regex, covers, state):\n",
" \"\"\"Recursively build a shortest regex from the components in covers.\"\"\"\n",
" if state.calls > 0:\n",
" state.calls -= 1\n",
" regex, covers = simplify_covers(regex, covers)\n",
" if not covers:\n",
" state.best = min(regex, state.best, key=len)\n",
" elif len(OR2(regex, min(covers, key=len))) < len(state.best):\n",
" # Try with and without the greedy-best component\n",
" def score(c): return 4 * len(covers[c]) - len(c)\n",
" best = max(covers, key=score)\n",
" covered = covers[best]\n",
" covers.pop(best)\n",
" bb_search(OR2(regex, best), {c:covers[c]-covered for c in covers}, state)\n",
" bb_search(regex, covers, state)\n",
" return state\n",
"\n",
"class Struct(object):\n",
" \"A mutable structure with specified fields and values.\"\n",
" def __init__(self, **kwds): vars(self).update(kwds)\n",
" def __repr__(self): return '<%s>' % vars(self)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def findregex(winners, losers, calls=100000):\n",
" \"Find the shortest disjunction of regex components that covers winners but not losers.\"\n",
" covers = regex_covers(winners, losers)\n",
" solution = '^(' + OR(winners) + ')$'\n",
" solution, calls = bb_search('', covers, solution, calls)\n",
" return solution\n",
"\n",
"def bb_search(regex, covers, solution, calls):\n",
" \"\"\"Recursively build a shortest regex from the components in covers.\"\"\"\n",
" if calls > 0:\n",
" calls -= 1\n",
" regex, covers = simplify_covers(regex, covers)\n",
" if not covers: # Solution is complete\n",
" solution = min(regex, solution, key=len)\n",
" elif len(OR2(regex, min(covers, key=len))) < len(solution):\n",
" # Try with and without the greedy-best component\n",
" def score(c): return 4 * len(covers[c]) - len(c)\n",
" r = max(covers, key=score) # Best component\n",
" covered = covers[r] # Set of winners covered by r\n",
" covers.pop(r)\n",
" solution, calls = bb_search(OR2(regex, r), \n",
" {c:covers[c]-covered for c in covers}, \n",
" solution, calls)\n",
" solution, calls = bb_search(regex, covers, solution, calls)\n",
" return solution, calls"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def findregex(winners, losers, calls=100000):\n",
" \"Find the shortest disjunction of regex components that covers winners but not losers.\"\n",
" global SOLUTION, CALLS\n",
" SOLUTION = '^(' + OR(winners) + ')$'\n",
" CALLS = calls\n",
" return bb_search(None, regex_covers(winners, losers))\n",
"\n",
"def bb_search(regex, covers):\n",
" \"\"\"Recursively build a shortest regex from the components in covers.\"\"\"\n",
" global SOLUTION, CALLS\n",
" CALLS -= 1\n",
" regex, covers = simplify_covers(regex, covers)\n",
" if not covers: # Solution is complete\n",
" SOLUTION = min(regex, SOLUTION, key=len)\n",
" elif CALLS >= 0 and len(OR(regex, min(covers, key=len))) < len(SOLUTION):\n",
" # Try with and without the greedy-best component\n",
" def score(c): return 4 * len(covers[c]) - len(c)\n",
" r = max(covers, key=score) # Best component\n",
" covered = covers[r] # Set of winners covered by r\n",
" covers.pop(r)\n",
" bb_search(OR(regex, r), {c:covers[c]-covered for c in covers})\n",
" bb_search(regex, covers)\n",
" return SOLUTION\n",
" \n",
"def OR(*regexes):\n",
" \"OR together regexes. Ignore 'None' components.\"\n",
" return '|'.join(r for r in regexes if r is not None)\n",
"\n",
"\n",
"def invert_multimap(multimap):\n",
" result = collections.defaultdict(list)\n",
" for key in multimap:\n",
" for val in multimap[key]:\n",
" result[val].append(key)\n",
" return result"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"## For debugging\n",
"\n",
"def findregex(winners, losers, calls=100000):\n",
" \"Find the shortest disjunction of regex components that covers winners but not losers.\"\n",
" solution = '^(' + OR(winners) + ')$'\n",
" covers = regex_covers(winners, losers)\n",
" b = BranchBound(solution, calls)\n",
" b.search(None, covers)\n",
" print b.calls, 'calls', len(b.solution), 'len'\n",
" return b.solution\n",
"\n",
"\n",
"def triage_covers(partial, covers):\n",
" \"Simplify covers by eliminating dominated regexes, and picking ones that uniquely cover a winner.\"\n",
" previous = None\n",
" while covers != previous:\n",
" previous = covers\n",
" # Eliminate regexes that are dominated by another regex\n",
" covers = eliminate_dominated(covers) # covers = {regex: {winner,...}}\n",
" coverers = invert_multimap(covers) # coverers = {winner: {regex,...}}\n",
" # For winners covered by only one component, move winner from covers to regex\n",
" singletons = {coverers[w][0] for w in coverers if len(coverers[w]) == 1}\n",
" if singletons:\n",
" partial = OR(partial, OR(singletons))\n",
" covered = {w for c in singletons for w in covers[c]}\n",
" covers = {c:covers[c]-covered for c in covers if c not in singletons}\n",
" return partial, covers\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
", and to , who suggested looking at [WFSTs](http://www.openfst.org/twiki/bin/view/FST/WebHome)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def regex_covers(winners, losers):\n",
" \"\"\"Generate regex components and return a dict of {regex: {winner...}}.\n",
" Each regex matches at least one winner and no loser.\"\"\"\n",
" losers_str = '\\n'.join(losers)\n",
" wholes = {'^'+winner+'$' for winner in winners}\n",
" parts = {d for w in wholes for p in subparts(w) for d in dotify(p)}\n",
" chars = set(cat(winners))\n",
" pairs = {A+'.'+rep_char+B for A in chars for B in chars for rep_char in '+*?'}\n",
" reps = {r for p in parts for r in repetitions(p)}\n",
" pool = wholes | parts | pairs | reps \n",
" searchers = [re.compile(c, re.MULTILINE).search for c in pool]\n",
" covers = {r: set(filter(searcher, winners)) \n",
" for (r, searcher) in zip(pool, searchers)\n",
" if not searcher(losers_str)}\n",
" covers = eliminate_dominated(covers)\n",
" return covers\n",
" return add_character_class_components(covers)\n",
"\n",
"def add_character_class_components(covers):\n",
" for (B, Ms, E) in combine_splits(covers):\n",
" N = len(Ms)\n",
" or_size = N*len(B+'.'+E) + N-1 # N=3 => 'B1E|B2E|B3E'\n",
" class_size = len(B+'[]'+E) + N # N=3 => 'B[123]E'\n",
" winners = {w for m in Ms for w in Ms[m]}\n",
" if class_size < or_size:\n",
" covers[B + make_char_class(Ms) + E] = winners\n",
" return covers\n",
"\n",
"def split3(word):\n",
" \"Splits a word into 3 parts, all ways, with middle part having 0 or 1 character.\"\n",
" return [(word[:i], word[i:i+L], word[i+L:]) \n",
" for i in range(len(word)+1) for L in (0, 1)\n",
" if not word[i:i+L].startswith(('.', '+', '*', '?'))]\n",
"\n",
"def combine_splits(covers):\n",
" \"Convert covers = {BME: {w...}} into a list of [(B, {M...}, E, {w...}].\"\n",
" table = collections.defaultdict(dict) # table = {(B, E): {M: {w...}}}\n",
" for r in covers:\n",
" for (B, M, E) in split3(r):\n",
" table[B, E][M] = covers[r]\n",
" return [(B, Ms, E) for ((B, E), Ms) in table.items()\n",
" if len(Ms) > 1]\n",
"\n",
"def make_char_class(chars):\n",
" chars = set(chars)\n",
" return '[%s]%s' % (cat(chars), ('?' if '' in chars else ''))\n",
"\n",
"covers = regex_covers(boys, girls)\n",
"old = set(covers)\n",
"print len(covers)\n",
"covers = add_character_class_components(covers)\n",
"print len(covers)\n",
"print set(covers) - old\n",
"\n",
"print dict(combine_splits({'..a': {1,2,3}, '..b': {4,5,6}, '..c':{7}}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Consider the two components `'..a'` and `'..b'`. If we wanted to cover all the winners that both of these match, we could use `'..a|..b'`, or we could share the common prefix and introduce a *character class* to get `'..[ab]'`. Since the former is 7 characters and the later is only 6, the later would be preferred. It would be an even bigger win to replace `'..az|..bz|..cz'` with `'..[abc]z'`; that reduces the count from 14 to 8. Similarly, replacing `'..az|..bz|..z'` with `'..[ab]?z'` saves 5 characters.\n",
"\n",
"There seems to be potential savings with character classes. But how do we know which characters from which components to combine into classes? To keep things from getting out of control, I'm going to only look at components that are left after we eliminate dominated. That is not an ideal approach&mdash;there may well be some components that are dominated on their own, but could be part of an optimal solution when combined with other components into a character class. But I'm going to keep it simple."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"Searching: Better Bounds\n",
"----\n",
"\n",
"Branch and bound prunes the search tree whenever it is on a branch that is guaranteed to result in a solution that is no better than the best solution found so far. Currently we estimate the best possible solution along the current branch by taking the length of the partial solution and adding the length of the shortest component in `covers`. We do that because we know for sure that we need at least one component, but we don't know for sure how many components we'll need (nor how long each of them will be. So our estimate is often severely underestimates the true answer, which means we don't cut off search some places where we could, if only we had a better estimate.\n",
" \n",
"Here's one way to get a better bound. We'll define the following quantities:\n",
"\n",
"+ *P* = the length of the partial solution, plus the \"|\", if needed. So if the partial solution is `None`, then *P* will be zero, otherwise *P* is the length plus 1.\n",
"+ *S* = the length of the shortest regex component in `covers`.\n",
"+ *W* = the number of winners still in `covers`.\n",
"+ *C* = the largest number of winners covered by any regex in `covers`.\n",
"\n",
"If we assume The current estimate is *P* + *S*. We can see that a better estimate is *P* + *S* &times; ceil(*W* / *C*)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import math\n",
"\n",
"class BranchBound(object):\n",
" \"Hold state information for a branch and bound search.\"\n",
" def __init__(self, solution, calls):\n",
" self.solution, self.calls = solution, calls\n",
" \n",
" def search(self, covers, partial=None):\n",
" \"Recursively extend partial regex until it matches all winners in covers.\"\n",
" if self.calls <= 0: \n",
" return self.solution\n",
" self.calls -= 1\n",
" covers, partial = simplify_covers(covers, partial)\n",
" if not covers: # Nothing left to cover; solution is complete\n",
" self.solution = min(partial, self.solution, key=len)\n",
" else:\n",
" P = 0 if not partial else len(partial) + 1\n",
" S = len(min(covers, key=len))\n",
" C = max(len(covers[r]) for r in covers)\n",
" W = len(set(w for r in covers for w in covers[r]))\n",
" if P + S * math.ceil(W / C) < len(self.solution):\n",
" # Try with and without the greedy-best component\n",
" def score(r): return 4 * len(covers[r]) - len(r)\n",
" r = max(covers, key=score) # Best component\n",
" covered = covers[r] # Set of winners covered by r\n",
" covers.pop(r)\n",
" self.search({c:covers[c]-covered for c in covers}, OR(partial, r))\n",
" self.search(covers, partial)\n",
" return self.solution"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.0"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
+2 -2
View File
@@ -57,7 +57,7 @@ def standard_env():
'>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,
'abs': abs,
'append': op.add,
'apply': apply,
'apply': lambda proc, args: proc(*args),
'begin': lambda *x: x[-1],
'car': lambda x: x[0],
'cdr': lambda x: x[1:],
@@ -142,4 +142,4 @@ def eval(x, env=global_env):
else: # (proc arg...)
proc = eval(x[0], env)
args = [eval(exp, env) for exp in x[1:]]
return proc(*args)
return proc(*args)
+258
View File
@@ -0,0 +1,258 @@
"""
Code to accompany the chapter "Natural Language Corpus Data"
from the book "Beautiful Data" (Segaran and Hammerbacher, 2009)
http://oreilly.com/catalog/9780596157111/
Code copyright (c) 2008-2009 by Peter Norvig
You are free to use this code under the MIT licencse:
http://www.opensource.org/licenses/mit-license.php
"""
import re, string, random, glob, operator, heapq
from collections import defaultdict
from math import log10
def memo(f):
"Memoize function f."
table = {}
def fmemo(*args):
if args not in table:
table[args] = f(*args)
return table[args]
fmemo.memo = table
return fmemo
def test(verbose=None):
"""Run some tests, taken from the chapter.
Since the hillclimbing algorithm is randomized, some tests may fail."""
import doctest
print 'Running tests...'
doctest.testfile('ngrams-test.txt', verbose=verbose)
################ Word Segmentation (p. 223)
@memo
def segment(text):
"Return a list of words that is the best segmentation of text."
if not text: return []
candidates = ([first]+segment(rem) for first,rem in splits(text))
return max(candidates, key=Pwords)
def splits(text, L=20):
"Return a list of all possible (first, rem) pairs, len(first)<=L."
return [(text[:i+1], text[i+1:])
for i in range(min(len(text), L))]
def Pwords(words):
"The Naive Bayes probability of a sequence of words."
return product(Pw(w) for w in words)
#### Support functions (p. 224)
def product(nums):
"Return the product of a sequence of numbers."
return reduce(operator.mul, nums, 1)
class Pdist(dict):
"A probability distribution estimated from counts in datafile."
def __init__(self, data=[], N=None, missingfn=None):
for key,count in data:
self[key] = self.get(key, 0) + int(count)
self.N = float(N or sum(self.itervalues()))
self.missingfn = missingfn or (lambda k, N: 1./N)
def __call__(self, key):
if key in self: return self[key]/self.N
else: return self.missingfn(key, self.N)
def datafile(name, sep='\t'):
"Read key,value pairs from file."
for line in file(name):
yield line.split(sep)
def avoid_long_words(key, N):
"Estimate the probability of an unknown word."
return 10./(N * 10**len(key))
N = 1024908267229 ## Number of tokens
Pw = Pdist(datafile('count_1w.txt'), N, avoid_long_words)
#### segment2: second version, with bigram counts, (p. 226-227)
def cPw(word, prev):
"Conditional probability of word, given previous word."
try:
return P2w[prev + ' ' + word]/float(Pw[prev])
except KeyError:
return Pw(word)
P2w = Pdist(datafile('count_2w.txt'), N)
@memo
def segment2(text, prev='<S>'):
"Return (log P(words), words), where words is the best segmentation."
if not text: return 0.0, []
candidates = [combine(log10(cPw(first, prev)), first, segment2(rem, first))
for first,rem in splits(text)]
return max(candidates)
def combine(Pfirst, first, (Prem, rem)):
"Combine first and rem results into one (probability, words) pair."
return Pfirst+Prem, [first]+rem
################ Secret Codes (p. 228-230)
def encode(msg, key):
"Encode a message with a substitution cipher."
return msg.translate(string.maketrans(ul(alphabet), ul(key)))
def ul(text): return text.upper() + text.lower()
alphabet = 'abcdefghijklmnopqrstuvwxyz'
def shift(msg, n=13):
"Encode a message with a shift (Caesar) cipher."
return encode(msg, alphabet[n:]+alphabet[:n])
def logPwords(words):
"The Naive Bayes probability of a string or sequence of words."
if isinstance(words, str): words = allwords(words)
return sum(log10(Pw(w)) for w in words)
def allwords(text):
"Return a list of alphabetic words in text, lowercase."
return re.findall('[a-z]+', text.lower())
def decode_shift(msg):
"Find the best decoding of a message encoded with a shift cipher."
candidates = [shift(msg, n) for n in range(len(alphabet))]
return max(candidates, key=logPwords)
def shift2(msg, n=13):
"Encode with a shift (Caesar) cipher, yielding only letters [a-z]."
return shift(just_letters(msg), n)
def just_letters(text):
"Lowercase text and remove all characters except [a-z]."
return re.sub('[^a-z]', '', text.lower())
def decode_shift2(msg):
"Decode a message encoded with a shift cipher, with no spaces."
candidates = [segment2(shift(msg, n)) for n in range(len(alphabet))]
p, words = max(candidates)
return ' '.join(words)
#### General substitution cipher (p. 231-233)
def logP3letters(text):
"The log-probability of text using a letter 3-gram model."
return sum(log10(P3l(g)) for g in ngrams(text, 3))
def ngrams(seq, n):
"List all the (overlapping) ngrams in a sequence."
return [seq[i:i+n] for i in range(1+len(seq)-n)]
P3l = Pdist(datafile('count_3l.txt'))
P2l = Pdist(datafile('count_2l.txt')) ## We'll need it later
def hillclimb(x, f, neighbors, steps=10000):
"Search for an x that maximizes f(x), considering neighbors(x)."
fx = f(x)
neighborhood = iter(neighbors(x))
for i in range(steps):
x2 = neighborhood.next()
fx2 = f(x2)
if fx2 > fx:
x, fx = x2, fx2
neighborhood = iter(neighbors(x))
if debugging: print 'hillclimb:', x, int(fx)
return x
debugging = False
def decode_subst(msg, steps=4000, restarts=90):
"Decode a substitution cipher with random restart hillclimbing."
msg = cat(allwords(msg))
candidates = [hillclimb(encode(msg, key=cat(shuffled(alphabet))),
logP3letters, neighboring_msgs, steps)
for _ in range(restarts)]
p, words = max(segment2(c) for c in candidates)
return ' '.join(words)
def shuffled(seq):
"Return a randomly shuffled copy of the input sequence."
seq = list(seq)
random.shuffle(seq)
return seq
cat = ''.join
def neighboring_msgs(msg):
"Generate nearby keys, hopefully better ones."
def swap(a,b): return msg.translate(string.maketrans(a+b, b+a))
for bigram in heapq.nsmallest(20, set(ngrams(msg, 2)), P2l):
b1,b2 = bigram
for c in alphabet:
if b1==b2:
if P2l(c+c) > P2l(bigram): yield swap(c,b1)
else:
if P2l(c+b2) > P2l(bigram): yield swap(c,b1)
if P2l(b1+c) > P2l(bigram): yield swap(c,b2)
while True:
yield swap(random.choice(alphabet), random.choice(alphabet))
################ Spelling Correction (p. 236-)
def corrections(text):
"Spell-correct all words in text."
return re.sub('[a-zA-Z]+', lambda m: correct(m.group(0)), text)
def correct(w):
"Return the word that is the most likely spell correction of w."
candidates = edits(w).items()
c, edit = max(candidates, key=lambda (c,e): Pedit(e) * Pw(c))
return c
def Pedit(edit):
"The probability of an edit; can be '' or 'a|b' or 'a|b+c|d'."
if edit == '': return (1. - p_spell_error)
return p_spell_error*product(P1edit(e) for e in edit.split('+'))
p_spell_error = 1./20.
P1edit = Pdist(datafile('count_1edit.txt')) ## Probabilities of single edits
def edits(word, d=2):
"Return a dict of {correct: edit} pairs within d edits of word."
results = {}
def editsR(hd, tl, d, edits):
def ed(L,R): return edits+[R+'|'+L]
C = hd+tl
if C in Pw:
e = '+'.join(edits)
if C not in results: results[C] = e
else: results[C] = max(results[C], e, key=Pedit)
if d <= 0: return
extensions = [hd+c for c in alphabet if hd+c in PREFIXES]
p = (hd[-1] if hd else '<') ## previous character
## Insertion
for h in extensions:
editsR(h, tl, d-1, ed(p+h[-1], p))
if not tl: return
## Deletion
editsR(hd, tl[1:], d-1, ed(p, p+tl[0]))
for h in extensions:
if h[-1] == tl[0]: ## Match
editsR(h, tl[1:], d, edits)
else: ## Replacement
editsR(h, tl[1:], d-1, ed(h[-1], tl[0]))
## Transpose
if len(tl)>=2 and tl[0]!=tl[1] and hd+tl[1] in PREFIXES:
editsR(hd+tl[1], tl[0]+tl[2:], d-1,
ed(tl[1]+tl[0], tl[0:2]))
## Body of edits:
editsR('', word, d, [])
return results
PREFIXES = set(w[:i] for w in Pw for i in range(len(w) + 1))
+1
View File
@@ -1 +1,2 @@
numpy
matplotlib