From bcab0e8fd358db4909049b07ceebefdfd473d2a1 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Thu, 21 Feb 2019 20:08:34 -0800 Subject: [PATCH] Add files via upload --- ipynb/Cryptarithmetic.ipynb | 491 ++++++++++++++++++++++++++++++++++++ ipynb/Golomb-Puzzle.ipynb | 479 ++++++++++++++++------------------- 2 files changed, 711 insertions(+), 259 deletions(-) create mode 100644 ipynb/Cryptarithmetic.ipynb diff --git a/ipynb/Cryptarithmetic.ipynb b/ipynb/Cryptarithmetic.ipynb new file mode 100644 index 0000000..8472ca3 --- /dev/null +++ b/ipynb/Cryptarithmetic.ipynb @@ -0,0 +1,491 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
Peter Norvig 2014
\n", + "\n", + "# Cryptarithmetic (Alphametic) Problems\n", + "\n", + "On April 28, Gary Antonik had another [Numberplay column](http://wordplay.blogs.nytimes.com/2014/04/28/num/) that quotes my friend Bill Gosper. (Gosper often presents more advanced puzzles in the [math-fun](http://mailman.xmission.com/cgi-bin/mailman/listinfo/math-fun) mailing list.) This puzzle was:\n", + "\n", + "> For the expression `N U M + B E R = P L A Y`,\n", + "> - Which distinct numerals (each different) can be substituted for letters to make a valid expression?\n", + "> - How many solutions are there?\n", + "\n", + "I [tackled]((https://www.udacity.com/wiki/cs212/unit-2#rethinking-eval)) this type of problem (known as a [cryptarithmetic](http://mathworld.wolfram.com/Cryptarithmetic.html) or [alphametic](http://mathworld.wolfram.com/Alphametic.html) problem) in my Udacity class [CS 212](https://www.udacity.com/wiki/cs212/unit-2#cryptarithmetic). \n", + "\n", + "My initial approach was simple: [when in doubt, use brute force](https://www.brainyquote.com/quotes/ken_thompson_185574). I try all permutations of digits replacing letters (that should be quick and easy—there can be at most 10 factorial or 3.6 million permutations), then for each one, I use Python's `eval` function to see if the resulting string is a valid expression. The basic idea is simple, but there are a few complications to worry about:\n", + "\n", + "1. Math uses `=` and Python uses `==` for equality; we'll allow either one in formulas.\n", + "2. A number with a leading zero (like `012`) is illegal (but `0` by itself is ok).\n", + "4. If we try to eval an expression like 1/0, we'll have to handle the divide-by-zero error.\n", + "5. Only uppercase letters are replaced by digits. So in `2 * BE or not 2`, the `or` and `not` are Python keywords.\n", + "6. Should I find one solution (faster) or all solutions (more complete)? I'll handle both use cases by \n", + "implementing my function `solve` to return an iterator, which yields solutions one at a time; you can get the first one with `next` or all of them with `set`. \n", + "\n", + "## The solution: `solve`\n", + "\n", + "Below we see that `solve` works by generating every way to replace letters in the formula with numbers,\n", + "and then filtering them to keep only valid strings (ones that evaluate to true and have no leading zero)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import itertools\n", + "import re\n", + "\n", + "def solve(formula):\n", + " \"\"\"Given a formula like 'NUM + BER = PLAY', fill in digits to solve it.\n", + " Generate all valid digit-filled-in strings.\"\"\"\n", + " return filter(valid, replace_letters(formula.replace(' = ', ' == ')))\n", + "\n", + "def replace_letters(formula):\n", + " \"\"\"Generate all possible replacements of letters with digits in formula.\"\"\"\n", + " letters = ''.join(set(re.findall('[A-Z]', formula)))\n", + " for digits in itertools.permutations('1234567890', len(letters)):\n", + " yield formula.translate(str.maketrans(letters, ''.join(digits)))\n", + "\n", + "def valid(exp):\n", + " \"\"\"Expression is valid iff it has no leading zero, and evaluates to true.\"\"\"\n", + " try:\n", + " return not leading_zero(exp) and eval(exp)\n", + " except ArithmeticError:\n", + " return False\n", + " \n", + "leading_zero = re.compile(r'\\b0[0-9]').search" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'746 + 289 == 1035'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "next(solve('NUM + BER = PLAY'))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 17 s, sys: 68.2 ms, total: 17.1 s\n", + "Wall time: 17.2 s\n" + ] + }, + { + "data": { + "text/plain": [ + "96" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%time len(set(solve('NUM + BER = PLAY')))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So there are 96 solutions, but `solve` is a bit slow to find them all. How could we make `solve` faster?\n", + "\n", + "## A faster solution: `faster_solve`\n", + "\n", + "I used `%prun` to get profiling results, and saw that 2/3 of the time is spent in `eval`. So let's eliminate the calls to `eval`. That should be doable, because the expression we are evaluating is basically the same each time, but with different permutations of digits filled in. We could save a lot of work if we convert the expression into a Python function, compile that function just once, and then call the function for each permutation of digits. In other words, we want to take an expression such as\n", + "\n", + " \"NUM + BER == PLAY\"\n", + " \n", + "and transform it into the Python function\n", + "\n", + "\n", + " lambda A,B,E,L,M,N,P,R,U,Y: (100*N+10*U+M) + (100*B+10*E+R) == (1000*P+100*L+10*A+Y)\n", + " \n", + "Actually that's not quite right. The rules say that \"N\", \"B\", and \"P\" cannot be zero. So the function should be:\n", + "\n", + " A,B,E,L,M,N,P,R,U,Y: B and N and P and ((100*N+10*U+M) + (100*B+10*E+R) == (1000*P+100*L+10*A+Y))\n", + "\n", + "Here is the code to compile a formula into a Python function: " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def compile_formula(formula, verbose=False):\n", + " \"\"\"Compile formula into a function. Also return letters found, as a str,\n", + " in same order as parms of function. For example, 'YOU == ME**2' returns\n", + " (lambda E,M,O,U,Y: M and Y and ((100*Y+10*O+U) == (10*M+E)**2), 'YMEUO'\"\"\"\n", + " formula = formula.replace(' = ', ' == ')\n", + " letters = ''.join(sorted(set(re.findall('[A-Z]', formula))))\n", + " firstletters = sorted(set(re.findall(r'\\b([A-Z])[A-Z]', formula)))\n", + " body = re.sub('[A-Z]+', compile_word, formula)\n", + " body = ' and '.join(firstletters + [body])\n", + " fn = 'lambda {}: {}'.format(','.join(letters), body)\n", + " if verbose: print(fn)\n", + " assert len(letters) <= 10\n", + " return eval(fn), letters\n", + "\n", + "def compile_word(matchobj):\n", + " \"Compile the word 'YOU' as '(100*Y + 10*O + U)'.\"\n", + " word = matchobj.group()\n", + " terms = reversed([mult(10**i, L) for (i, L) in enumerate(reversed(word))])\n", + " return '(' + '+'.join(terms) + ')'\n", + "\n", + "def mult(factor, var): return var if factor == 1 else str(factor) + '*' + var" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lambda E,M,O,U,Y: M and Y and (100*Y+10*O+U) == (10*M+E)**2\n" + ] + }, + { + "data": { + "text/plain": [ + "((E, M, O, U, Y)>, 'EMOUY')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "compile_formula(\"YOU == ME**2\", verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lambda A,B,E,L,M,N,P,R,U,Y: 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": [ + "((A, B, E, L, M, N, P, R, U, Y)>, 'ABELMNPRUY')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "compile_formula(\"NUM + BER = PLAY\", verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def faster_solve(formula):\n", + " \"\"\"Given a formula like 'NUM + BER == PLAY', fill in digits to solve it.\n", + " This version precompiles the formula and generates all digit-filled-in strings.\"\"\"\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", + " if fn(*digits):\n", + " yield formula.translate(str.maketrans(letters, ''.join(map(str, digits))))\n", + " except ArithmeticError: \n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'587 + 439 = 1026'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "next(faster_solve('NUM + BER = PLAY'))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 1.3 s, sys: 3.96 ms, total: 1.3 s\n", + "Wall time: 1.3 s\n" + ] + }, + { + "data": { + "text/plain": [ + "96" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%time len(list(faster_solve('NUM + BER = PLAY')))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We get the same answer, but `faster_solve` is 15 times faster than `solve`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# More Examples" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NUM + BER = PLAY | 587 + 439 = 1026\n", + "YOU = ME ** 2 | 576 = 24 ** 2\n", + "SEND + MORE = MONEY | 9567 + 1085 = 10652\n", + "PI * R**2 = AREA | 96 * 7**2 = 4704\n", + "WRONG + WRONG = RIGHT | 37081 + 37081 = 74162\n", + "WRIGHT + WRIGHT = TO * FLY + FLIGHT | 413058 + 413058 = 82 * 769 + 763058\n", + "TWO + TWENTY = TWELVE + TEN | 784 + 781279 = 781351 + 712\n", + "A**2 + B**2 = C**2 | 3**2 + 4**2 = 5**2\n", + "AYH**2 + BEE**2 = SEE**2 | 760**2 + 522**2 = 922**2\n", + "RAMN = R**3 + RM**3 = N**3 + RX**3 | 1729 = 1**3 + 12**3 = 9**3 + 10**3\n", + "MON-EY = EVIL**(1/2) | 108-42 = 4356**(1/2)\n", + "ALPHABET + LETTERS = SCRABBLE | 17531908 + 7088062 = 24619970\n", + "SIN**2 + COS**2 = UNITY | 235**2 + 142**2 = 75389\n", + "POTATO + TOMATO = PUMPKIN | 168486 + 863486 = 1031972\n", + "ODD * ODD = FREAKY | 677 * 677 = 458329\n", + "DOUBLE + DOUBLE + TOIL = TROUBLE | 798064 + 798064 + 1936 = 1598064\n", + "WASH + YOUR = HANDS | 5291 + 6748 = 12039\n", + "SPEED + LIMIT = FIFTY | 40221 + 36568 = 76789\n", + "TERRIBLE + NUMBER = THIRTEEN | 45881795 + 302758 = 46184553\n", + "SEVEN + SEVEN + SIX = TWENTY | 68782 + 68782 + 650 = 138214\n", + "EIGHT + EIGHT + TWO + ONE + ONE = TWENTY | 52371 + 52371 + 104 + 485 + 485 = 105816\n", + "ELEVEN + NINE + FIVE + FIVE = THIRTY | 797275 + 5057 + 4027 + 4027 = 810386\n", + "NINE + SEVEN + SEVEN + SEVEN = THIRTY | 3239 + 49793 + 49793 + 49793 = 152618\n", + "FOURTEEN + TEN + TEN + SEVEN = FORTYONE | 19564882 + 482 + 482 + 78082 = 19643928\n", + "HAWAII + IDAHO + IOWA + OHIO = STATES | 510199 + 98153 + 9301 + 3593 = 621246\n", + "VIOLIN * 2 + VIOLA = TRIO + SONATA | 176478 * 2 + 17645 = 2076 + 368525\n", + "SEND + A + TAD + MORE = MONEY | 9283 + 7 + 473 + 1062 = 10825\n", + "ZEROES + ONES = BINARY | 698392 + 3192 = 701584\n", + "DCLIZ + DLXVI = MCCXXV | 62049 + 60834 = 122883\n", + "COUPLE + COUPLE = QUARTET | 653924 + 653924 = 1307848\n", + "FISH + N + CHIPS = SUPPER | 5718 + 3 + 98741 = 104462\n", + "SATURN + URANUS + NEPTUNE + PLUTO = PLANETS | 127503 + 502351 + 3947539 + 46578 = 4623971\n", + "PLUTO not in {PLANETS} | 63985 not in {6314287}\n", + "EARTH + AIR + FIRE + WATER = NATURE | 67432 + 704 + 8046 + 97364 = 173546\n", + "TWO * TWO = SQUARE | 807 * 807 = 651249\n", + "HIP * HIP = HURRAY | 958 * 958 = 917764\n", + "NORTH / SOUTH = EAST / WEST | 51304 / 61904 = 7260 / 8760\n", + "NAUGHT ** 2 = ZERO ** 3 | 328509 ** 2 = 4761 ** 3\n", + "I + THINK + IT + BE + THINE = INDEED | 1 + 64125 + 16 + 73 + 64123 = 128338\n", + "DO + YOU + FEEL = LUCKY | 57 + 870 + 9441 = 10368\n", + "WELL - DO + YOU = PUNK | 5277 - 13 + 830 = 6094\n", + "NOW + WE + KNOW + THE = TRUTH | 673 + 38 + 9673 + 128 = 10512\n", + "SORRY + TO + BE + A + PARTY = POOPER | 80556 + 20 + 34 + 9 + 19526 = 100145\n", + "SORRY + TO + BUST + YOUR = BUBBLE | 94665 + 24 + 1092 + 5406 = 101187\n", + "STEEL + BELTED = RADIALS | 87336 + 936732 = 1024068\n", + "ABRA + CADABRA + ABRA + CADABRA = HOUDINI | 7457 + 1797457 + 7457 + 1797457 = 3609828\n", + "I + GUESS + THE + TRUTH = HURTS | 5 + 26811 + 478 + 49647 = 76941\n", + "LETS + CUT + TO + THE = CHASE | 9478 + 127 + 75 + 704 = 10384\n", + "THATS + THE + THEORY = ANYWAY | 86987 + 863 + 863241 = 951091\n", + "TWO + TWO = FOUR | 734 + 734 = 1468\n", + "X / X = X | 1 / 1 = 1\n", + "A**N + B**N = C**N and N > 1 | 3**2 + 4**2 = 5**2 and 2 > 1\n", + "ATOM**0.5 = A + TO + M | 1296**0.5 = 1 + 29 + 6\n", + "GLITTERS is not GOLD | 35499278 is not 3651\n", + "ONE < TWO and FOUR < FIVE | 351 < 703 and 2386 < 2491\n", + "ONE < TWO < THREE < TWO * TWO | 431 < 674 < 62511 < 674 * 674\n", + "(2 * BE or not 2 * BE) = THE + QUES-TION | (2 * 13 or not 2 * 13) = 843 + 7239-8056\n", + "sum(range(AA)) = BB | sum(range(11)) = 55\n", + "sum(range(POP)) = BOBO | sum(range(101)) = 5050\n", + "ODD + ODD = EVEN | 655 + 655 = 1310\n", + "ROMANS+ALSO+MORE+OR+LESS+ADDED = LETTERS | 975348+3187+5790+79+1088+36606 = 1022098\n", + "FOUR+ONE==FIVE and ONE+ONE+ONE+ONE+ONE==FIVE | 1380+345==1725 and 345+345+345+345+345==1725\n", + "TEN+SEVEN+SEVEN+SEVEN+FOUR+FOUR+ONE = FORTY | 520+12820+12820+12820+4937+4937+902 = 49756\n", + "NINETEEN+THIRTEEN+THREE+2*TWO+3*ONE = FORTYTWO| 42415114+56275114+56711+2*538+3*841 = 98750538\n", + "IN + ARCTIC + TERRAIN + AN + ANCIENT + EERIE + ICE + TRACT + I + ENTER + A + TRANCE = FLATIANA| 42 + 379549 + 5877342 + 32 + 3294825 + 88748 + 498 + 57395 + 4 + 82587 + 3 + 573298 = 10354323\n", + "ONE < TWO < THREE < SEVEN - THREE < THREE + TWO < THREE + THREE < SEVEN < SEVEN + ONE < THREE * THREE| 321 < 483 < 45711 < 91612 - 45711 < 45711 + 483 < 45711 + 45711 < 91612 < 91612 + 321 < 45711 * 45711\n", + "AN + ACCELERATING + INFERENTIAL + ENGINEERING + TALE + ELITE + GRANT + FEE + ET + CETERA = ARTIFICIAL + INTELLIGENCE| 59 + 577404251698 + 69342491650 + 49869442698 + 1504 + 40614 + 82591 + 344 + 41 + 741425 = 5216367650 + 691400684974\n", + "CPU times: user 49.7 s, sys: 216 ms, total: 50 s\n", + "Wall time: 50.2 s\n" + ] + }, + { + "data": { + "text/plain": [ + "67" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "examples = \"\"\"\n", + "NUM + BER = PLAY\n", + "YOU = ME ** 2\n", + "SEND + MORE = MONEY\n", + "PI * R**2 = AREA\n", + "WRONG + WRONG = RIGHT\n", + "WRIGHT + WRIGHT = TO * FLY + FLIGHT\n", + "TWO + TWENTY = TWELVE + TEN\n", + "A**2 + B**2 = C**2\n", + "AYH**2 + BEE**2 = SEE**2\n", + "RAMN = R**3 + RM**3 = N**3 + RX**3\n", + "MON-EY = EVIL**(1/2)\n", + "ALPHABET + LETTERS = SCRABBLE\n", + "SIN**2 + COS**2 = UNITY\n", + "POTATO + TOMATO = PUMPKIN\n", + "ODD * ODD = FREAKY\n", + "DOUBLE + DOUBLE + TOIL = TROUBLE\n", + "WASH + YOUR = HANDS\n", + "SPEED + LIMIT = FIFTY\n", + "TERRIBLE + NUMBER = THIRTEEN\n", + "SEVEN + SEVEN + SIX = TWENTY\n", + "EIGHT + EIGHT + TWO + ONE + ONE = TWENTY\n", + "ELEVEN + NINE + FIVE + FIVE = THIRTY\n", + "NINE + SEVEN + SEVEN + SEVEN = THIRTY\n", + "FOURTEEN + TEN + TEN + SEVEN = FORTYONE\n", + "HAWAII + IDAHO + IOWA + OHIO = STATES\n", + "VIOLIN * 2 + VIOLA = TRIO + SONATA\n", + "SEND + A + TAD + MORE = MONEY\n", + "ZEROES + ONES = BINARY\n", + "DCLIZ + DLXVI = MCCXXV\n", + "COUPLE + COUPLE = QUARTET\n", + "FISH + N + CHIPS = SUPPER\n", + "SATURN + URANUS + NEPTUNE + PLUTO = PLANETS\n", + "PLUTO not in {PLANETS}\n", + "EARTH + AIR + FIRE + WATER = NATURE\n", + "TWO * TWO = SQUARE\n", + "HIP * HIP = HURRAY\n", + "NORTH / SOUTH = EAST / WEST\n", + "NAUGHT ** 2 = ZERO ** 3\n", + "I + THINK + IT + BE + THINE = INDEED\n", + "DO + YOU + FEEL = LUCKY\n", + "WELL - DO + YOU = PUNK\n", + "NOW + WE + KNOW + THE = TRUTH\n", + "SORRY + TO + BE + A + PARTY = POOPER\n", + "SORRY + TO + BUST + YOUR = BUBBLE\n", + "STEEL + BELTED = RADIALS\n", + "ABRA + CADABRA + ABRA + CADABRA = HOUDINI\n", + "I + GUESS + THE + TRUTH = HURTS\n", + "LETS + CUT + TO + THE = CHASE\n", + "THATS + THE + THEORY = ANYWAY\n", + "TWO + TWO = FOUR\n", + "X / X = X\n", + "A**N + B**N = C**N and N > 1\n", + "ATOM**0.5 = A + TO + M\n", + "GLITTERS is not GOLD\n", + "ONE < TWO and FOUR < FIVE\n", + "ONE < TWO < THREE < TWO * TWO\n", + "(2 * BE or not 2 * BE) = THE + QUES-TION\n", + "sum(range(AA)) = BB\n", + "sum(range(POP)) = BOBO\n", + "ODD + ODD = EVEN\n", + "ROMANS+ALSO+MORE+OR+LESS+ADDED = LETTERS\n", + "FOUR+ONE==FIVE and ONE+ONE+ONE+ONE+ONE==FIVE\n", + "TEN+SEVEN+SEVEN+SEVEN+FOUR+FOUR+ONE = FORTY\n", + "NINETEEN+THIRTEEN+THREE+2*TWO+3*ONE = FORTYTWO\n", + "IN + ARCTIC + TERRAIN + AN + ANCIENT + EERIE + ICE + TRACT + I + ENTER + A + TRANCE = FLATIANA\n", + "ONE < TWO < THREE < SEVEN - THREE < THREE + TWO < THREE + THREE < SEVEN < SEVEN + ONE < THREE * THREE\n", + "AN + ACCELERATING + INFERENTIAL + ENGINEERING + TALE + ELITE + GRANT + FEE + ET + CETERA = ARTIFICIAL + INTELLIGENCE\n", + "\"\"\".strip().splitlines()\n", + "\n", + "def run(examples):\n", + " for e in examples:\n", + " print('{:45}| {}'.format(e, next(faster_solve(e))))\n", + " return len(examples)\n", + " \n", + "%time run(examples)" + ] + } + ], + "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.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/ipynb/Golomb-Puzzle.ipynb b/ipynb/Golomb-Puzzle.ipynb index 8c6e852..3ca5c15 100644 --- a/ipynb/Golomb-Puzzle.ipynb +++ b/ipynb/Golomb-Puzzle.ipynb @@ -4,7 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Sol Golomb’s Rectangle Puzzle" + "# Sol Golomb’s Rectangle Puzzle\n", + "\n", + "
Peter Norvig" ] }, { @@ -13,15 +15,15 @@ "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", - ">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", + ">1. *How many different sets of five rectangles are possible?*\n", ">\n", - ">2. What are the maximum and minimum values for the total areas of the five rectangles?\n", + ">2. *What are the maximum and minimum values for the total areas of the five rectangles?*\n", ">\n", - ">3. What other values for the total areas of the five rectangles are possible?\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?\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." ] @@ -70,7 +72,9 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def rectangle_sets(sides):\n", @@ -87,7 +91,9 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -114,7 +120,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -147,7 +155,9 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -188,13 +198,15 @@ } }, "source": [ - "I think I know this one, but I'm not completely sure. I know that a rectangle with a fixed perimeter has maximum area when it is a square. My guess is that the maximum total area occurs when each rectangle is *almost* square: a 9 × 10 rectangle; 7 × 8; and so on. So that would give us a maximum area of:" + "I think I know this one, but I'm not sure. I know that a rectangle with a fixed perimeter has maximum area when it is a square. My guess is that the maximum *total* area occurs when each rectangle is *almost* square: a 9 × 10 rectangle; 7 × 8; and so on. So that would give us a maximum area of:" ] }, { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -221,7 +233,9 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -248,7 +262,9 @@ { "cell_type": "code", "execution_count": 7, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def total_area(rectangles): return sum(w * h for (w, h) in rectangles)" @@ -257,7 +273,9 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -277,7 +295,9 @@ { "cell_type": "code", "execution_count": 9, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -318,7 +338,9 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", @@ -343,7 +365,9 @@ { "cell_type": "code", "execution_count": 11, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -364,7 +388,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The answer is: \"All the integers between 110 and 190 inclusive, except 176, 185, 188, and 189.\"" + "The answer is: \"All the integers between the min (110) and max (190), except 176, 185, 188, and 189.\"" ] }, { @@ -378,13 +402,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The only way I can think about this is to write a program; I don't see any way to work it out by hand. I do know that the total area will have to be a perfect square, and since the total area is between 110 and 191, that means either 121, 144, or 169:" + "The only way I can think about this is to write a program; I don't see any way to work it out by hand. I do know that the total area will have to be a perfect square, so:" ] }, { "cell_type": "code", "execution_count": 12, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -432,30 +458,11 @@ } ], "source": [ - "perfect_squares = {i ** 2 for i in range(100)}\n", + "def is_perfect_square(n): return is_integer(n ** 0.5)\n", + "def is_integer(n): return (int(n) == n)\n", "\n", "sorted((total_area(s), s) \n", - " for s in all_sets if total_area(s) in perfect_squares)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "35" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(_)" + " for s in all_sets if is_perfect_square(total_area(s)))" ] }, { @@ -472,8 +479,10 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, + "execution_count": 13, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "empty = (0, 0)\n", @@ -486,8 +495,10 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, + "execution_count": 14, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -499,7 +510,7 @@ " [(0, 0), (0, 0), (0, 0), (0, 0), (0, 0)]]" ] }, - "execution_count": 15, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -517,8 +528,10 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, + "execution_count": 15, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def place_rectangle_at(rect, grid, pos):\n", @@ -537,8 +550,10 @@ }, { "cell_type": "code", - "execution_count": 17, - "metadata": {}, + "execution_count": 16, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -550,7 +565,7 @@ " [(0, 0), (0, 0), (3, 4), (3, 4), (3, 4)]]" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -562,8 +577,10 @@ }, { "cell_type": "code", - "execution_count": 18, - "metadata": {}, + "execution_count": 17, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -575,7 +592,7 @@ " [(2, 5), (2, 5), (3, 4), (3, 4), (3, 4)]]" ] }, - "execution_count": 18, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -590,8 +607,10 @@ }, { "cell_type": "code", - "execution_count": 19, - "metadata": {}, + "execution_count": 18, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -603,7 +622,7 @@ " [(2, 5), (2, 5), (3, 4), (3, 4), (3, 4)]]" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -615,8 +634,10 @@ }, { "cell_type": "code", - "execution_count": 20, - "metadata": {}, + "execution_count": 19, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", @@ -635,6 +656,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "# Packing Strategy\n", + "\n", "Now we need a strategy for packing a set of rectangles onto a grid. I know that many variants of [bin packing problems](http://en.wikipedia.org/wiki/Bin_packing_problem) are NP-hard, but we only have 5 rectangles, so it should be easy: just exhaustively try each rectangle in each possible position, and in both possible orientations (horizontal and vertical). But placing rectangles is commutative, so we can do this two ways:\n", "\n", "> Way 1: Considering the *rectangles* in a fixed order, try every possible *position* for each *rectangle*.\n", @@ -645,37 +668,34 @@ "\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 105 = 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 horizontaly or vertically, so we would have to consider 5! × 25 = 3840 combinations. Since 3840 < 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 horizontally or vertically, so we would have to consider 5! × 25 = 3840 combinations. Since 3840 < 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. " ] }, { "cell_type": "code", - "execution_count": 21, - "metadata": {}, + "execution_count": 20, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def pack(rectangles, grid):\n", " \"\"\"Find a way to pack all rectangles onto grid and return the packed grid,\n", " or return None if not possible.\"\"\"\n", - " if not rectangles:\n", + " if not rectangles or grid is None:\n", " return grid \n", " pos = first_empty_cell(grid)\n", - " if grid and pos:\n", - " for (rectangles2, grid2) in rectangle_placements(rectangles, grid, pos):\n", - " solution = pack(rectangles2, grid2)\n", + " if pos is None:\n", + " return None\n", + " for rect in rectangles:\n", + " for (w, h) in [rect, reversed(rect)]:\n", + " grid2 = place_rectangle_at((w, h), grid, pos)\n", + " solution = pack(rectangles - {rect}, grid2)\n", " if solution:\n", " return solution\n", "\n", - "def rectangle_placements(rectangles, grid, pos):\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", - " if grid2: \n", - " yield rectangles - {(w, h)}, grid2 \n", - " \n", "def first_empty_cell(grid):\n", " \"The uppermost, leftmost empty cell.\"\n", " for (y, row) in enumerate(grid):\n", @@ -693,8 +713,10 @@ }, { "cell_type": "code", - "execution_count": 22, - "metadata": {}, + "execution_count": 21, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -706,7 +728,7 @@ " [(2, 5), (2, 5), (3, 4), (3, 4), (3, 4)]]" ] }, - "execution_count": 22, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -728,13 +750,15 @@ "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. (*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.)" + "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." ] }, { "cell_type": "code", - "execution_count": 23, - "metadata": {}, + "execution_count": 22, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "from IPython.display import HTML, display, clear_output\n", @@ -745,8 +769,7 @@ " display(html_table(grid, colored_cell))\n", " \n", "def html_table(grid, cell_function='{}'.format):\n", - " \"\"\"Return an HTML , where each cell's contents comes from calling \n", - " cell_function(grid[y][x])\"\"\"\n", + " \"Return an HTML
, where each cell's contents comes from calling cell_function(grid[y][x])\"\n", " return HTML('
{}
'\n", " .format(cat('\\n' + cat(map(cell_function, row)) \n", " for row in grid)))\n", @@ -755,16 +778,17 @@ " x, y = sorted(rect)\n", " return '{}{}'.format(colors[x], x%10, y%10)\n", "\n", - "colors = ('lightgrey yellow plum chartreuse cyan coral red olive slateblue lightgrey wheat'\n", - " .split())\n", + "colors = 'lightgrey yellow plum chartreuse cyan coral red olive slateblue lightgrey wheat'.split()\n", "\n", "cat = ''.join" ] }, { "cell_type": "code", - "execution_count": 24, - "metadata": {}, + "execution_count": 23, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -798,26 +822,28 @@ }, { "cell_type": "code", - "execution_count": 25, - "metadata": {}, + "execution_count": 24, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "\n", - "
37373750505050505050505050\n", - "
37373750505050505050505050\n", - "
37373750505050505050505050\n", - "
37373750505050505050505050\n", - "
37373750505050505050505050\n", - "
37373712898989898989898989\n", - "
37373712898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989
" + "50505050508989898989898989\n", + "50505050508989898989898989\n", + "50505050508989898989898989\n", + "50505050508989898989898989\n", + "50505050508989898989898989\n", + "50505050508989898989898989\n", + "50505050508989898989898989\n", + "50505050508989898989898989\n", + "50505050508989898989898989\n", + "50505050501212464646464646\n", + "37373737373737464646464646\n", + "37373737373737464646464646\n", + "37373737373737464646464646" ], "text/plain": [ "" @@ -830,19 +856,19 @@ "data": { "text/html": [ "\n", - "
79797979797979606060606060\n", - "
79797979797979606060606060\n", - "
79797979797979606060606060\n", - "
79797979797979606060606060\n", - "
79797979797979606060606060\n", - "
79797979797979606060606060\n", - "
79797979797979606060606060\n", - "
79797979797979606060606060\n", - "
79797979797979606060606060\n", - "
45454545451212606060606060\n", - "
45454545453838383838383838\n", - "
45454545453838383838383838\n", - "
45454545453838383838383838
" + "45454545797979797979797979\n", + "45454545797979797979797979\n", + "45454545797979797979797979\n", + "45454545797979797979797979\n", + "45454545797979797979797979\n", + "38383812797979797979797979\n", + "38383812797979797979797979\n", + "38383860606060606060606060\n", + "38383860606060606060606060\n", + "38383860606060606060606060\n", + "38383860606060606060606060\n", + "38383860606060606060606060\n", + "38383860606060606060606060" ], "text/plain": [ "" @@ -855,17 +881,17 @@ "data": { "text/html": [ "\n", - "
3939395858585858585858\n", - "
3939395858585858585858\n", - "
3939395858585858585858\n", - "
3939395858585858585858\n", - "
3939395858585858585858\n", - "
3939394747474747474716\n", - "
3939394747474747474716\n", - "
3939394747474747474716\n", - "
3939394747474747474716\n", - "
2020202020202020202016\n", - "
2020202020202020202016
" + "2020393939393939393939\n", + "2020393939393939393939\n", + "2020393939393939393939\n", + "2020474747475858585858\n", + "2020474747475858585858\n", + "2020474747475858585858\n", + "2020474747475858585858\n", + "2020474747475858585858\n", + "2020474747475858585858\n", + "2020474747475858585858\n", + "1616161616165858585858" ], "text/plain": [ "" @@ -878,17 +904,17 @@ "data": { "text/html": [ "\n", - "
2828191919191919191919\n", - "
2828474747475050505050\n", - "
2828474747475050505050\n", - "
2828474747475050505050\n", - "
2828474747475050505050\n", - "
2828474747475050505050\n", - "
2828474747475050505050\n", - "
2828474747475050505050\n", - "
3636363636365050505050\n", - "
3636363636365050505050\n", - "
3636363636365050505050
" + "5050505050363636363636\n", + "5050505050363636363636\n", + "5050505050363636363636\n", + "5050505050474747472828\n", + "5050505050474747472828\n", + "5050505050474747472828\n", + "5050505050474747472828\n", + "5050505050474747472828\n", + "5050505050474747472828\n", + "5050505050474747472828\n", + "1919191919191919192828" ], "text/plain": [ "" @@ -902,7 +928,7 @@ "def pack_square(rectangles):\n", " \"Pack rectangles into a square of appropriate size, if possible.\"\n", " A = total_area(rectangles)\n", - " if A in perfect_squares:\n", + " if is_perfect_square(A):\n", " return pack(rectangles, Square(int(A ** 0.5)))\n", "\n", "for s in all_sets:\n", @@ -916,88 +942,6 @@ "So there are 4 sets of rectangles that work." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Animated Colored Rectangles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It is gratifying to see the final results, and have the computation be so fast, but I'd like to get a better understanding of the process of finding the results. I can try to visualize the process by *animating* the search that `pack` does: every time we successfully call `place_rectangle_at`, I can show the new grid, and then pause for a short period. I accomplish this by modifying `pack` to take an optional argument, `animation`, which when false gives the same behavior as before, but when it is a number, pauses for that number of seconds in between displaying each step of the packing." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "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. Pause `animation` seconds between \n", - " displaying each rectangle placement if `animation` is not false.\"\"\"\n", - " if animation: \n", - " clear_output()\n", - " show(grid)\n", - " time.sleep(animation) \n", - " if not rectangles:\n", - " return grid \n", - " pos = first_empty_cell(grid)\n", - " if grid and pos:\n", - " for (rectangles2, grid2) in rectangle_placements(rectangles, grid, pos): \n", - " solution = pack(rectangles2, grid2, animation)\n", - " if solution:\n", - " return solution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you are running this in a live IPython notebook (not in nbviewer), you can see for yourself by re-running this cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "
37373750505050505050505050\n", - "
37373750505050505050505050\n", - "
37373750505050505050505050\n", - "
37373750505050505050505050\n", - "
37373750505050505050505050\n", - "
37373712898989898989898989\n", - "
37373712898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989\n", - "
46464646898989898989898989
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "pack({(1, 2), (3, 7), (4, 6), (5, 10), (8, 9)}, Square(13), 1);" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -1032,8 +976,10 @@ }, { "cell_type": "code", - "execution_count": 28, - "metadata": {}, + "execution_count": 29, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "from __future__ import division\n", @@ -1067,16 +1013,18 @@ }, { "cell_type": "code", - "execution_count": 29, - "metadata": {}, + "execution_count": 30, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/plain": [ - "'325 + 764 == 1089'" + "'352 + 746 == 1098'" ] }, - "execution_count": 29, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -1087,15 +1035,17 @@ }, { "cell_type": "code", - "execution_count": 30, - "metadata": {}, + "execution_count": 31, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 30 s, sys: 56.5 ms, total: 30 s\n", - "Wall time: 30.1 s\n" + "CPU times: user 41.4 s, sys: 452 ms, total: 41.9 s\n", + "Wall time: 44.2 s\n" ] }, { @@ -1104,7 +1054,7 @@ "96" ] }, - "execution_count": 30, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -1135,8 +1085,10 @@ }, { "cell_type": "code", - "execution_count": 31, - "metadata": {}, + "execution_count": 32, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def compile_formula(formula, verbose=False):\n", @@ -1164,23 +1116,25 @@ }, { "cell_type": "code", - "execution_count": 32, - "metadata": {}, + "execution_count": 33, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "lambda M,U,E,O,Y: M and Y and ((100*Y+10*O+U) == (10*M+E)**2)\n" + "lambda O,Y,E,U,M: M and Y and ((100*Y+10*O+U) == (10*M+E)**2)\n" ] }, { "data": { "text/plain": [ - "(>, 'MUEOY')" + "(>, 'OYEUM')" ] }, - "execution_count": 32, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -1191,23 +1145,25 @@ }, { "cell_type": "code", - "execution_count": 33, - "metadata": {}, + "execution_count": 34, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "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" + "lambda M,L,P,R,Y,A,E,N,U,B: P and B and N and ((100*N+10*U+M) + (100*B+10*E+R) == (1000*P+100*L+10*A+Y))\n" ] }, { "data": { "text/plain": [ - "(>, 'UARELYNBMP')" + "(>, 'MLPRYAENUB')" ] }, - "execution_count": 33, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1218,15 +1174,16 @@ }, { "cell_type": "code", - "execution_count": 34, - "metadata": {}, + "execution_count": 35, + "metadata": { + "collapsed": true + }, "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; \n", - " only one eval per formula.\"\"\"\n", + " Capital letters are variables. This version precompiles the formula; 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", @@ -1236,7 +1193,7 @@ " pass\n", " \n", "def replace_all(text, olds, news):\n", - " \"Replace each occurence of each old in text with the corresponding new.\"\n", + " \"Replace each occurrence 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", @@ -1245,16 +1202,18 @@ }, { "cell_type": "code", - "execution_count": 35, - "metadata": {}, + "execution_count": 36, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/plain": [ - "'325 + 764 = 1089'" + "'352 + 746 = 1098'" ] }, - "execution_count": 35, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -1265,15 +1224,17 @@ }, { "cell_type": "code", - "execution_count": 36, - "metadata": {}, + "execution_count": 37, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 1.77 s, sys: 4.05 ms, total: 1.77 s\n", - "Wall time: 1.77 s\n" + "CPU times: user 2.57 s, sys: 31.5 ms, total: 2.6 s\n", + "Wall time: 2.7 s\n" ] }, { @@ -1282,7 +1243,7 @@ "96" ] }, - "execution_count": 36, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -1315,7 +1276,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.3" + "version": "3.6.0" } }, "nbformat": 4,