Compare commits
7 Commits
0c0d4b2586
...
d5d31f932d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5d31f932d | ||
|
|
1ae17ba5b6 | ||
|
|
f26e74abb6 | ||
|
|
334a041609 | ||
|
|
0b9c561a5f | ||
|
|
48345cd9d6 | ||
|
|
44926e9569 |
44
README.md
44
README.md
@@ -26,14 +26,17 @@ For each notebook you can hover or click on the title, or click one of the lette
|
||||
|
||||
|
||||
|
||||
|Run|Year|Recent (2022)|
|
||||
|Run|Year|Recent (2022–2024)|
|
||||
|---|---|---|
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/AlphaCode.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAlphaCode.ipynb)[G](ipynb/AlphaCode.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAlphaCode.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/AlphaCode.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/AlphaCode.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/AlphaCode.ipynb" title="Analysis of AlphaCode's automated solution to a coding problem">AlphaCode Automated Programming</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FOneLetterOff.ipynb)[G](ipynb/OneLetterOff.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FOneLetterOff.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb) | 2023 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb" title="Word game; use of a large language model to generate clues.">One Letter Off</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FMenu.ipynb)[G](ipynb/Menu.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FMenu.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb" title="Efficiently Selecting Names from a Menu, by typing characters and arrows">Selecting Names from a Menu</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2023.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2023.ipynb)[G](ipynb/Advent-2023.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2023.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2023.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2023.ipynb) | 2023 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2023.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2022">Advent of Code 2023</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2022.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2022.ipynb)[G](ipynb/Advent-2022.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2022.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2022.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2022.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2022.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2022">Advent of Code 2022</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/AdventUtils.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdventUtils.ipynb)[G](ipynb/AdventUtils.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdventUtils.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/AdventUtils.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/AdventUtils.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/AdventUtils.ipynb" title="Utility functions for Advent of Code puzzles">Advent of Code Utilities</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Wordle.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FWordle.ipynb)[G](ipynb/Wordle.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FWordle.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Wordle.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Wordle.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Wordle.ipynb" title="A simple human-usable strategy to always win at Wordle. And an analysis of 2-guess wins">Winning Wordle</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Overtime.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FOvertime.ipynb)[G](ipynb/Overtime.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FOvertime.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Overtime.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Overtime.ipynb) | 2023 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Overtime.ipynb" title="In American Football, which team has the advantage in overtime?">Overtime in American Football</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FGoldberg.ipynb)[G](ipynb/Goldberg.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FGoldberg.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb" title="A re-implementation in Python 3 of Yoav Goldberg's unreasonably effective character-level n-gram language model.">Goldberg's Character-level Language Model</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Wordle.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FWordle.ipynb)[G](ipynb/Wordle.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FWordle.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Wordle.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Wordle.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Wordle.ipynb" title="A simple human-usable strategy to always win at Wordle. And an analysis of 2-guess wins">Winning Wordle</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Anigrams.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAnigrams.ipynb)[G](ipynb/Anigrams.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAnigrams.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Anigrams.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Anigrams.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Anigrams.ipynb" title="Finding the longest chain of anagrams that each add one letter">Anigrams: Word Chains</a> |
|
||||
|
||||
|
||||
@@ -47,24 +50,41 @@ For each notebook you can hover or click on the title, or click one of the lette
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Life.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FLife.ipynb)[G](ipynb/Life.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FLife.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Life.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Life.ipynb) | 2017 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Life.ipynb" title="The cellular automata zero-player game">Conway's Game of Life</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Maze.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FMaze.ipynb)[G](ipynb/Maze.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FMaze.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Maze.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Maze.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Maze.ipynb" title="Make a maze by generating a random tree superimposed on a grid and solve it">Generating and Solving Mazes</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Konane.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FKonane.ipynb)[G](ipynb/Konane.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FKonane.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Konane.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Konane.ipynb) | 2021 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Konane.ipynb" title="Solving the game of Konane (Hawaiian checkers).">Mel's Konane Board</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FMenu.ipynb)[G](ipynb/Menu.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FMenu.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb" title="Efficiently Selecting Names from a Menu, by typing characters and arrows">Selecting Names from a Menu</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FOneLetterOff.ipynb)[G](ipynb/OneLetterOff.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FOneLetterOff.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb) | 2023 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb" title="Word game; use of a large language model to generate clues.">One Letter Off</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/PhotoFocalLengths.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FPhotoFocalLengths.ipynb)[G](ipynb/PhotoFocalLengths.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FPhotoFocalLengths.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/PhotoFocalLengths.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/PhotoFocalLengths.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/PhotoFocalLengths.ipynb" title="Generate charts of what focal lengths were used on a photo trip">Photo Focal Lengths</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Pickleball.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FPickleball.ipynb)[G](ipynb/Pickleball.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FPickleball.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Pickleball.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Pickleball.ipynb) | 2018 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Pickleball.ipynb" title="Scheduling a doubles tournament fairly and efficiently">Pickleball Tournament</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Project%20Euler%20Utils.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FProject%20Euler%20Utils.ipynb)[G](ipynb/Project%20Euler%20Utils.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FProject%20Euler%20Utils.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Project%20Euler%20Utils.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Project%20Euler%20Utils.ipynb) | 2017 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Project%20Euler%20Utils.ipynb" title="My utility functions for the Project Euler problems, including `Primes` and `Factors`">Project Euler Utilities</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FMenu.ipynb)[G](ipynb/Menu.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FMenu.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Menu.ipynb" title="Efficiently Selecting Names from a Menu, by typing characters and arrows">Selecting Names from a Menu</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Electoral%20Votes.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FElectoral%20Votes.ipynb)[G](ipynb/Electoral%20Votes.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FElectoral%20Votes.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Electoral%20Votes.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Electoral%20Votes.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Electoral%20Votes.ipynb" title="How many electoral votes would Trump get if he wins the state where he has positive net approval?">Tracking Trump: Electoral Votes</a> |
|
||||
|
||||
|
||||
|Run|Year|Advent of Code|
|
||||
|---|---|---|
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2023.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2023.ipynb)[G](ipynb/Advent-2023.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2023.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2023.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2023.ipynb) | 2023 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2023.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2022">Advent of Code 2023</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2022.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2022.ipynb)[G](ipynb/Advent-2022.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2022.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2022.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2022.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2022.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2022">Advent of Code 2022</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2021.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2021.ipynb)[G](ipynb/Advent-2021.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2021.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2021.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2021.ipynb) | 2021 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2021.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2021">Advent of Code 2021</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2020.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2020.ipynb)[G](ipynb/Advent-2020.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2020.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2020.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2020.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2020.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2020">Advent of Code 2020</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2018.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2018.ipynb)[G](ipynb/Advent-2018.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2018.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2018.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2018.ipynb) | 2018 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2018.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2018">Advent of Code 2018</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent%202017.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent%202017.ipynb)[G](ipynb/Advent%202017.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent%202017.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent%202017.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent%202017.ipynb) | 2017 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent%202017.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2017">Advent of Code 2017</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent%20of%20Code.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent%20of%20Code.ipynb)[G](ipynb/Advent%20of%20Code.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent%20of%20Code.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent%20of%20Code.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent%20of%20Code.ipynb) | 2016 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent%20of%20Code.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2016">Advent of Code 2016</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2017.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2017.ipynb)[G](ipynb/Advent-2017.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2017.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2017.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2017.ipynb) | 2017 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2017.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2017">Advent of Code 2017</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2016.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdvent-2016.ipynb)[G](ipynb/Advent-2016.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdvent-2016.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2016.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Advent-2016.ipynb) | 2016 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2016.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2016">Advent of Code 2016</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/AdventUtils.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAdventUtils.ipynb)[G](ipynb/AdventUtils.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAdventUtils.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/AdventUtils.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/AdventUtils.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/AdventUtils.ipynb" title="Utility functions for Advent of Code puzzles">Advent of Code Utilities</a> |
|
||||
|
||||
|
||||
|Run|Year|Probability and Uncertainty|
|
||||
|---|---|---|
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Overtime.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FOvertime.ipynb)[G](ipynb/Overtime.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FOvertime.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Overtime.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Overtime.ipynb) | 2023 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Overtime.ipynb" title="In American Football, which team has the advantage in overtime?">Overtime in American Football</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FGoldberg.ipynb)[G](ipynb/Goldberg.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FGoldberg.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb" title="A re-implementation in Python 3 of Yoav Goldberg's unreasonably effective character-level n-gram language model.">Goldberg's Character-level Language Model</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Probability.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FProbability.ipynb)[G](ipynb/Probability.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FProbability.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Probability.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Probability.ipynb) | 2018 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Probability.ipynb" title="Code and examples of the basic principles of Probability Theory">A Concrete Introduction to Probability</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/ProbabilityParadox.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FProbabilityParadox.ipynb)[G](ipynb/ProbabilityParadox.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FProbabilityParadox.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/ProbabilityParadox.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/ProbabilityParadox.ipynb) | 2016 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/ProbabilityParadox.ipynb" title="Some classic paradoxes in Probability Theory, and how to think about disagreements">Probability, Paradox, and the Reasonable Person Principle</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/ProbabilitySimulation.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FProbabilitySimulation.ipynb)[G](ipynb/ProbabilitySimulation.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FProbabilitySimulation.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/ProbabilitySimulation.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/ProbabilitySimulation.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/ProbabilitySimulation.ipynb" title="When the sample space is too complex, simulations can estimate probabilities">Estimating Probabilities with Simulations</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Coin%20Flip.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FCoin%20Flip.ipynb)[G](ipynb/Coin%20Flip.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FCoin%20Flip.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Coin%20Flip.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Coin%20Flip.ipynb) | 2019 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Coin%20Flip.ipynb" title="How to beat the Devil at his own game">The Devil and the Coin Flip Game</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Dice%20Baseball.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FDice%20Baseball.ipynb)[G](ipynb/Dice%20Baseball.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FDice%20Baseball.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Dice%20Baseball.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Dice%20Baseball.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Dice%20Baseball.ipynb" title="Simulating baseball games">Dice Baseball</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Economics.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FEconomics.ipynb)[G](ipynb/Economics.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FEconomics.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Economics.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Economics.ipynb) | 2018 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Economics.ipynb" title="A simulation of a simple economic game">Economics Simulation</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/poker.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2Fpoker.ipynb)[G](ipynb/poker.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2Fpoker.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/poker.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/poker.ipynb) | 2012 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/poker.ipynb" title="How do we decide which poker hand wins? Several variants of poker are considered">Poker Hand Ranking</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/risk.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2Frisk.ipynb)[G](ipynb/risk.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2Frisk.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/risk.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/risk.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/risk.ipynb" title="Determining who is likely to win an interminably long game of Risk">The Unfinished Game .... of Risk</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/WWW.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FWWW.ipynb)[G](ipynb/WWW.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FWWW.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/WWW.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/WWW.ipynb) | 2019 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/WWW.ipynb" title="Computing the probability of winning the NBA title, for my home town Warriors, or any other team">WWW: Who Will Win (NBA Title)?</a> |
|
||||
|
||||
|
||||
|Run|Year|Logic and Number Puzzles|
|
||||
|---|---|---|
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Cryptarithmetic.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FCryptarithmetic.ipynb)[G](ipynb/Cryptarithmetic.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FCryptarithmetic.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Cryptarithmetic.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Cryptarithmetic.ipynb) | 2014 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Cryptarithmetic.ipynb" title="Substitute digits for letters and make NUM + BER = PLAY">Cryptarithmetic</a> |
|
||||
@@ -103,20 +123,6 @@ For each notebook you can hover or click on the title, or click one of the lette
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/xkcd-Name-Dominoes.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2Fxkcd-Name-Dominoes.ipynb)[G](ipynb/xkcd-Name-Dominoes.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2Fxkcd-Name-Dominoes.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/xkcd-Name-Dominoes.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/xkcd-Name-Dominoes.ipynb) | 2018 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/xkcd-Name-Dominoes.ipynb" title="Lay out dominoes legally; the dominoes have people names, not numbers">xkcd 1970: Name Dominoes</a> |
|
||||
|
||||
|
||||
|Run|Year|Probability and Uncertainty|
|
||||
|---|---|---|
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FGoldberg.ipynb)[G](ipynb/Goldberg.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FGoldberg.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb" title="A re-implementation in Python 3 of Yoav Goldberg's unreasonably effective character-level n-gram language model.">Goldberg's Character-level Language Model</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Probability.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FProbability.ipynb)[G](ipynb/Probability.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FProbability.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Probability.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Probability.ipynb) | 2018 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Probability.ipynb" title="Code and examples of the basic principles of Probability Theory">A Concrete Introduction to Probability</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/ProbabilityParadox.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FProbabilityParadox.ipynb)[G](ipynb/ProbabilityParadox.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FProbabilityParadox.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/ProbabilityParadox.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/ProbabilityParadox.ipynb) | 2016 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/ProbabilityParadox.ipynb" title="Some classic paradoxes in Probability Theory, and how to think about disagreements">Probability, Paradox, and the Reasonable Person Principle</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/ProbabilitySimulation.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FProbabilitySimulation.ipynb)[G](ipynb/ProbabilitySimulation.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FProbabilitySimulation.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/ProbabilitySimulation.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/ProbabilitySimulation.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/ProbabilitySimulation.ipynb" title="When the sample space is too complex, simulations can estimate probabilities">Estimating Probabilities with Simulations</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Coin%20Flip.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FCoin%20Flip.ipynb)[G](ipynb/Coin%20Flip.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FCoin%20Flip.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Coin%20Flip.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Coin%20Flip.ipynb) | 2019 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Coin%20Flip.ipynb" title="How to beat the Devil at his own game">The Devil and the Coin Flip Game</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Dice%20Baseball.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FDice%20Baseball.ipynb)[G](ipynb/Dice%20Baseball.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FDice%20Baseball.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Dice%20Baseball.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Dice%20Baseball.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Dice%20Baseball.ipynb" title="Simulating baseball games">Dice Baseball</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Economics.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FEconomics.ipynb)[G](ipynb/Economics.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FEconomics.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Economics.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Economics.ipynb) | 2018 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Economics.ipynb" title="A simulation of a simple economic game">Economics Simulation</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/poker.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2Fpoker.ipynb)[G](ipynb/poker.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2Fpoker.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/poker.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/poker.ipynb) | 2012 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/poker.ipynb" title="How do we decide which poker hand wins? Several variants of poker are considered">Poker Hand Ranking</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/risk.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2Frisk.ipynb)[G](ipynb/risk.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2Frisk.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/risk.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/risk.ipynb) | 2020 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/risk.ipynb" title="Determining who is likely to win an interminably long game of Risk">The Unfinished Game .... of Risk</a> |
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/WWW.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FWWW.ipynb)[G](ipynb/WWW.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FWWW.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/WWW.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/WWW.ipynb) | 2019 | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/WWW.ipynb" title="Computing the probability of winning the NBA title, for my home town Warriors, or any other team">WWW: Who Will Win (NBA Title)?</a> |
|
||||
|
||||
|
||||
|Run|Year|The Riddler (from 538)|
|
||||
|---|---|---|
|
||||
| [C](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Anigrams.ipynb)[D](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmain%2Fipynb%2FAnigrams.ipynb)[G](ipynb/Anigrams.ipynb)[M](https://mybinder.org/v2/gh/norvig/pytudes/main?filepath=ipynb%2FAnigrams.ipynb)[N](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Anigrams.ipynb)[S](https://studiolab.sagemaker.aws/import/github/norvig/pytudes/blob/main/ipynb/Anigrams.ipynb) | <u>2022</u> | <a href="https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Anigrams.ipynb" title="Finding the longest chain of anagrams that each add one letter">Anigrams: Word Chains</a> |
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 33,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -31,24 +31,24 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Reading Data: `rides` and `yearly`\n",
|
||||
"# Reading Data: `rides`, `yearly`, and `daily`\n",
|
||||
"\n",
|
||||
"I saved a bunch of my recorded [Strava](https://www.strava.com/athletes/575579) rides, most of them longer than 25 miles, as [`bikerides.tsv`](bikerides.tsv). The columns are: the date; the year; a title; the elapsed time of the ride; the length of the ride in miles; and the total climbing in feet, e.g.: \n",
|
||||
"I saved a bunch of my recorded [Strava](https://www.strava.com/athletes/575579) rides, most of them longer than 25 miles, as [`bikerides.tsv`](bikerides.tsv). The tab-separated columns are: the date; the year; a title; the elapsed time of the ride; the length of the ride in miles; and the total climbing in feet, e.g.: \n",
|
||||
"\n",
|
||||
" Mon, 10/5\t2020\tHalf way around the bay on bay trail\t6:26:35\t80.05\t541\n",
|
||||
" Mon, 10/5/2020\tHalf way around the bay on bay trail\t6:26:35\t80.05\t541\n",
|
||||
" \n",
|
||||
"I parse the file into the pandas dataframe `rides`, adding derived columns for miles per hour, vertical meters climbed per hour (VAM), grade in feet per mile, grade in percent, and kilometers ridden:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 119,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def parse_rides(lines):\n",
|
||||
" \"\"\"Parse a bikerides.tsv file.\"\"\"\n",
|
||||
" return drop_index(add_columns(pd.read_table(lines, comment='#',\n",
|
||||
" return drop_index(add_ride_columns(pd.read_table(lines, comment='#',\n",
|
||||
" converters=dict(hours=parse_hours, feet=parse_int))))\n",
|
||||
"\n",
|
||||
"def parse_hours(time: str) -> float: \n",
|
||||
@@ -57,18 +57,20 @@
|
||||
" for i, x in enumerate(reversed(time.split(':'))))\n",
|
||||
" return round(hrs, 2)\n",
|
||||
"\n",
|
||||
"def parse_int(field: str) -> int: return int(field.replace(',', ''))\n",
|
||||
"def parse_int(field: str) -> int: return int(field.replace(',', '').replace('ft', '').replace('mi', ''))\n",
|
||||
"\n",
|
||||
"def add_columns(rides) -> pd.DataFrame:\n",
|
||||
"def add_ride_columns(rides) -> pd.DataFrame:\n",
|
||||
" \"\"\"Compute new columns from existing ones.\"\"\"\n",
|
||||
" mi, hr, ft = rides['miles'], rides['hours'], rides['feet']\n",
|
||||
" if 'date' in rides and 'year' not in rides:\n",
|
||||
" rides.insert(1, \"year\", [int(str(d).split('/')[-1]) for d in rides['date'].tolist()])\n",
|
||||
" return rides.assign(\n",
|
||||
" mph=round(mi / hr, 2),\n",
|
||||
" vam=round(ft / hr / 3.28084),\n",
|
||||
" fpm=round(ft / mi),\n",
|
||||
" fpmi=round(ft / mi),\n",
|
||||
" pct=round(ft / mi * 100 / 5280, 2),\n",
|
||||
" kms=round(mi * 1.609, 2),\n",
|
||||
" km_up=round(ft * 0.0003048, 1))\n",
|
||||
" meters=round(ft * 0.3048))\n",
|
||||
"\n",
|
||||
"def drop_index(frame) -> pd.DataFrame:\n",
|
||||
" \"\"\"Drop the index column.\"\"\"\n",
|
||||
@@ -78,15 +80,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 65,
|
||||
"execution_count": 125,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"rides = parse_rides(open('bikerides.tsv'))\n",
|
||||
"yearly = parse_rides(open('bikeyears.tsv')).drop(columns=['date', 'title'])\n",
|
||||
"\n",
|
||||
"yearly = parse_rides(open('bikeyears.tsv')).drop(columns='date')\n",
|
||||
"\n",
|
||||
"daily = yearly.copy()\n",
|
||||
"for name in 'hours miles feet kms km_up'.split():\n",
|
||||
" daily[name] = round(daily[name].map(lambda x: x / 350), 3 if name == 'km_up' else 1)"
|
||||
"for name in 'hours miles feet kms meters'.split():\n",
|
||||
" daily[name] = round(daily[name].map(lambda x: x / (6 * 52)), 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -106,7 +110,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 41,
|
||||
"execution_count": 36,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -118,30 +122,12 @@
|
||||
" title, mi, ft, *times = segment.split(',')[:5]\n",
|
||||
" for time in times:\n",
|
||||
" records.append((title, parse_hours(time), float(mi), parse_int(ft)))\n",
|
||||
" return add_columns(pd.DataFrame(records, columns=('title', 'hours', 'miles', 'feet')))"
|
||||
" return add_ride_columns(pd.DataFrame(records, columns=('title', 'hours', 'miles', 'feet')))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"segments = parse_segments(open('bikesegments.csv'))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 43,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"places = pd.read_table(open('bikeplaceshort.csv'), sep=',', comment='#')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 50,
|
||||
"execution_count": 39,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -160,13 +146,20 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 57,
|
||||
"execution_count": 59,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"segments = parse_segments(open('bikesegments.csv'))\n",
|
||||
"\n",
|
||||
"places = drop_index(pd.read_table(open('bikeplaceshort.csv'), sep=',', comment='#'))\n",
|
||||
"\n",
|
||||
"tiles = drop_index(pd.DataFrame(columns='date square cluster total comment'.split(), data=[\n",
|
||||
" ('06/30/2023', 13, 689, 2640, 'Rides in east Bay!9298603815'),\n",
|
||||
" ('04/14/2023', 13, 630, 2595, 'Black Sands Beach connects Marin to max cluster!8891171008'),\n",
|
||||
" ('01/01/2024', 14, 1056, 3105, 'Start of this year'),\n",
|
||||
" ('12/08/2023', 14, 1042, 3084, 'Benicia ride connects East Bay and Napa clusters!10350071201'),\n",
|
||||
" ('11/05/2023', 14, 932, 2914, 'Alum Rock ride gets 14x14 max square!8850905872'),\n",
|
||||
" ('06/30/2023', 13, 689, 2640, 'Rides in east Bay fill in holes!9298603815'),\n",
|
||||
" ('04/14/2023', 13, 630, 2595, 'Black Sands Beach low-tide hike connects Marin to max cluster!8891171008'),\n",
|
||||
" ('03/04/2023', 13, 583, 2574, 'Almaden rides connects Gilroy to max cluster!8654437264'),\n",
|
||||
" ('10/22/2022', 13, 396, 2495, 'Alviso levees to get to 13x13 max square!8003921626'),\n",
|
||||
" ('10/16/2022', 12, 393, 2492, 'Milpitas ride connects East Bay to max cluster!7974994605'),\n",
|
||||
@@ -183,7 +176,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"execution_count": 41,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -216,7 +209,7 @@
|
||||
" \"\"\"Given a ride distance in miles and total climb in feet, estimate time in minutes.\"\"\"\n",
|
||||
" return round(60 * miles / estimator(feet / miles))\n",
|
||||
"\n",
|
||||
"def top(frame, field, n=20): return frame.sort_values(field, ascending=False).head(n)"
|
||||
"def top(frame, field, n=20): return drop_index(frame.sort_values(field, ascending=False).head(n))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -228,23 +221,33 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"execution_count": 101,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def mapl(f, *values): return list(map(f, *values))\n",
|
||||
"\n",
|
||||
"def wandering(places=places, by=['pct']):\n",
|
||||
" \"All those who wander are not lost.\" # Also try by=['cat', 'pct']\n",
|
||||
" frame = places.sort_values(by=by, ascending=('pct' not in by))\n",
|
||||
" M = 1_000_000\n",
|
||||
" for i, (name, miles, county, pct) in frame.iterrows():\n",
|
||||
" # Some fiddling to get the format right\n",
|
||||
" p = f'{pct:.1f}' if (pct > 0.1) else f'{pct:.3f}'\n",
|
||||
" mymiles = pct / 100 * miles\n",
|
||||
" done = f'{rounded(mymiles)}/{rounded(miles)} mi'\n",
|
||||
" togo = next((f'{rounded(target / 100 * miles - mymiles):>5} mi for {target}%' \n",
|
||||
" for target in (0.02, 0.1, 0.2, 1, 2, 25, 50, 90, 99)\n",
|
||||
" if mymiles < target / 100 * miles), '')\n",
|
||||
" print(f'{county} {p:>5}% {name:25} {done:>15} {togo}') \n",
|
||||
" F = drop_index(places.sort_values(by=by, ascending=('pct' not in by)))\n",
|
||||
" pd.set_option('display.max_rows', None)\n",
|
||||
" return pd.DataFrame(\n",
|
||||
" {'pct': [f'{p:.1f}%' if (p > 1) else f'{p:.3f}%' for p in F['pct']],\n",
|
||||
" 'county': F['county'],\n",
|
||||
" 'name': F['name'],\n",
|
||||
" 'total': F['miles'],\n",
|
||||
" 'done': mapl(rounded, F['miles'] * F['pct'] / 100),\n",
|
||||
" 'to next badge': mapl(to_go, F['pct'], F['miles'])})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def to_go(pct, miles, targets=(0.02, 0.1, 0.2, 1, 2, 25, 50, 90, 99)):\n",
|
||||
" \"\"\"Describe next target to hit to get a badge.\"\"\"\n",
|
||||
" done = pct * miles / 100\n",
|
||||
" return next((f'{rounded(target / 100 * miles - done):>5} mi to {target}%' \n",
|
||||
" for target in targets\n",
|
||||
" if done < target / 100 * miles), \n",
|
||||
" '')\n",
|
||||
" \n",
|
||||
"def rounded(x: float) -> str: \n",
|
||||
" \"\"\"Round x to 3 spaces wide (if possible).\"\"\"\n",
|
||||
@@ -263,32 +266,36 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"execution_count": 44,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def make_leaders(data):\n",
|
||||
" \"\"\"Make a dataframe of leaders in two counties.\"\"\"\n",
|
||||
" leaders = pd.DataFrame(data, columns=['Name', 'Initials', 'SMC %', 'SCC %', 'Front?'])\n",
|
||||
" leaders = pd.DataFrame(data, columns=['Name', 'Initials', 'SMC %', 'SCC %'])\n",
|
||||
" leaders['SMC miles'] = [round(2814 * d[2] / 100) for d in data]\n",
|
||||
" leaders['SCC miles'] = [round(7569 * d[3] / 100) for d in data]\n",
|
||||
" leaders['Total miles'] = leaders['SMC miles'] + leaders['SCC miles']\n",
|
||||
" leaders['Avg %'] = (leaders['SMC %'] + leaders['SCC %']) / 2\n",
|
||||
" return drop_index(leaders.sort_values('Avg %', ascending=False))\n",
|
||||
"\n",
|
||||
"leaders = make_leaders([ # Data as of Sept 20, 2023 (Name, Initials, SMC, SCC, Frontier?)\n",
|
||||
" ('Barry Mann', 'BM', 76.97, 30.21, 1), ('Jason Molenda', 'JM', 7.13, 55.39, 1), \n",
|
||||
" ('Peter Norvig', 'PN', 61.56, 32.8, 1), ('Brian Feinberg', 'BF', 32.5, 43.68, 1),\n",
|
||||
" ('Jim Brooks', 'JB', 4.23, 44.36, 0), ('Megan Gardner', 'MG', 97.62, 8.69, 1),\n",
|
||||
" ('Matthew Ring', 'MR', 78.85, 1.48, 0), ('Elliot Hoff', 'EF', 52.88, 8.14, 0)])\n",
|
||||
"leaders = make_leaders([ # Data as of Jan 3, 2024 (Name, Initials, SMC, SCC)\n",
|
||||
" ('Megan Gardner', 'MG', 99.01, 13.6),\n",
|
||||
" ('Barry Mann', 'BM', 77.41, 30.38), \n",
|
||||
" ('Peter Norvig', 'PN', 63.5, 33.0),\n",
|
||||
" ('Brian Feinberg', 'BF', 32.5, 43.9),\n",
|
||||
" ('Jason Molenda', 'JM', 7.56, 56.25) \n",
|
||||
" ])\n",
|
||||
" \n",
|
||||
"def pareto_front(leaders):\n",
|
||||
" ax = leaders.plot('SMC %', 'SCC %', grid=True, kind='scatter')\n",
|
||||
" front = sorted((x, y) for i, (_, _, x, y, f, *_) in leaders.iterrows() if f)\n",
|
||||
" ax = leaders.plot('SMC %', 'SCC %', kind='scatter')\n",
|
||||
" front = sorted((x, y) for i, (_, _, x, y, *_) in leaders.iterrows())\n",
|
||||
" ax.plot(*zip(*front), ':'); ax.axis('square'); grid()\n",
|
||||
" ax.set_xlabel('San Mateo County %')\n",
|
||||
" ax.set_ylabel('Santa Clara County %')\n",
|
||||
" for i, (name, initials, x, y, *_) in leaders.iterrows():\n",
|
||||
" ax.text(x - 2, y + 2, initials)\n",
|
||||
" return leaders.drop(columns=['Front?'])"
|
||||
" return leaders"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -300,7 +307,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"execution_count": 45,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -314,16 +321,14 @@
|
||||
" \"\"\"The number of rides needed to reach an Eddington number target.\"\"\"\n",
|
||||
" return target - sum(distances >= target)\n",
|
||||
"\n",
|
||||
"def Ed_gaps(rides, E_km=100, E_mi=67, N=11) -> dict:\n",
|
||||
"def Ed_gaps(rides, E_km=103, E_mi=69, N=9) -> dict:\n",
|
||||
" \"\"\"A table of gaps to Eddington numbers by year.\"\"\"\n",
|
||||
" data = [(E_km + d, sum(rides.kms >= E_km + d), Ed_gap(rides.kms, E_km + d), \n",
|
||||
" E_mi + d, sum(rides.miles >= E_mi + d), Ed_gap(rides.miles, E_mi + d))\n",
|
||||
" data = [(E_km + d, Ed_gap(rides.kms, E_km + d), E_mi + d, Ed_gap(rides.miles, E_mi + d))\n",
|
||||
" for d in range(N)]\n",
|
||||
" df = pd.DataFrame(data, columns=['kms', 'km rides', 'kms gap', \n",
|
||||
" 'miles', 'miles rides', 'miles gap'])\n",
|
||||
" df = pd.DataFrame(data, columns=['kms', 'kms gap', 'miles', 'miles gap'])\n",
|
||||
" return drop_index(df)\n",
|
||||
"\n",
|
||||
"def Ed_progress(rides, years=range(2023, 2013, -1)) -> pd.DataFrame:\n",
|
||||
"def Ed_progress(rides, years=range(2024, 2013, -1)) -> pd.DataFrame:\n",
|
||||
" \"\"\"A table of Eddington numbers by year, and a plot.\"\"\"\n",
|
||||
" def Ed(year, unit): return Ed_number(rides[rides['year'] <= year], unit)\n",
|
||||
" data = [(y, Ed(y, 'kms'), Ed(y, 'miles')) for y in years]\n",
|
||||
|
||||
2395
ipynb/Goldberg.ipynb
2395
ipynb/Goldberg.ipynb
File diff suppressed because it is too large
Load Diff
978
ipynb/Overtime.ipynb
Normal file
978
ipynb/Overtime.ipynb
Normal file
@@ -0,0 +1,978 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "13d72a29-e929-4b88-8d4f-6db3154dec12",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"text-align: right\" align=\"right\"><i>Peter Norvig<br> Feb 2024</i></div> \n",
|
||||
"\n",
|
||||
"# Probabilities for Overtime in the Super Bowl\n",
|
||||
"\n",
|
||||
"In American football, if the game is tied at the end of 60 minutes of play, an overtime period commences, with these rules:\n",
|
||||
"\n",
|
||||
"- There is a coin toss; the team that guesses right has the option of possessing the ball first or second.\n",
|
||||
"- Both teams get one possession to attempt to score points.\n",
|
||||
"- After those two possessions, if the score is still tied, the game continues and the next score wins the game.\n",
|
||||
"\n",
|
||||
"A recap of (most of) the scoring rules:\n",
|
||||
"\n",
|
||||
"- A team scores **3** points for kicking a **field goal**. \n",
|
||||
"- A team scores **6** points for a **touchdown**. \n",
|
||||
"- After scoring a touchdown, if the game is not over, a team has the option of trying for extra point(s): either **one** point (easy) or **two** points (harder).\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"The 2024 Super Bowl went into overtime, and there was some criticism of San Francisco 49ers coach Kyle Shanahan, who, after winning the coin toss, elected to possess the ball first rather than second. As it turned out the 49ers scored a field goal and then the Chiefs scored a touchdown to win. If the 49ers had taken the ball second, and they had known that the Chiefs scored a touchdown, they could have gone for their own touchdown rather than the field goal, and perhaps tied or won the game. The first question is: Is it better to possess the ball first (team **A**) or second (team **B**)? There are two main points to consider:\n",
|
||||
"\n",
|
||||
"- **B** has the advantage of knowing **B**'s score on their first possession.\n",
|
||||
"- **A** has the advantage that if the score is tied after each team has their first possession, **A** gets the ball next (third), and next score wins.\n",
|
||||
"\n",
|
||||
"The second question is: if a team scores a touchdown on their first possession, what should their strategy be for the extra point(s)? It sems like **A** should go for 1, because if they go for 2 and miss, then it is too easy for **B** to make a 1-point conversion and win. On the other hand, it seems that **B** should go for 2, because if they tie the score at 7-7, then it is too easy for **A** to score next and win.\n",
|
||||
"\n",
|
||||
"In this notebook I do a simulation to answer these two questions.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Code to Simulate One Random Overtime Game\n",
|
||||
"\n",
|
||||
"Calling the function `overtime()` below runs a random simulation and returns a tuple of the two scores for team **A** and team **B**. Optionally, you can set certain probability parameters (they will be the same for both teams):\n",
|
||||
"- **TD**: the probability of scoring a touchdown on a given possession, for both teams. (About 20%, by league average in recent years.)\n",
|
||||
"- **FG**: the probability of scoring a field goal, for both teams. (About 25%.)\n",
|
||||
"- **go**: the *additional* probability of scoring a touchdown for a team that must score a touchdown or lose–i.e., if team **A** scores a touchdown on their first possession, team **B** would never kick a field goal (or punt), in those situations they will instead go for a touchdown, which they get with probability **go**. (Set at 10%, but I'm not sure if that is a good estimate.)\n",
|
||||
"- **one**: the probability of succesfully kicking a 1-point conversion attempt. (About 98%.)\n",
|
||||
"- **two**: the probability of succesfully scoring on a 2-point conversion attempt. (About 48%.)\n",
|
||||
"- **A_extra**: 1 or 2, denoting what **A** should try for if they score a touchdown on their first possession.\n",
|
||||
"- **B_extra**: 1 or 2, denoting what **B** should try for if **A** scored 7 points on their first possession.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "890a789e-719f-4ca2-aa9e-f5384d7dfd80",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"from collections import Counter\n",
|
||||
"Prob = float # The type for a probability, a number between 0 and 1.\n",
|
||||
"\n",
|
||||
"def overtime(TD=0.20, FG=0.25, go=0.10, one=0.98, two=0.48, A_extra=1, B_extra=2) -> tuple:\n",
|
||||
" \"\"\"Given probabilities, play a random overtime and return (team_A_score, team_B_score).\n",
|
||||
" Both teams have same probabilities:\n",
|
||||
" TD: probability of scoring a touchdown on a 'normal' possession. \n",
|
||||
" FG: probability of scoring a field goal on a 'normal' possession.\n",
|
||||
" go: additional probability of scoring a touchdown, if you resolve not to kick.\n",
|
||||
" one: probability of making a one-point conversion.\n",
|
||||
" two: probability of making a two-point conversion.\n",
|
||||
" A_extra: what team A goes for on the extra point.\n",
|
||||
" B_extra: what team B goes for on the extra point (when behind by 1).\"\"\"\n",
|
||||
" A = B = 0 # The scores of the two teams\n",
|
||||
" possession = 1 # The number of possessions for each team\n",
|
||||
" while A == B:\n",
|
||||
" extra = (0 if possession > 1 else P(one, 1) if A_extra == 1 else P(two, 2))\n",
|
||||
" A += score(TD, FG, extra)\n",
|
||||
" if possession == 1 or A == B: # B gets a chance on their first possession, or if it is still tied.\n",
|
||||
" extra = (0 if B + 6 > A else P(one, 1) if B + 6 == A or (B_extra == 1 and B + 7 == A) else P(two, 2))\n",
|
||||
" B += (score(TD + go, 0, extra) if A - B > 3 # Must go for TD if behind by more than 3\n",
|
||||
" else \n",
|
||||
" score(TD, FG, 0))\n",
|
||||
" possession += 1\n",
|
||||
" return A, B\n",
|
||||
"\n",
|
||||
"def score(TD: Prob, FG: Prob, extra: int) -> int: \n",
|
||||
" \"\"\"Randomly simulate a score, given probabilities for TD and FG, and given the number of extra points.\"\"\"\n",
|
||||
" return P(TD, 6 + extra) or P(FG / (1 - TD), 3)\n",
|
||||
"\n",
|
||||
"def P(p: Prob, points: int) -> int: \n",
|
||||
" \"\"\"Return `points` with probability `p`, else 0.\"\"\"\n",
|
||||
" return points if random.random() < p else 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cecd1023-957e-449b-8810-d34fcbe3ea33",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's play a random overtime game and see the scores of the two teams:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "3ca41256-ef10-40f9-9d39-c4e12a4b70b9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0, 3)"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"overtime()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c78dd400-6ae7-410c-8494-7b74f9bb45aa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Code to Simulate a Million Games, and Draw Conclusions\n",
|
||||
"\n",
|
||||
"OK, but that's just one game. What if we play a million games?"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "b8c62d41-0bd3-4483-ba84-7a084b05510a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[((3, 0), 245539),\n",
|
||||
" ((0, 3), 196710),\n",
|
||||
" ((0, 6), 157459),\n",
|
||||
" ((7, 0), 137498),\n",
|
||||
" ((6, 0), 89649),\n",
|
||||
" ((3, 6), 63006),\n",
|
||||
" ((7, 6), 30297),\n",
|
||||
" ((7, 8), 28557),\n",
|
||||
" ((6, 3), 22473),\n",
|
||||
" ((9, 3), 17667),\n",
|
||||
" ((3, 9), 9921),\n",
|
||||
" ((6, 7), 1206),\n",
|
||||
" ((12, 6), 6),\n",
|
||||
" ((9, 6), 5),\n",
|
||||
" ((6, 12), 4),\n",
|
||||
" ((6, 9), 3)]"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"Counter(overtime() for _ in range(1000_000)).most_common()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3429fec7-2efe-4ee2-8309-acb8bed6b8a3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"That gives us the range of possible scores and the frequency of each one. Note:\n",
|
||||
"- Field goals are more common than touchdowns, so the most common scores are 3-0 and 0-3.\n",
|
||||
"- 3-0 is more common than 0-3, because 3-0 includes times when **A** scored first and also third (or later, but that's rarer).\n",
|
||||
"- Why is 0-6 more common than 7-0? Because 0-6 includes times when **B** scored second, or fourth (or later).\n",
|
||||
"- Note that the total of 7-0 (**A** scores first) and 6-0 (**A** scores third, or first and misses the kick) is more than the count for 0-6.\n",
|
||||
"- Note that 3-6 is almost 3 times more common than 6-3. Either could result from 3 field goals, but 3-6 could also be **B** scoring a touchdown; 6-3 could never result from **A** scoring a touchdown.\n",
|
||||
"- 6-9, 9-6, 12-6, and 6-12 are all rare scores, because they mean that *both* teams missed a kick.\n",
|
||||
"\n",
|
||||
"That's all interesting, but the question remains:\n",
|
||||
"\n",
|
||||
"## Who Has the Advantage, Team A or Team B?\n",
|
||||
"\n",
|
||||
"We can add up the games in which **A** wins:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "6b73b711-0073-4b40-893b-875a9ae6c8fa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def win_probability(n=100_000, **kwds) -> float:\n",
|
||||
" \"\"\"Probability that first team (team A) wins, in n simulations of overtime(**kwds).\"\"\"\n",
|
||||
" scores = (overtime(**kwds) for _ in range(n))\n",
|
||||
" return sum(A > B for A, B in scores) / n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "926afa2e-e60f-4641-bb18-b2f04c027f2f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.5438"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"win_probability()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c7d4f929-04aa-484e-851d-2a83758d4948",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Using the given scoring percentages, my simulation says team **A** has about a 54% chance of winning. This supports Shanahan's decision. \n",
|
||||
"\n",
|
||||
"However, I don't have confidence that the probability parameter values I chose are reflective of reality, so let's explore a wider range of parameters: "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "be360c09-bb94-4328-b439-4bcef5ff6ef3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"from IPython.display import HTML\n",
|
||||
" \n",
|
||||
"def chart(TDs=(0.15, 0.20, 0.33), \n",
|
||||
" FGs=(0.20, 0.25, 0.33), \n",
|
||||
" gos=(0.05, 0.10, 0.20), \n",
|
||||
" twos=(0, 0.48, 0.60),\n",
|
||||
" n=100_000) -> HTML:\n",
|
||||
" \"\"\"Create a chart of Win percentages for various parameter values.\"\"\"\n",
|
||||
" data = [((win_probability(n=n, TD=TD, FG=FG, go=go)), TD, FG, go, two)\n",
|
||||
" for TD in TDs for FG in FGs for go in gos for two in twos]\n",
|
||||
" df = pd.DataFrame(data, columns=('Win', 'TD', 'FG', 'go', 'two')).sort_values('Win')\n",
|
||||
" print(f'Team A win probability: min: {min(df.Win):.1%}, max: {max(df.Win):.1%}')\n",
|
||||
" return HTML(df.to_html(index=False, formatters={'Win': '{:.1%}'.format}))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "e323e49e-84d7-43f6-99f2-8b9def6a4b0d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Team A win probability: min: 51.3%, max: 55.7%\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th>Win</th>\n",
|
||||
" <th>TD</th>\n",
|
||||
" <th>FG</th>\n",
|
||||
" <th>go</th>\n",
|
||||
" <th>two</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <td>51.3%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>51.4%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>51.6%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>51.7%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>51.7%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>51.9%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>52.2%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>52.6%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>52.7%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.2%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.2%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.2%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.2%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.3%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.3%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.3%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.4%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.4%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.4%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.4%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.5%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.5%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.5%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.7%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.7%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.7%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.7%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.8%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.8%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.9%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>53.9%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.0%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.0%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.0%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.0%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.1%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.1%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.1%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.1%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.1%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.1%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.2%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.2%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.2%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.2%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.3%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.3%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.3%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.3%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.4%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.4%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.5%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.5%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.5%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.5%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.6%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.6%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.6%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.7%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.7%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.7%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.7%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.8%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.8%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.8%</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.8%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>54.9%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.0%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.0%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.0%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.1%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.25</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.1%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.2%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.2%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.3%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.3%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.3%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.10</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.4%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.00</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.4%</td>\n",
|
||||
" <td>0.20</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.4%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.60</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <td>55.7%</td>\n",
|
||||
" <td>0.15</td>\n",
|
||||
" <td>0.33</td>\n",
|
||||
" <td>0.05</td>\n",
|
||||
" <td>0.48</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.HTML object>"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chart()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5773d2a1-642e-4a28-bfe9-b63e37aab2a2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now I feel more confident that across a wide range of parameter values, **A** always has the advantage.\n",
|
||||
"\n",
|
||||
"Another question still remains:\n",
|
||||
"\n",
|
||||
"## What Are the Best Strategies for Extra Points?\n",
|
||||
"\n",
|
||||
"Earlier I claimed that **A** should go for 1 and **B** for 2. That gives us about a 54% win probability for **A**:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "95f231d1-b7d4-4387-8cbf-4ae5cb3f7cdb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.54277"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"win_probability(A_extra=1, B_extra=2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "931acdc1-753a-4b7f-ae68-42c648eefef2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can see that if **A** goes for 2, they will do worse:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "3a374721-3e4e-4185-90f5-4e27cc0de458",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.53674"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"win_probability(A_extra=2, B_extra=2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "009a1a1d-8c58-45ab-8ee2-b597915b523a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can also see that if **B** goes for 1, they will do worse (i.e., **A** will do better):"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "8a24b181-16d3-4c30-84af-25ceb5c55929",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.55175"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"win_probability(A_extra=1, B_extra=1)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"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.8.15"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -8,43 +8,40 @@
|
||||
"\n",
|
||||
"# Solving Any Sudoku Puzzle\n",
|
||||
"\n",
|
||||
"The rules of <a href=\"http://en.wikipedia.org/wiki/Sudoku\">Sudoku</a> are simple and finite: fill in the empty squares so that each column, row, and 3x3 box contains all the digits from 1 to 9:\n",
|
||||
"The rules of <a href=\"http://en.wikipedia.org/wiki/Sudoku\">Sudoku</a> are simple and finite: fill in the empty squares in the puzzle so that each column, row, and 3×3 box contains all the digits from 1 to 9 with no repeats. For example:\n",
|
||||
"\n",
|
||||
"|Puzzle|Solution|\n",
|
||||
"|---|---|\n",
|
||||
"|||\n",
|
||||
"|Puzzle|-|Solution|\n",
|
||||
"|---|--|---|\n",
|
||||
"|| ||\n",
|
||||
"\n",
|
||||
"This notebook develops a program to solve any Sudoku puzzle, in about 100 lines of Python. You can also see:\n",
|
||||
" - The original 2006 version of this notebook, as a [raw HTML page](http://norvig.com/sudoku.html): \n",
|
||||
" - It uses some outdated Python idioms; the Python code in this notebook is more modern.\n",
|
||||
" - Thus, if you read this notebook, you don't need to see the older version.\n",
|
||||
" - However, the disqus comments might be of some interest.\n",
|
||||
" - [A Java program for Sudoku](SudokuJava.ipynb) that solves over 100,000 puzzles per second.\n",
|
||||
" - [A Python program for Star Battle](StarBattle.ipynb), a related fill-in-the-grid puzzle game.\n",
|
||||
"This notebook develops a program to solve any Sudoku puzzle, in about 80 lines of Python. You can also see:\n",
|
||||
" - The [original 2006 version of this program](http://norvig.com/sudoku.html); it has some obsolete Python idioms.\n",
|
||||
" - [A Java program for Sudoku](SudokuJava.ipynb) that is less clear but faster, solving over 100,000 puzzles per second.\n",
|
||||
" - [A Python program for Star Battle](StarBattle.ipynb), a related fill-in-the-grid puzzle.\n",
|
||||
" - [A Python program for KenKen](KenKen.ipynb), a related fill-in-the-grid puzzle.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Sudoku: Implementing Basic Concepts \n",
|
||||
"\n",
|
||||
"Here are the key concepts and the Python implementation choices I made:\n",
|
||||
"Here are the key concepts and the Python implementation choices I made. Types are in Uppercase and constants in lowercase:\n",
|
||||
"\n",
|
||||
"- **Digit**: the digits are the characters `'1'` to `'9'`. \n",
|
||||
"- **Digit set**: when several digits could fill a square, use `'123'` to mean 1, 2, or 3 are possible.\n",
|
||||
"- **Rows**: by convention the 9 rows have labels `'A'` to `'I'` (top to bottom). \n",
|
||||
"- **Columns**: by convention the 9 columns have labels `'1'` to `'9'` (left to right).\n",
|
||||
"- **Boxes**: the 9 boxes are 3×3 squares within the grid (outlined with heavy black lines in the diagram). \n",
|
||||
"- **rows**: by convention the 9 rows have labels `'A'` to `'I'` (top to bottom). \n",
|
||||
"- **columns**: by convention the 9 columns have labels `'1'` to `'9'` (left to right).\n",
|
||||
"- **Square**: a square is named by the concatenation of its row and column labels, e.g. `'A9'` is the upper-rightmost square.\n",
|
||||
" - `squares` is a tuple of all 81 squares.\n",
|
||||
"- **Grid**: a grid of 81 squares is represented as a dict of `{Square: DigitSet}` such as `{'A9': '123', ...}`.\n",
|
||||
" - `Fail` represents a grid that has no solution (e.g. if we guess wrong on the filler for some square).\n",
|
||||
"- **Picture**: for input and output, a string *picture* of the grid is used (details described below).\n",
|
||||
"- **Unit**: a unit is a row, column, or box; each unit is a list of 9 squares. \n",
|
||||
" - `all_units` is a tuple of all 27 units.\n",
|
||||
" - `units[s]` is a tuple of the 3 units that square `s` is a part of.\n",
|
||||
"- **Boxes**: the 9 boxes are 3×3 squares within the grid (outlined with heavy black lines in the diagram). \n",
|
||||
" - `all_boxes` is a list of all 9 boxes\n",
|
||||
"- **Unit**: a unit is a row, column, or box; each unit is a tuple of 9 squares. \n",
|
||||
" - `all_units` is a list of all 27 units.\n",
|
||||
" - `units` is a dict such that `units[s]` is a tuple of the 3 units that square `s` is a part of.\n",
|
||||
"- **Peers**: the squares that share a unit are called peers. \n",
|
||||
" - `peers[s]` is a set of 20 squares that are in some unit with `s`.\n",
|
||||
"- **Solution**: A grid is a valid solution to a puzzle if:\n",
|
||||
" - A square that was filled with a digit in the puzzle has the same digit in the solution.\n",
|
||||
" - Every unit is filled with all nine digits, one to a square, no repeats."
|
||||
" - `peers` is a dict such that `peers[s]` is a set of 20 squares that are in some unit with `s`.\n",
|
||||
"- **None**: If a puzzle has no solution, we represent that with `None`.\n",
|
||||
"- **Picture**: for input and output, a string is used to describe the grid (details described below).\n",
|
||||
"- **Solution**: A grid is a valid solution to a puzzle if every unit is filled with all nine digits, one to a square, with no repeats, and each square that was filled with a digit in the puzzle has the same digit in the solution."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -54,30 +51,30 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import re\n",
|
||||
"\n",
|
||||
"DigitSet = str # e.g. '123'\n",
|
||||
"Square = str # e.g. 'A9'\n",
|
||||
"Picture = str # e.g. \"53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79\"\n",
|
||||
"Grid = dict # E.g. {'A9': '123', ...}, a dict of {Square: DigitSet}\n",
|
||||
"Fail = Grid() # The empty Grid is used to indicate failure to find a solution\n",
|
||||
"from typing import Dict, Optional\n",
|
||||
"\n",
|
||||
"def cross(A, B) -> tuple:\n",
|
||||
" \"Cross product of strings in A and strings in B.\"\n",
|
||||
" return tuple(a + b for a in A for b in B)\n",
|
||||
"\n",
|
||||
"Digit = str # e.g. '1'\n",
|
||||
"digits = '123456789'\n",
|
||||
"DigitSet = str # e.g. '123'\n",
|
||||
"rows = 'ABCDEFGHI'\n",
|
||||
"cols = digits\n",
|
||||
"Square = str # e.g. 'A9'\n",
|
||||
"squares = cross(rows, cols)\n",
|
||||
"Grid = Dict[Square, DigitSet] # E.g. {'A9': '123', ...}\n",
|
||||
"all_boxes = [cross(rs, cs) for rs in ('ABC','DEF','GHI') for cs in ('123','456','789')]\n",
|
||||
"all_units = [cross(rows, c) for c in cols] + [cross(r, cols) for r in rows] + all_boxes\n",
|
||||
"units = {s: tuple(u for u in all_units if s in u) for s in squares}\n",
|
||||
"peers = {s: set().union(*units[s]) - {s} for s in squares}\n",
|
||||
"Picture = str \n",
|
||||
"\n",
|
||||
"def is_solution(solution: Grid, puzzle: Grid) -> bool:\n",
|
||||
" \"Is this proposed solution to the puzzle actually valid?\"\n",
|
||||
" return (solution is not Fail and\n",
|
||||
" all(solution[s] == puzzle[s] for s in squares if len(puzzle[s]) == 1) and\n",
|
||||
" return (solution is not None and\n",
|
||||
" all(solution[s] in puzzle[s] for s in squares) and\n",
|
||||
" all({solution[s] for s in unit} == set(digits) for unit in all_units))"
|
||||
]
|
||||
},
|
||||
@@ -172,28 +169,65 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Roads not taken\n",
|
||||
"# Constraint Propagation\n",
|
||||
"\n",
|
||||
"I made my implementation choices for clarity and ease of debugging. There are alternative choices I could have made:\n",
|
||||
"- **Digit**: could have been an `int`, but:\n",
|
||||
" - There is no need to do arithmetic on digits (all that matters is that they are unique).\n",
|
||||
" - I wanted the compact DigitSet representation.\n",
|
||||
"- **Digit set**: I considered two other options:\n",
|
||||
" - `set` of digits: a good choice, but:\n",
|
||||
" - I would then need to do a `deepcopy` of each grid, not just a copy.\n",
|
||||
" - The printed representation `'123'` is shorter and easier to read than `{'1', '2', '3'}` when debugging.\n",
|
||||
" - `int` bitset: each digit is represented by a bit; a DigitSet is a 9-bit binary int.\n",
|
||||
" - Efficient, but obfuscating. Used in the [Java version](SudokuJava.ipynb) for efficiency.\n",
|
||||
"- **Grid**: I considered three other options: \n",
|
||||
" - `defaultdict`: the default is the DigitSet `'123456789'`.\n",
|
||||
" - I thought that the top levels in the search tree would have only a few entries, so this would save space. \n",
|
||||
" - But the first round of constraint propagation touches almost all the squares anyway.\n",
|
||||
" - `list` of 9 rows, each a `list` of 9 squares (or a numpy 2D array):\n",
|
||||
" - Then a `Square` is a pair of coordinates, like `(1, 2)` rather than the simpler string `C2`.\n",
|
||||
" - Copying a grid would again require a `deepcopy`. \n",
|
||||
" - `list` of 81 squares: \n",
|
||||
" - Then a `Square` is an integer from 0 to 81.\n",
|
||||
" - Efficient, but for debugging it is clear that `C2` is in the third row and second column; it is not obvious that `19` is.\n"
|
||||
"Sudoku players know these two key strategies:\n",
|
||||
"\n",
|
||||
"1. If a square has only one possible digit, then **eliminate** that digit as a possibility for each of the square's peers.\n",
|
||||
"2. If a unit has only one possible square that can hold a digit, then **fill** the square with the digit.</i>\n",
|
||||
"\n",
|
||||
"This suggests two functions:\n",
|
||||
"1. `eliminate(grid, s, d)` to eliminate digit `d` as a possibility for square `s`\n",
|
||||
"2. `fill(grid, s, d)` to fill square `s` with the digit `d`. \n",
|
||||
"\n",
|
||||
"You might think that `fill` is the most fundamental operation, and that it could be implemented with `grid[s] = d`. But it turns out the code is simpler if we think of `eliminate` as the fundamental operation, and implement `fill` as \"eliminate all the digits except for `d` from `s`.\" Both functions mutate the grid they are passed, and return the mutated grid if they can perform the operation, or return `None` if there is a contradiction.\n",
|
||||
"\n",
|
||||
"We say that these two strategies constitute [constraint propagation](http://en.wikipedia.org/wiki/Constraint_satisfaction): \"constraint\" because they constrain what values can go in what squares, and \"propagation\" because a `fill` of one square can lead to an `eliminate` in other squares, which can in turn cause a `fill` of yet another square, and so on. \n",
|
||||
"\n",
|
||||
"The function `constrain` starts the whole process off. We initialize a new grid, `result`, (so that the original puzzle is not mutated), where `result` is filled with the known digits from the original grid. \n",
|
||||
"\n",
|
||||
"Here is the code:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def constrain(grid) -> Grid:\n",
|
||||
" \"Propagate constraints on a copy of grid to yield a new constrained Grid.\"\n",
|
||||
" result: Grid = {s: digits for s in squares}\n",
|
||||
" for s in grid:\n",
|
||||
" if len(grid[s]) == 1:\n",
|
||||
" fill(result, s, grid[s])\n",
|
||||
" return result\n",
|
||||
"\n",
|
||||
"def fill(grid: Grid, s: Square, d: Digit) -> Optional[Grid]:\n",
|
||||
" \"\"\"Eliminate all the digits except d from grid[s].\"\"\"\n",
|
||||
" if grid[s] == d or all(eliminate(grid, s, d2) for d2 in grid[s] if d2 != d):\n",
|
||||
" return grid\n",
|
||||
" else:\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def eliminate(grid: Grid, s: Square, d: Digit) -> Optional[Grid]:\n",
|
||||
" \"\"\"Eliminate d from grid[s]; implement the two constraint propagation strategies.\"\"\"\n",
|
||||
" if d not in grid[s]:\n",
|
||||
" return grid ## Already eliminated\n",
|
||||
" grid[s] = grid[s].replace(d, '')\n",
|
||||
" if not grid[s]:\n",
|
||||
" return None ## None: no legal digit left\n",
|
||||
" elif len(grid[s]) == 1:\n",
|
||||
" # 1. If a square has only one possible digit, then eliminate that digit as a possibility for each of the square's peers.\n",
|
||||
" d2 = grid[s]\n",
|
||||
" if not all(eliminate(grid, s2, d2) for s2 in peers[s]):\n",
|
||||
" return None ## None: can't eliminate d2 from some square\n",
|
||||
" for u in units[s]:\n",
|
||||
" dplaces = [s for s in u if d in grid[s]]\n",
|
||||
" # 2. If a unit has only one possible square that can hold a digit, then fill the square with the digit.\n",
|
||||
" if not dplaces or (len(dplaces) == 1 and not fill(grid, dplaces[0], d)):\n",
|
||||
" return None ## None: no place in u for d\n",
|
||||
" return grid"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -202,17 +236,16 @@
|
||||
"source": [
|
||||
"# Input and Output: Pictures and Grids\n",
|
||||
"\n",
|
||||
"We will need to convert between a `Picture` (used for input/output) and a `Grid` (used for computation). Conventions for `Picture`:\n",
|
||||
"To show how constraint propagation works, we will need to read in a string representing a puzzle (what we call a `Picture`), convert that picture to a `Grid`, apply constraint propagation, convert the solution back to a picture, and display the picture. Conventions for `Picture`:\n",
|
||||
"- A filled square is represented by a single digit, e.g.: `5`.\n",
|
||||
"- A blank square is represented by a period: `.`.\n",
|
||||
"- Spaces, newlines, and dashes/bars to separate boxes are included on output, and ignored on input.\n",
|
||||
"- An uncertain square is represented by a DigitSet in braces, e.g.: `{123}`.\n",
|
||||
" - This `'{123}'` notation never occurs in a standard Sudoku puzzle, but it is what `picture(grid)` produces for a grid that is partially solved, so I thought it was important to be able to read that format back in with `parse(picture)`.\n"
|
||||
"- Spaces, newlines, and dashes/bars to separate boxes are included on output, but are optional (and ignored) on input.\n",
|
||||
"- An uncertain square is represented by a DigitSet in braces, e.g.: `{123}`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -224,14 +257,15 @@
|
||||
" for s, v in zip(squares, vals)}\n",
|
||||
"\n",
|
||||
"def picture(grid) -> Picture:\n",
|
||||
" \"\"\"Convert a Grid to a Picture.\"\"\"\n",
|
||||
" if grid is Fail: \n",
|
||||
" return \"Fail\"\n",
|
||||
" \"\"\"Convert a Grid to a Picture string, one line at a time.\"\"\"\n",
|
||||
" if grid is None: \n",
|
||||
" return \"None\"\n",
|
||||
" def val(d: DigitSet) -> str: return '.' if d == digits else d if len(d) == 1 else '{' + d + '}'\n",
|
||||
" width = max(len(val(grid[s])) for s in grid)\n",
|
||||
" dash = '\\n' + '+'.join(['-' * (width * 3 + 2)] * 3) + ' '\n",
|
||||
" def cell(r, c): return val(grid[r + c]).center(width) + ('|' if c in '36' else ' ')\n",
|
||||
" def line(r): return ''.join(cell(r, c) for c in cols) + (dash if r in 'CF' else '')\n",
|
||||
" maxwidth = max(len(val(grid[s])) for s in grid)\n",
|
||||
" dash1 = '-' * (maxwidth * 3 + 2)\n",
|
||||
" dash3 = '\\n' + '+'.join(3 * [dash1])\n",
|
||||
" def cell(r, c): return val(grid[r + c]).center(maxwidth) + ('|' if c in '36' else ' ')\n",
|
||||
" def line(r): return ''.join(cell(r, c) for c in cols) + (dash3 if r in 'CF' else '')\n",
|
||||
" return '\\n'.join(map(line, rows))"
|
||||
]
|
||||
},
|
||||
@@ -244,7 +278,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -266,7 +300,8 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"grid1 = parse(\"53..7.... 6..195... .98....6. 8...6...3 4..8.3..1 7...2...6 .6....28. ...419..5 ....8..79\")\n",
|
||||
"pic1 = \"53..7.... 6..195... .98....6. 8...6...3 4..8.3..1 7...2...6 .6....28. ...419..5 ....8..79\"\n",
|
||||
"grid1 = parse(pic1)\n",
|
||||
"print(picture(grid1))"
|
||||
]
|
||||
},
|
||||
@@ -274,12 +309,12 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In a grid, filled squares have one possible digit, and unfilled squares have all 9 possible digits:"
|
||||
"In a grid, filled squares (like `A1`, the upper left corner of `grid1`) have one possible digit, and unfilled squares (like `A9`, the upper right corner of `grid1`) have all 9 possible digits:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -288,7 +323,7 @@
|
||||
"'5'"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -299,7 +334,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -308,68 +343,13 @@
|
||||
"'123456789'"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"grid1['A3']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Constraint Propagation\n",
|
||||
"\n",
|
||||
"Sudoku players know these two key strategies:\n",
|
||||
"\n",
|
||||
"1. If a square has only one possible digit, then **eliminate** that digit from the square's peers.\n",
|
||||
"2. If a unit has only one possible square that can hold a digit, then **fill** the square with the digit.</i>\n",
|
||||
"\n",
|
||||
"This suggests two functions:\n",
|
||||
"1. `eliminate(grid, s, d)` to eliminate digit `d` as a possibility for square `s`\n",
|
||||
"2. `fill(grid, s, d)` to fill square `s` with the digit `d`. \n",
|
||||
"\n",
|
||||
"You might think that `fill` is the most fundamental operation, and that it could be implemented with `grid[s] = d`. But the code is simpler if we think of `eliminate` as the fundamental operation, and implement `fill` as \"eliminate all the digits except for `d` from `s`.\" Both functions mutate the grid they are passed, and return the mutated grid if they can perform the operation, or return `Fail` if there is a contradiction.\n",
|
||||
"\n",
|
||||
"We say that these two strategies constitute [constraint propagation](http://en.wikipedia.org/wiki/Constraint_satisfaction): \"constraint\" because they constrain what values can go in what squares, and \"propagation\" because a `fill` of one square can lead to `eliminate` in other squares, which can in turn cause a `fill` of yet another square, and so on. \n",
|
||||
"\n",
|
||||
"Here is the code:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def fill(grid, s, d) -> Grid:\n",
|
||||
" \"\"\"Eliminate all the other digits (except d) from grid[s].\"\"\"\n",
|
||||
" if grid[s] == d or all(eliminate(grid, s, d2) for d2 in grid[s] if d2 != d):\n",
|
||||
" return grid\n",
|
||||
" else:\n",
|
||||
" return Fail\n",
|
||||
"\n",
|
||||
"def eliminate(grid, s, d) -> Grid:\n",
|
||||
" \"\"\"Eliminate d from grid[s]; implement the two constraint propagation strategies.\"\"\"\n",
|
||||
" if d not in grid[s]:\n",
|
||||
" return grid ## Already eliminated\n",
|
||||
" grid[s] = grid[s].replace(d, '')\n",
|
||||
" if not grid[s]:\n",
|
||||
" return Fail ## Fail: no legal digit left\n",
|
||||
" elif len(grid[s]) == 1:\n",
|
||||
" # 1. If a square has only one possible digit, then eliminate that digit from the square's peers.\n",
|
||||
" d2 = grid[s]\n",
|
||||
" if not all(eliminate(grid, s2, d2) for s2 in peers[s]):\n",
|
||||
" return Fail ## Fail: can't eliminate d2 from some square\n",
|
||||
" for u in units[s]:\n",
|
||||
" dplaces = [s for s in u if d in grid[s]]\n",
|
||||
" # 2. If a unit has only one possible square that can hold a digit, then fill the square with the digit.\n",
|
||||
" if not dplaces or (len(dplaces) == 1 and not fill(grid, dplaces[0], d)):\n",
|
||||
" return Fail ## Fail: no place in u for d\n",
|
||||
" return grid"
|
||||
"grid1['A9']"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -418,36 +398,13 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The function `constrain` returns the result of initiating constraint propagation on a grid. So that the original puzzle is not mutated, we create a new `constrained` grid that initially has all possible digits for each square, but then is filled with the known digits from the original grid."
|
||||
"Many puzzles can be completely solved by `constrain` alone, for example:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def constrain(grid) -> Grid:\n",
|
||||
" \"Propagate constraints on a copy of grid to yield a new constrained Grid.\"\n",
|
||||
" constrained: Grid = {s: digits for s in squares}\n",
|
||||
" for s in grid:\n",
|
||||
" d = grid[s]\n",
|
||||
" if len(d) == 1:\n",
|
||||
" fill(constrained, s, d)\n",
|
||||
" return constrained"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Many puzzles can be solved by `constrain` alone, for example:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
@@ -478,6 +435,34 @@
|
||||
"But for other puzzles, `constrain` is not enough:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"4 . .|. . .|8 . 5 \n",
|
||||
". 3 .|. . .|. . . \n",
|
||||
". . .|7 . .|. . . \n",
|
||||
"-----+-----+-----\n",
|
||||
". 2 .|. . .|. 6 . \n",
|
||||
". . .|. 8 .|4 . . \n",
|
||||
". . .|. 1 .|. . . \n",
|
||||
"-----+-----+-----\n",
|
||||
". . .|6 . 3|. 7 . \n",
|
||||
"5 . .|2 . .|. . . \n",
|
||||
"1 . 4|. . .|. . . \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"grid2 = parse(\"4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......\")\n",
|
||||
"print(picture(grid2))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
@@ -502,8 +487,6 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"grid2 = parse(\"4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......\")\n",
|
||||
"\n",
|
||||
"print(picture(constrain(grid2)))"
|
||||
]
|
||||
},
|
||||
@@ -511,7 +494,7 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We see that some progress has been made: the original puzzle has 17 squares filled in, and after constraint propagation, 20 are filled in. But that leaves 61 squares to go. We could try to add [additional constraint propagation strategies](https://bestofsudoku.com/sudoku-strategy), but there are many strategies, each one complicates the code, and even if we implemented them all, we wouldn't be *guaranteed* they could solve *any* puzzle. \n",
|
||||
"We see that some progress has been made: the `grid2` puzzle has 17 squares filled in, and after constraint propagation, 20 are filled in. But that leaves 61 squares to go. We could try to add [additional constraint propagation strategies](https://bestofsudoku.com/sudoku-strategy), but there are many strategies, each one complicates the code, and even if we implemented them all, we wouldn't be *guaranteed* they could solve *any* puzzle. \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Search Strategy\n",
|
||||
@@ -524,20 +507,19 @@
|
||||
"position, and that we have access to next-generation computers\n",
|
||||
"with 1024–core CPUs running at 100 GHz, and let's say\n",
|
||||
"we could afford a million of them, and while we're shopping, let's also pick up a time machine and go back 13 billion years to the origin of the universe and start our program running. We can then [estimate](http://www.google.com/search?&q=100+GHz/10+*+1024+*+1+million+*+13+billion+years+%2F+4.6e38+in+percent)\n",
|
||||
"that we'd be almost 1% done with this one puzzle by now. Brute force is not the search you're looking for.\n",
|
||||
"that we'd be almost 1% done with this one puzzle by now! Brute force is not the search you're looking for.\n",
|
||||
"\n",
|
||||
"A smarter search strategy is to **interleave** constraint propagation and systematic guessing as follows:\n",
|
||||
" - If the current grid contains a contradiction, don't try more combinations; return failure.\n",
|
||||
" - If there are no unfilled squares in the puzzle, then stop and return the succesful solution.\n",
|
||||
" - Otherwise choose some unfilled square, *s*. \n",
|
||||
" - Guess that some digit *d* fills square *s*. \n",
|
||||
" - Call `fill` on *s* and *d* to initiate constraint propagation.\n",
|
||||
"It is too slow to do constraint propagation first and then search. A smarter strategy is to **interleave** constraint propagation and search as follows:\n",
|
||||
" - If a previous step of the search on this branch returned `None`, then pass the `None` along.\n",
|
||||
" - If there are no squares with multiple possibilities in the puzzle, then return the completed grid.\n",
|
||||
" - Otherwise choose one of the not-yet-determined squares, *s*. \n",
|
||||
" - For each digit *d* that could possibly fill square *s*:\n",
|
||||
" - Initiate constraint propagation by calling `fill` on *s* and *d* and a copy of the grid.\n",
|
||||
" - Recursively search for a solution to the puzzle from there.\n",
|
||||
" - If the guess was right, we will get a solution.\n",
|
||||
" - If the guess was wrong, back up and guess a different digit.\n",
|
||||
" - Because we give up as soon as we get a contradiction, we examine far fewer possibilities than brute force search.\n",
|
||||
" - If the guess was wrong, continue on to the next digit *d*.\n",
|
||||
"\n",
|
||||
"Here is the implementation:"
|
||||
"Because we give up as soon as we get a contradiction, we examine far fewer possibilities than brute force search. Here is the implementation:"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -547,18 +529,18 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def search(grid) -> Grid:\n",
|
||||
" \"Depth-first search with constraint propagation (`fill`) to find a solution.\"\n",
|
||||
" if grid is Fail: \n",
|
||||
" return Fail\n",
|
||||
" unfilled = [s for s in squares if len(grid[s]) > 1]\n",
|
||||
" if not unfilled: \n",
|
||||
" \"Depth-first search with constraint propagation to find a solution.\"\n",
|
||||
" if grid is None: \n",
|
||||
" return None\n",
|
||||
" s = min((s for s in squares if len(grid[s]) > 1), \n",
|
||||
" default=None, key=lambda s: len(grid[s]))\n",
|
||||
" if s is None: # No squares with multiple possibilities; the search has succeeded\n",
|
||||
" return grid\n",
|
||||
" s = min(unfilled, key=lambda s: len(grid[s]))\n",
|
||||
" for d in grid[s]:\n",
|
||||
" solution = search(fill(grid.copy(), s, d))\n",
|
||||
" if solution:\n",
|
||||
" return solution\n",
|
||||
" return Fail"
|
||||
" return None"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -582,7 +564,7 @@
|
||||
"Note we create a new **copy** of `grid` for each recursive call to `search`. This way\n",
|
||||
"each branch of the search tree is independent, and mutations to `grid` done by constraint propagation in one branch do not confuse other branches. When it is time to back up and try a different digit, we have the original unaltered `grid` ready to go.\n",
|
||||
"\n",
|
||||
"We could call `search` directly, but I'll define the helper function `solve(puzzles)` to call `constrain` and then `search` on each puzzle, and then verify that the solution is correct with `is_solution`, and finally print the puzzle and the solution side by side:"
|
||||
"We could call `search` directly, but I'll define the helper function `solve_puzzles(puzzles)` to call `constrain` and then `search` on each puzzle, and then verify that the solution is correct with `is_solution`, and finally print the puzzle and the solution side by side:"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -591,17 +573,20 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def solve(puzzles, verbose=True) -> int:\n",
|
||||
"def solve_puzzles(puzzles, verbose=True) -> int:\n",
|
||||
" \"Solve and verify each puzzle, and if `verbose`, print puzzle and solution.\"\n",
|
||||
" sep = ' '\n",
|
||||
" for puzzle in puzzles:\n",
|
||||
" solution = search(constrain(puzzle))\n",
|
||||
" assert is_solution(solution, puzzle)\n",
|
||||
" if verbose:\n",
|
||||
" print('\\nPuzzle ', sep, 'Solution')\n",
|
||||
" for p, s in zip(picture(puzzle).splitlines(), picture(solution).splitlines()):\n",
|
||||
" print(p, sep, s)\n",
|
||||
" return len(puzzles)"
|
||||
" print_side_by_side('\\nPuzzle:\\n' + picture(puzzle), \n",
|
||||
" '\\nSolution:\\n' + picture(solution))\n",
|
||||
" return len(puzzles)\n",
|
||||
"\n",
|
||||
"def print_side_by_side(left, right, width=20):\n",
|
||||
" \"\"\"Print two strings side-by-side, line-by-line, each side `width` wide.\"\"\"\n",
|
||||
" for L, R in zip(left.splitlines(), right.splitlines()):\n",
|
||||
" print(L.ljust(width), R.ljust(width)) "
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -614,7 +599,7 @@
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
"5 3 .|. 7 .|. . . 5 3 4|6 7 8|9 1 2 \n",
|
||||
"6 . .|1 9 5|. . . 6 7 2|1 9 5|3 4 8 \n",
|
||||
". 9 8|. . .|. 6 . 1 9 8|3 4 2|5 6 7 \n",
|
||||
@@ -627,7 +612,7 @@
|
||||
". . .|4 1 9|. . 5 2 8 7|4 1 9|6 3 5 \n",
|
||||
". . .|. 8 .|. 7 9 3 4 5|2 8 6|1 7 9 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
"4 . .|. . .|8 . 5 4 1 7|3 6 9|8 2 5 \n",
|
||||
". 3 .|. . .|. . . 6 3 2|1 5 8|9 4 7 \n",
|
||||
". . .|7 . .|. . . 9 5 8|7 2 4|3 1 6 \n",
|
||||
@@ -653,7 +638,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"solve([grid1, grid2])"
|
||||
"solve_puzzles([grid1, grid2])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -675,7 +660,7 @@
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
". . .|. . .|. . . 1 2 3|4 5 6|7 8 9 \n",
|
||||
". . .|. . .|. . . 4 5 6|7 8 9|1 2 3 \n",
|
||||
". . .|. . .|. . . 7 8 9|1 2 3|4 5 6 \n",
|
||||
@@ -703,7 +688,7 @@
|
||||
"source": [
|
||||
"empty = parse('.' * 81)\n",
|
||||
" \n",
|
||||
"solve([empty])"
|
||||
"solve_puzzles([empty])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -715,11 +700,11 @@
|
||||
"We had success in solving `grid1`, `grid2`, and `empty`, but we are left with some questions:\n",
|
||||
"- Can the program solve *any* puzzle? \n",
|
||||
" - We'll test it on some more `puzzles`, taken from some files. The more we test, the more confidence we have.\n",
|
||||
" - Assuming no bugs, the program can solve any puzzle, but we haven't determined a bound on how long it would take.\n",
|
||||
" - Theoretically, the program should be able to solve any puzzle, but we haven't determined a bound on how long it would take.\n",
|
||||
"- Are the components of the program correct? \n",
|
||||
" - We'll run some `unit_tests`. There should be more.\n",
|
||||
" - We'll run some `unit_tests` to give us some confidence/ There should be more tests.\n",
|
||||
"- How long does it take to solve a puzzle?\n",
|
||||
" - We'll do `%time solve(puzzles)`.\n",
|
||||
" - We can measure that with the `%time` magic command.\n",
|
||||
" - For a more complete investigation of run times, see the [Java version](SudokuJava.ipynb)."
|
||||
]
|
||||
},
|
||||
@@ -727,12 +712,24 @@
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'pass'"
|
||||
]
|
||||
},
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def unit_tests():\n",
|
||||
" \"A suite of unit tests.\"\n",
|
||||
" assert len(squares) == 81\n",
|
||||
" assert len(all_units) == 27\n",
|
||||
" assert cross('AB', '12') == ('A1', 'A2', 'B1', 'B2')\n",
|
||||
" for s in squares:\n",
|
||||
" assert len(units[s]) == 3\n",
|
||||
" assert len(peers[s]) == 20\n",
|
||||
@@ -749,26 +746,8 @@
|
||||
" return [parse(p) for p in pictures if p]\n",
|
||||
"\n",
|
||||
"hardest = parse_grids(open('hardest.txt'))\n",
|
||||
"grids10k = parse_grids(open('sudoku10k.txt'))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'pass'"
|
||||
]
|
||||
},
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"grids10k = parse_grids(open('sudoku10k.txt'))\n",
|
||||
"\n",
|
||||
"unit_tests()"
|
||||
]
|
||||
},
|
||||
@@ -781,15 +760,15 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"CPU times: user 27.6 s, sys: 23.5 ms, total: 27.7 s\n",
|
||||
"Wall time: 27.7 s\n"
|
||||
"CPU times: user 27.2 s, sys: 17.1 ms, total: 27.2 s\n",
|
||||
"Wall time: 27.4 s\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -798,13 +777,13 @@
|
||||
"10000"
|
||||
]
|
||||
},
|
||||
"execution_count": 19,
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%time solve(grids10k, verbose=False)"
|
||||
"%time solve_puzzles(grids10k, verbose=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -813,7 +792,42 @@
|
||||
"source": [
|
||||
"That took less than 3 milliseconds per puzzle, or over 350 puzzles per second. (The [Java version](SudokuJava.ipynb) does over 100,000 puzzles per second. It benefits from being able to run 20 threads in parallel, from using more efficient data structures, and from the Java JVM being more efficient than Python's bytecode interpreter.)\n",
|
||||
"\n",
|
||||
"We can inspect the solutions for the ten allegedly hardest puzzles:"
|
||||
"The ten allegedly hardest puzzles also take about 3 milliseconds per puzzle on average:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"CPU times: user 33.8 ms, sys: 1.08 ms, total: 34.9 ms\n",
|
||||
"Wall time: 34.3 ms\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"10"
|
||||
]
|
||||
},
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%time solve_puzzles(hardest, verbose=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can see the puzzles and their solutions:"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -826,7 +840,7 @@
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
"8 5 .|. . 2|4 . . 8 5 9|6 1 2|4 3 7 \n",
|
||||
"7 2 .|. . .|. . 9 7 2 3|8 5 4|1 6 9 \n",
|
||||
". . 4|. . .|. . . 1 6 4|3 7 9|5 2 8 \n",
|
||||
@@ -839,7 +853,7 @@
|
||||
". 1 7|. . .|. . . 6 1 7|4 2 5|8 9 3 \n",
|
||||
". . .|. 3 6|. 4 . 5 9 8|7 3 6|2 4 1 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
". . 5|3 . .|. . . 1 4 5|3 2 7|6 9 8 \n",
|
||||
"8 . .|. . .|. 2 . 8 3 9|6 5 4|1 2 7 \n",
|
||||
". 7 .|. 1 .|5 . . 6 7 2|9 1 8|5 4 3 \n",
|
||||
@@ -852,7 +866,7 @@
|
||||
". . 4|. . .|. 3 . 9 8 4|7 6 1|2 3 5 \n",
|
||||
". . .|. . 9|7 . . 5 2 1|8 3 9|7 6 4 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
"1 2 .|. 4 .|. . . 1 2 8|5 4 7|6 3 9 \n",
|
||||
". . 5|. 6 9|. 1 . 3 4 5|8 6 9|2 1 7 \n",
|
||||
". . 9|. . .|5 . . 6 7 9|2 1 3|5 4 8 \n",
|
||||
@@ -865,7 +879,7 @@
|
||||
"4 . .|9 . .|8 . 1 4 6 7|9 3 5|8 2 1 \n",
|
||||
". . 3|. . .|9 . 4 2 5 3|1 7 8|9 6 4 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
". . .|5 7 .|. 3 . 6 2 4|5 7 8|1 3 9 \n",
|
||||
"1 . .|. . .|. 2 . 1 3 5|4 9 6|8 2 7 \n",
|
||||
"7 . .|. 2 3|4 . . 7 8 9|1 2 3|4 5 6 \n",
|
||||
@@ -878,7 +892,7 @@
|
||||
". . .|7 . .|9 . . 5 6 8|7 3 2|9 4 1 \n",
|
||||
". . 1|8 . .|. . . 3 7 1|8 4 9|5 6 2 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
"1 . .|. . 7|. 9 . 1 6 2|8 5 7|4 9 3 \n",
|
||||
". 3 .|. 2 .|. . 8 5 3 4|1 2 9|6 7 8 \n",
|
||||
". . 9|6 . .|5 . . 7 8 9|6 4 3|5 2 1 \n",
|
||||
@@ -891,7 +905,7 @@
|
||||
". 4 .|. . .|. . 7 2 4 1|9 3 5|8 6 7 \n",
|
||||
". . 7|. . .|3 . . 8 9 7|2 6 1|3 5 4 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
"1 . .|. 3 4|. 8 . 1 5 2|9 3 4|6 8 7 \n",
|
||||
". . .|8 . .|5 . . 7 6 3|8 2 1|5 4 9 \n",
|
||||
". . 4|. 6 .|. 2 1 9 8 4|5 6 7|3 2 1 \n",
|
||||
@@ -904,7 +918,7 @@
|
||||
". . 6|. . 9|. . . 4 3 6|2 1 9|7 5 8 \n",
|
||||
". 9 .|6 4 .|. . 2 8 9 7|6 4 5|1 3 2 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
". . .|9 2 .|. . . 3 8 7|9 2 6|4 1 5 \n",
|
||||
". . 6|8 . 3|. . . 5 4 6|8 1 3|9 7 2 \n",
|
||||
"1 9 .|. 7 .|. . 6 1 9 2|4 7 5|8 3 6 \n",
|
||||
@@ -917,7 +931,7 @@
|
||||
". . .|5 . 7|2 . . 6 1 3|5 9 7|2 8 4 \n",
|
||||
". . .|. 6 4|. . . 8 2 9|1 6 4|3 5 7 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
". 6 .|5 . 4|. 3 . 8 6 9|5 7 4|1 3 2 \n",
|
||||
"1 . .|. 9 .|. . 8 1 2 4|3 9 6|7 5 8 \n",
|
||||
". . .|. . .|. . . 3 7 5|1 2 8|6 9 4 \n",
|
||||
@@ -930,7 +944,7 @@
|
||||
"4 . .|. 8 .|. . 1 4 9 3|7 8 5|2 6 1 \n",
|
||||
". 5 .|2 . 3|. 4 . 6 5 8|2 1 3|9 4 7 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
"7 . .|. . .|4 . . 7 9 8|6 3 5|4 2 1 \n",
|
||||
". 2 .|. 7 .|. 8 . 1 2 6|9 7 4|5 8 3 \n",
|
||||
". . 3|. . 8|. 7 9 4 5 3|2 1 8|6 7 9 \n",
|
||||
@@ -943,7 +957,7 @@
|
||||
". 3 .|. 4 .|. 6 . 8 3 5|7 4 9|1 6 2 \n",
|
||||
". . 9|. . 1|. 3 5 2 4 9|8 6 1|7 3 5 \n",
|
||||
" \n",
|
||||
"Puzzle Solution\n",
|
||||
"Puzzle: Solution: \n",
|
||||
". . .|. 7 .|. 2 . 5 9 4|8 7 6|1 2 3 \n",
|
||||
"8 . .|. . .|. . 6 8 2 3|9 1 4|7 5 6 \n",
|
||||
". 1 .|2 . 5|. . . 6 1 7|2 3 5|8 9 4 \n",
|
||||
@@ -954,9 +968,7 @@
|
||||
"-----+-----+----- -----+-----+----- \n",
|
||||
". . .|3 . 2|. 8 . 1 5 9|3 4 2|6 8 7 \n",
|
||||
"4 . .|. . .|. . 9 4 3 6|5 8 7|2 1 9 \n",
|
||||
". 7 .|. 6 .|. . . 2 7 8|1 6 9|4 3 5 \n",
|
||||
"CPU times: user 40.6 ms, sys: 3.5 ms, total: 44.1 ms\n",
|
||||
"Wall time: 41.5 ms\n"
|
||||
". 7 .|. 6 .|. . . 2 7 8|1 6 9|4 3 5 \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -971,7 +983,35 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%time solve(hardest)"
|
||||
"solve_puzzles(hardest)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Roads not taken\n",
|
||||
"\n",
|
||||
"I made my implementation choices for clarity and ease of debugging. There are alternative choices I could have made:\n",
|
||||
"- **Digit**: could have been an `int`, but:\n",
|
||||
" - There is no need to do arithmetic on digits (all that matters is that they are unique).\n",
|
||||
" - I wanted the compact DigitSet representation.\n",
|
||||
"- **Digit set**: I considered two other options:\n",
|
||||
" - `set` of digits: a good choice, but:\n",
|
||||
" - I would then need to do a `deepcopy` of each grid, not just a copy.\n",
|
||||
" - The printed representation `'123'` is shorter and easier to read than `{'1', '2', '3'}` when debugging.\n",
|
||||
" - `int` bitset: each digit is represented by a bit; a DigitSet is a 9-bit binary int.\n",
|
||||
" - Efficient, but obfuscating. Used in the [Java version](SudokuJava.ipynb) for efficiency.\n",
|
||||
"- **Grid**: I considered three other options: \n",
|
||||
" - `defaultdict`: the default is the DigitSet `'123456789'`.\n",
|
||||
" - I thought that the top levels in the search tree would have only a few entries, so this would save space. \n",
|
||||
" - But the first round of constraint propagation touches almost all the squares anyway.\n",
|
||||
" - `list` of 9 rows, each a `list` of 9 squares (or a numpy 2D array):\n",
|
||||
" - Then a `Square` is a pair of coordinates, like `(1, 2)` rather than the simpler string `C2`.\n",
|
||||
" - Copying a grid would again require a `deepcopy`. \n",
|
||||
" - `list` of 81 squares: \n",
|
||||
" - Then a `Square` is an integer from 0 to 81.\n",
|
||||
" - Efficient, but for debugging it is clear that `C2` is in the third row and second column; it is not obvious that `19` is.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -995,7 +1035,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
@@ -1009,7 +1049,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.6"
|
||||
"version": "3.9.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
1074
ipynb/bikerides.tsv
1074
ipynb/bikerides.tsv
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
notebooks = {
|
||||
|
||||
'Recent (2022)': [], # Gets updated automatically
|
||||
'Recent (2022–2024)': [], # Gets updated automatically
|
||||
|
||||
'Programming Examples': [
|
||||
("AlphaCode Automated Programming", 2022, 'AlphaCode.ipynb', "Analysis of AlphaCode's automated solution to a coding problem"),
|
||||
@@ -138,7 +138,7 @@ def find_recent(notebooks) -> None:
|
||||
recent = next(key for key in notebooks if key.startswith('Recent'))
|
||||
for category in notebooks:
|
||||
for line in notebooks[category]:
|
||||
if line[1] in {2022}:
|
||||
if line[1] in {2022, 2023, 2024}:
|
||||
notebooks[recent].append(line)
|
||||
|
||||
def format_category(category) -> str:
|
||||
|
||||
Reference in New Issue
Block a user