Merge branch 'master' into fix-randall-munroe

This commit is contained in:
Peter Norvig 2020-06-23 12:42:18 -07:00 committed by GitHub
commit 2a4141a4b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 26324 additions and 23133 deletions

183
README.md
View File

@ -1,104 +1,123 @@
<div align="right" style="text-align:right"><i>Peter Norvig<br><a href="https://github.com/norvig/pytudes/blob/master/LICENSE">MIT License</a><br>2015-2020</i></div>
# pytudes
"An *étude* (a French word meaning *study*) is an instrumental musical composition, usually short, of considerable difficulty, and designed to provide practice material for perfecting a particular musical skill." &mdash; [Wikipedia](https://en.wikipedia.org/wiki/%C3%89tude)
This project contains **pytudes**&mdash;Python programs, usually short, for perfecting programming skills.
This project contains **pytudes**&mdash;Python programs, usually short, for perfecting particular programming skills.
Some programs are in Jupyter (`.ipynb`) notebooks, some in `.py` files. For each notebook you can:
- Click on [co](https://colab.research.google.com) to **run** the file on Colab
- Click on [dn](https://deepnote.com) to **run** the notebook on DeepNote
- Click on [my](https://mybinder.org) to **run** the notebook on MyBinder
- Click on [nb](https://nbviewer.jupyter.org/) to **view** the notebook on NBViewer
- Click on the title to **view** the notebook on github.
- Hover over the title to **view** a description.
Some are in Jupyter (IPython) notebooks, some in `.py` files. You can view the files here on github, or click the **NB** link to view them on [nbviewer](http://nbviewer.jupyter.org/) (which sometimes works better). If you want to *run* the notebooks, not just view them, you can clone the project, or run all the notebooks online by clicking this button: [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/norvig/pytudes/master), or click the **DN** link to run each individual notebook on [deepnote](https://beta.deepnote.org/).
# Index of Jupyter (IPython) Notebooks
|Run|Year|Programming Examples|
|---|----|---|
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Advent-2018.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FAdvent-2018.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FAdvent-2018.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Advent-2018.ipynb) | 2018 | <b><a href="ipynb/Advent-2018.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2018 ">Advent of Code 2018</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Advent%202017.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FAdvent%202017.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FAdvent%202017.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Advent%202017.ipynb) | 2017 | <b><a href="ipynb/Advent%202017.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2017">Advent of Code 2017</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Advent%20of%20Code.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FAdvent%20of%20Code.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FAdvent%20of%20Code.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Advent%20of%20Code.ipynb) | 2016 | <b><a href="ipynb/Advent%20of%20Code.ipynb" title="Puzzle site with a coding puzzle each day for Advent 2016*">Advent of Code 2016</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Beal.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FBeal.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FBeal.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Beal.ipynb) | 2018 | <b><a href="ipynb/Beal.ipynb" title="A search for counterexamples to Beal's Conjecture">Beal's Conjecture Revisited</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Bike%20Speed%20versus%20Grade.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FBike%20Speed%20versus%20Grade.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FBike%20Speed%20versus%20Grade.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Bike%20Speed%20versus%20Grade.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/Bike%20Speed%20versus%20Grade.ipynb" title="How fast can I bike as the route gets steeper?">Bike Speed Versus Grade</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Cant-Stop.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCant-Stop.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FCant-Stop.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Cant-Stop.ipynb) | 2018 | <b><a href="ipynb/Cant-Stop.ipynb" title="Optimal play in a dice board game">Can't Stop</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Sierpinski.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSierpinski.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FSierpinski.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Sierpinski.ipynb) | 2019 | <b><a href="ipynb/Sierpinski.ipynb" title="A surprising appearance of the Sierpinski triangle in a random walk between vertexes">Chaos with Triangles</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Life.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FLife.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FLife.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Life.ipynb) | 2017 | <b><a href="ipynb/Life.ipynb" title="The cellular automata zero-player game">Conway's Game of Life</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Dice%20Baseball.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FDice%20Baseball.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FDice%20Baseball.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Dice%20Baseball.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/Dice%20Baseball.ipynb" title="Simulating baseball games">Dice Baseball</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Maze.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FMaze.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FMaze.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Maze.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/Maze.ipynb" title="Make a maze by generating a random tree superimposed on a grid and solve it.">Generating and Solving Mazes</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/PhotoFocalLengths.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FPhotoFocalLengths.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FPhotoFocalLengths.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/PhotoFocalLengths.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/PhotoFocalLengths.ipynb" title="Generate charts of what focal lengths were used on a photo trip.">Photo Focal Lengths</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Pickleball.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FPickleball.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FPickleball.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Pickleball.ipynb) | 2018 | <b><a href="ipynb/Pickleball.ipynb" title="Scheduling a doubles tournament fairly and efficiently">Pickleball Tournament</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Project%20Euler%20Utils.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FProject%20Euler%20Utils.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FProject%20Euler%20Utils.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Project%20Euler%20Utils.ipynb) | 2017 | <b><a href="ipynb/Project%20Euler%20Utils.ipynb" title="My utility functions for the Project Euler problems, including `Primes` and `Factors`">Project Euler Utilities</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Orderable%20Cards.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FOrderable%20Cards.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FOrderable%20Cards.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Orderable%20Cards.ipynb) | 2018 | <b><a href="ipynb/Orderable%20Cards.ipynb" title="Can you get your hand of cards into a nice order with just one move?">Properly Ordered Card Hands</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Electoral%20Votes.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FElectoral%20Votes.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FElectoral%20Votes.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Electoral%20Votes.ipynb) | <b><u>2020</b></u> | <b><a href="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></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/WWW.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FWWW.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FWWW.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/WWW.ipynb) | 2019 | <b><a href="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></b> |
|Programming Examples|
|---|
|[Advent of Code 2018](ipynb/Advent-2018.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Advent-2018.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FAdvent-2018.ipynb)**<br>*Puzzle site with a coding puzzle each day for Advent 2018 .*|
|[Advent of Code 2017](ipynb/Advent%202017.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Advent%202017.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FAdvent%25202017.ipynb)**<br>*Puzzle site with a coding puzzle each day for Advent 2017.*|
|[Advent of Code 2016](ipynb/Advent%20of%20Code.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Advent%20of%20Code.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FAdvent%2520of%2520Code.ipynb)**<br>*Puzzle site with a coding puzzle each day for Advent 2016*.|
|[Project Euler Utilities](ipynb/Project%20Euler%20Utils.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Project%20Euler%20Utils.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FProject%2520Euler%2520Utils.ipynb)**<br>*My utility functions for the Project Euler problems, including `Primes` and `Factors`.*|
|[Translating English Sentences into Propositional Logic Statements](ipynb/PropositionalLogic.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/PropositionalLogic.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FPropositionalLogic.ipynb)**<br>*Automatically converting informal English sentences into formal Propositional Logic.*|
|[Beal's Conjecture Revisited](ipynb/Beal.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Beal.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FBeal.ipynb)**<br>*A search for counterexamples to Beal's Conjecture*|
|[WWW: Who Will Win (NBA Title)?](ipynb/WWW.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/WWW.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FWWW.ipynb)**<br>*Computing the probability of winning the NBA title, for my home town Warriors, or any other team.*|
|[Pickleball Tournament](ipynb/Pickleball.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Pickleball.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FPickleball.ipynb)**<br>*Scheduling a doubles tournament fairly and efficiently.*|
|[Dice Baseball](ipynb/Dice%20Baseball.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Dice%2520Baseball.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FDice%2520Baseball.ipynb)**<br>*Simulating baseball games.*|
|[Conway's Game of Life](ipynb/Life.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Life.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FLife.ipynb)**<br>*The cellular automata zero-player game.*|
|[A Chaos Game with Triangles](ipynb/Sierpinski.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Sierpinski.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSierpinski.ipynb)**<br>*A surprising appearance of the Sierpinski triangle in a random walk between vertexes.*|
|[Generating Mazes](ipynb/Maze.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Maze.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FMaze.ipynb)**<br>*Make a maze by generating a random tree superimposed on a grid.*|
|[Weighing Twelve Balls](ipynb/TwelveBalls.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/TwelveBalls.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FTwelveBalls.ipynb)**<br>*A puzzle where you are given some billiard balls and a balance scale, and asked to find the one ball that is heavier or lighter, in a limited number of weighings.*|
|[Can't Stop](ipynb/Cant-Stop.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Cant-Stop.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCant-Stop.ipynb)**<br>*Optimal play in a dice board game.*|
|[Bike Speed Versus Grade](ipynb/Bike%20Speed%20versus%20Grade.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Bike%20Speed%20versus%20Grade.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FBike%2520Speed%2520versus%2520Grade.ipynb)**<br>*How fast can I bike as the route gets steeper?*|
|[Properly Ordered Card Hands](ipynb/Orderable%20Cards.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Orderable%20Cards.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FOrderable%2520Cards.ipynb)**<br>*Can you get your hand of cards into a nice order with just one move?*|
|[Tracking Trump: Electoral Votes](ipynb/Electoral%20Votes.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Electoral%20Votes.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FElectoral%20Votes.ipynb)**<br>*How many electoral votes would Trump get if he wins the state where he has positive net approval?*|
|Run|Year|Logic and Number Puzzles|
|---|----|---|
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Cryptarithmetic.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCryptarithmetic.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FCryptarithmetic.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Cryptarithmetic.ipynb) | 2014 | <b><a href="ipynb/Cryptarithmetic.ipynb" title="Substitute digits for letters and make NUM + BER = PLAY">Cryptarithmetic</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Countdown.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCountdown.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FCountdown.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Countdown.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/Countdown.ipynb" title="Solving the equation 10 _ 9 _ 8 _ 7 _ 6 _ 5 _ 4 _ 3 _ 2 _ 1 = 2016. From an Alex Bellos puzzle">Four 4s, Five 5s, Equilength Numbers, and Countdown to 2016</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/flipping.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fflipping.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2Fflipping.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/flipping.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/flipping.ipynb" title="Can you go through a deck of cards, guessing higher or lower correctly for each card?">Flipping Cards: A Guessing Game</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/NightKing.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FNightKing.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FNightKing.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/NightKing.ipynb) | 2019 | <b><a href="ipynb/NightKing.ipynb" title="Investigasting a battle between the army of the dead and the army of the living">How Many Soldiers to Beat the Night King?</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Fred%20Buns.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FFred%20Buns.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FFred%20Buns.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Fred%20Buns.ipynb) | 2015 | <b><a href="ipynb/Fred%20Buns.ipynb" title="A tale of a bicycle combination lock that uses letters instead of digits. Inspired by Bike Snob NYC">Let's Code About Bike Locks</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Socks.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSocks.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FSocks.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Socks.ipynb) | 2019 | <b><a href="ipynb/Socks.ipynb" title="What is the probability that you will be able to pair up socks as you randomly pull them out of the dryer?">Pairing Socks</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Mean%20Misanthrope%20Density.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FMean%20Misanthrope%20Density.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FMean%20Misanthrope%20Density.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Mean%20Misanthrope%20Density.ipynb) | 2017 | <b><a href="ipynb/Mean%20Misanthrope%20Density.ipynb" title="How crowded will this neighborhood be, if nobody wants to live next door to anyone else?">The Puzzle of the Misanthropic Neighbors</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Riddler%20Battle%20Royale.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FRiddler%20Battle%20Royale.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FRiddler%20Battle%20Royale.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Riddler%20Battle%20Royale.ipynb) | 2017 | <b><a href="ipynb/Riddler%20Battle%20Royale.ipynb" title="A puzzle involving allocating your troops and going up against an opponent">Riddler: Battle Royale</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/RiddlerLottery.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FRiddlerLottery.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FRiddlerLottery.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/RiddlerLottery.ipynb) | 2019 | <b><a href="ipynb/RiddlerLottery.ipynb" title="Can you find what lottery number tickets these five friends picked?">Riddler Lottery</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/TourDe538.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FTourDe538.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FTourDe538.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/TourDe538.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/TourDe538.ipynb" title="Solve a puzzle involving the best pace for a bicycle race.">Riddler: Tour de 538</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Sicherman%20Dice.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSicherman%20Dice.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FSicherman%20Dice.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Sicherman%20Dice.ipynb) | 2018 | <b><a href="ipynb/Sicherman%20Dice.ipynb" title="Find a pair of dice that is like a regular pair of dice, only different">Sicherman Dice</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Golomb-Puzzle.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FGolomb-Puzzle.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FGolomb-Puzzle.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Golomb-Puzzle.ipynb) | 2014 | <b><a href="ipynb/Golomb-Puzzle.ipynb" title="A Puzzle involving placing rectangles of different sizes inside a square">Sol Golomb's Rectangle Puzzle</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Cheryl.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCheryl.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FCheryl.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Cheryl.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/Cheryl.ipynb" title="Solving the *Cheryl's Birthday* logic puzzle">When is Cheryl's Birthday? (new: Mad Cheryl)</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Cheryl-and-Eve.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCheryl-and-Eve.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FCheryl-and-Eve.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Cheryl-and-Eve.ipynb) | 2015 | <b><a href="ipynb/Cheryl-and-Eve.ipynb" title="Inventing new puzzles in the Style of Cheryl's Birthday">When Cheryl Met Eve: A Birthday Story</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/xkcd1313.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fxkcd1313.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2Fxkcd1313.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/xkcd1313.ipynb) | 2015 | <b><a href="ipynb/xkcd1313.ipynb" title="Find the smallest regular expression; inspired by Randall Munroe">xkcd 1313: Regex Golf</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/xkcd1313-part2.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fxkcd1313-part2.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2Fxkcd1313-part2.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/xkcd1313-part2.ipynb) | 2015 | <b><a href="ipynb/xkcd1313-part2.ipynb" title="Regex Golf: better, faster, funner. With Stefan Pochmann.">xkcd 1313: Regex Golf (Part 2: Infinite Problems)</a></b> |
|Logic and Number Puzzles|
|---|
|[When is Cheryl's Birthday?](ipynb/Cheryl.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Cheryl.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCheryl.ipynb)**<br>*Solving the "Cheryl's Birthday" logic puzzle.*|
|[When Cheryl Met Eve: A Birthday Story](ipynb/Cheryl-and-Eve.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Cheryl-and-Eve.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCheryl-and-Eve.ipynb)**<br>*Inventing new puzzles in the Style of Cheryl's Birthday.*|
|[How Many Soldiers Do You Need to Beat the Night King?](ipynb/NightKing.ipynb) **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/NightKing.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FNightKing.ipynb)**<br>*Investigasting a battle between the army of the dead and the army of the living.*|
|[The Devil and the Coin Flip Game](ipynb/Coin%20Flip.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Coin%20Flip.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCoin%2520Flip.ipynb)**<br>*How to beat the Devil at his own game.*|
|[The Puzzle of the Misanthropic Neighbors](ipynb/Mean%20Misanthrope%20Density.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Mean%20Misanthrope%20Density.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FMean%2520Misanthrope%2520Density.ipynb)**<br>*How crowded will this neighborhood be, if nobody wants to live next door to anyone else?*|
|[Four 4s, Five 5s, and Countdown to 2016](ipynb/Countdown.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Countdown.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCountdown.ipynb)**<br>*Solving the equation 10 _ 9 _ 8 _ 7 _ 6 _ 5 _ 4 _ 3 _ 2 _ 1 = 2016. From an Alex Bellos puzzle.*|
|[Sicherman Dice](ipynb/Sicherman%20Dice.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Sicherman%20Dice.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSicherman%2520Dice.ipynb)**<br>*Find a pair of dice that is like a regular pair of dice, only different.*|
|[Sol Golomb's Rectangle Puzzle](ipynb/Golomb-Puzzle.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Golomb-Puzzle.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FGolomb-Puzzle.ipynb)**<br>*A Puzzle involving placing rectangles of different sizes inside a square.*|
|[Cryptarithmetic](ipynb/Cryptarithmetic.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Cryptarithmetic.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCryptarithmetic.ipynb)**<br>*Substitute digits for letters and make NUM + BER = PLAY.*|
|[The Riddler: Battle Royale](ipynb/Riddler%20Battle%20Royale.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Riddler%20Battle%20Royale.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FRiddler%2520Battle%2520Royale.ipynb)**<br>*A puzzle involving allocating your troops and going up against an opponent.*|
|Run|Year|Word Puzzles|
|---|----|---|
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Boggle.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FBoggle.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FBoggle.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Boggle.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/Boggle.ipynb" title="Find all the words on a Boggle board; then find a board with a lot of words">Boggle / Inverse Boggle</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Scrabble.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FScrabble.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FScrabble.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Scrabble.ipynb) | 2017 | <b><a href="ipynb/Scrabble.ipynb" title="Refactoring the Scrabble / Word with Friends game from Udacity 212">Crossword Game : Refactoring a Scrabble Program</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/ElementSpelling.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FElementSpelling.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FElementSpelling.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/ElementSpelling.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/ElementSpelling.ipynb" title="Spelling words using the chemical element symbols, like CoIn">Chemical Element Spelling</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Gesture%20Typing.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FGesture%20Typing.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FGesture%20Typing.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Gesture%20Typing.ipynb) | 2017 | <b><a href="ipynb/Gesture%20Typing.ipynb" title="What word has the longest path on a gesture-typing smartphone keyboard?">Gesture Typing</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Ghost.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FGhost.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FGhost.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Ghost.ipynb) | 2017 | <b><a href="ipynb/Ghost.ipynb" title="The word game Ghost (add letters, try to avoid making a word)">Ghost: A Word game</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/How%20to%20Do%20Things%20with%20Words.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FHow%20to%20Do%20Things%20with%20Words.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FHow%20to%20Do%20Things%20with%20Words.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/How%20to%20Do%20Things%20with%20Words.ipynb) | 2018 | <b><a href="ipynb/How%20to%20Do%20Things%20with%20Words.ipynb" title="Spelling Correction, Secret Codes, Word Segmentation, and more">How to Do Things with Words: NLP in Python</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/SpellingBee.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSpellingBee.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FSpellingBee.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/SpellingBee.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/SpellingBee.ipynb" title="Find the highest-scoring board for the NY Times Spelling Bee puzzle">Spelling Bee</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/PropositionalLogic.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FPropositionalLogic.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FPropositionalLogic.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/PropositionalLogic.ipynb) | 2017 | <b><a href="ipynb/PropositionalLogic.ipynb" title="Automatically convert informal English sentences into formal Propositional Logic">Translating English into Propositional Logic</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/pal3.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fpal3.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2Fpal3.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/pal3.ipynb) | 2017 | <b><a href="ipynb/pal3.ipynb" title="Searching for a long Panama-style palindrome, this time letter-by-letter">World's Longest Palindrome</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Portmantout.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FPortmantout.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FPortmantout.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Portmantout.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/Portmantout.ipynb" title="Find a 1word that squishes together a bunch of words">World's Shortest Portmantout Word</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/xkcd-Name-Dominoes.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fxkcd-Name-Dominoes.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2Fxkcd-Name-Dominoes.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/xkcd-Name-Dominoes.ipynb) | 2018 | <b><a href="ipynb/xkcd-Name-Dominoes.ipynb" title="Lay out dominoes legally; the dominoes have people names, not numbers">xkcd 1970: Name Dominoes</a></b> |
|Word Games|
|---|
|[Portmantout Words](ipynb/Portmantout.ipynb) **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Portmantout.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FPortmantout.ipynb)**<br>*Find a long word that squishes together a bunch of words.*|
|[xkcd 1970: Name Dominoes](ipynb/xkcd-Name-Dominoes.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/xkcd-Name-Dominoes.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fxkcd-Name-Dominoes.ipynb)**<br>*Lay out dominoes legally; the dominoes have people names, not numbers.*|
|[Ghost](ipynb/Ghost.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Ghost.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FGhost.ipynb)**<br>*The word game Ghost (add letters, try to avoid making a word).*|
|[World's Longest Palindrome](ipynb/pal3.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/pal3.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fpal3.ipynb)**<br>*Searching for a long Panama-style palindrome, this time letter-by-letter.*|
|[Refactoring a Crossword Game Program](ipynb/Scrabble.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Scrabble.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FScrabble.ipynb)**<br>*Refactoring the Scrabble / Word with Friends game from Udacity 212.*|
|[xkcd 1313: Regex Golf](ipynb/xkcd1313.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/xkcd1313.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fxkcd1313.ipynb)**<br>*Find the smallest regular expression; inspired by Randall Munroe.*|
|[xkcd 1313: Regex Golf (Part 2: Infinite Problems)](ipynb/xkcd1313-part2.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/xkcd1313-part2.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2Fxkcd1313-part2.ipynb)**<br>*Regex Golf: better, faster, funner. With Stefan Pochmann.*|
|[Let's Code About Bike Locks](ipynb/Fred%20Buns.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Fred%20Buns.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FFred%2520Buns.ipynb)**<br>*A tale of a bicycle combination lock that uses letters instead of digits. Inspired by Bike Snob NYC.*|
|[Gesture Typing](ipynb/Gesture%20Typing.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Gesture%20Typing.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FGesture%2520Typing.ipynb)**<br>*What word has the longest path on a gesture-typing smartphone keyboard?*|
|[How to Do Things with Words, or Statistical Natural Language Processing in Python](ipynb/How%20to%20Do%20Things%20with%20Words.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/How%20to%20Do%20Things%20with%20Words.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FHow%2520to%2520Do%2520Things%2520with%2520Words.ipynb)**<br>*Spelling Correction, Secret Codes, Word Segmentation, and more: grab your bag of words.*|
|Run|Year|Math Concepts: Probability, Uncertainty, Counting, etc.|
|---|----|---|
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Probability.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FProbability.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FProbability.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Probability.ipynb) | 2018 | <b><a href="ipynb/Probability.ipynb" title="Code and examples of the basic principles of Probability Theory">A Concrete Introduction to Probability</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/ProbabilityParadox.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FProbabilityParadox.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FProbabilityParadox.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/ProbabilityParadox.ipynb) | 2016 | <b><a href="ipynb/ProbabilityParadox.ipynb" title="Some classic paradoxes in Probability Theory, and how to think about disagreements">Probability, Paradox, and the Reasonable Person Principle</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/ProbabilitySimulation.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FProbabilitySimulation.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FProbabilitySimulation.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/ProbabilitySimulation.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/ProbabilitySimulation.ipynb" title="When the sample space is too complex, simulations can estimate probabilities">Estimating Probabilities with Simulations</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Coin%20Flip.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FCoin%20Flip.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FCoin%20Flip.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Coin%20Flip.ipynb) | 2019 | <b><a href="ipynb/Coin%20Flip.ipynb" title="How to beat the Devil at his own game">The Devil and the Coin Flip Game</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Economics.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FEconomics.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FEconomics.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Economics.ipynb) | 2018 | <b><a href="ipynb/Economics.ipynb" title="A simulation of a simple economic game">Economics Simulation</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Euler's%20Conjecture.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FEuler's%20Conjecture.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FEuler's%20Conjecture.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Euler's%20Conjecture.ipynb) | 2018 | <b><a href="ipynb/Euler's%20Conjecture.ipynb" title="Solving a 200-year-old puzzle by finding integers that satisfy a<sup>5</sup> + b<sup>5</sup> + c<sup>5</sup> + d<sup>5</sup> = e<sup>5</sup>">Euler's Sum of Powers Conjecture</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/How%20To%20Count%20Things.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FHow%20To%20Count%20Things.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FHow%20To%20Count%20Things.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/How%20To%20Count%20Things.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/How%20To%20Count%20Things.ipynb" title="Combinatorial math: how to count how many things there are, when there are a lot of them">How to Count Things</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/TwelveBalls.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FTwelveBalls.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FTwelveBalls.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/TwelveBalls.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/TwelveBalls.ipynb" title="A puzzle where you are given some billiard balls and a balance scale, and asked to find the one ball that is heavier or lighter, in a limited number of weighings">Weighing Twelve Balls</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Differentiation.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FDifferentiation.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FDifferentiation.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Differentiation.ipynb) | 2017 | <b><a href="ipynb/Differentiation.ipynb" title="A computer algebra system that manipulates expressions, including symbolic differentiation">Symbolic Algebra, Simplification, and Differentiation</a></b> |
|Math Concepts|
|---|
|[A Concrete Introduction to Probability](ipynb/Probability.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Probability.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FProbability.ipynb)**<br>*Code and examples of the basic principles of Probability Theory.*|
|[Probability, Paradox, and the Reasonable Person Principle](ipynb/ProbabilityParadox.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/ProbabilityParadox.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FProbabilityParadox.ipynb)**<br>*Some classic paradoxes in Probability Theory, and how to think about disagreements.*|
|[Symbolic Algebra, Simplification, and Differentiation](ipynb/Differentiation.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Differentiation.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FDifferentiation.ipynb)**<br>*A computer algebra system that manipulates expressions, including symbolic differentiation.*|
|[Economics Simulation](ipynb/Economics.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Economics.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FEconomics.ipynb)**<br>*A simulation of a simple economic game.*|
|[How to Count Things](ipynb/How%20To%20Count%20Things.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/How%20To%20Count%20Things.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FHow%2520To%2520Count%2520Things.ipynb)**<br>*Combinatorial math: how to count how many things there are, when there are a lot of them.*|
|[Pairing Socks](ipynb/Socks.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Socks.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSocks.ipynb)**<br>*What is the probability that you will be able to pair up socks as you randomly pull them out of the dryer?*|
|[Euler's Sum of Powers Conjecture](ipynb/Euler's%20Conjecture.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Euler's%20Conjecture.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FEuler's%2520Conjecture.ipynb)**<br>*Solving a 200-year-old puzzle by finding integers that satisfy a<sup>5</sup> + b<sup>5</sup> + c<sup>5</sup> + d<sup>5</sup> = e<sup>5</sup>.*|
|Computer Science Algorithms and Concepts|
|---|
|[BASIC Interpreter](ipynb/BASIC.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/BASIC.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FBASIC.ipynb)**<br>*How to write an interpreter for the BASIC programming language.*|
|[Bad Grade, Good Experience](ipynb/Snobol.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Snobol.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSnobol.ipynb)**<br>*As a student, did you ever get a bad grade on a programming assignment? (Snobol, Concordance)*|
|[The Convex Hull Problem](ipynb/Convex%20Hull.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Convex%20Hull.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FConvex%2520Hull.ipynb)**<br>*A classic Computer Science Algorithm.*|
|[The Traveling Salesperson Problem](ipynb/TSP.ipynb)&nbsp; &nbsp; **[NB](http:/nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/TSP.ipynb)**, **[DN](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FTSP.ipynb)**<br>*Another of the classics.*|
|Run|Year|Computer Science Algorithms and Concepts|
|---|----|---|
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Snobol.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FSnobol.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FSnobol.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Snobol.ipynb) | 2017 | <b><a href="ipynb/Snobol.ipynb" title="As a student, did you ever get a bad grade on a programming assignment? (Snobol, Concordance)">Bad Grade, Good Experience</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/BASIC.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FBASIC.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FBASIC.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/BASIC.ipynb) | 2017 | <b><a href="ipynb/BASIC.ipynb" title="How to write an interpreter for the BASIC programming language">BASIC Interpreter</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/Convex%20Hull.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FConvex%20Hull.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FConvex%20Hull.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/Convex%20Hull.ipynb) | 2017 | <b><a href="ipynb/Convex%20Hull.ipynb" title="A classic Computer Science Algorithm">The Convex Hull Problem</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/StableMatching.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FStableMatching.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FStableMatching.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/StableMatching.ipynb) | <b><u>2020</b></u> | <b><a href="ipynb/StableMatching.ipynb" title="What is the best way to pair up two groups with each other, obeying preferences?">The Stable Matching Problem</a></b> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/master/ipynb/TSP.ipynb) [dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2FTSP.ipynb) [my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2FTSP.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/TSP.ipynb) | 2018 | <b><a href="ipynb/TSP.ipynb" title="Another of the classics">The Traveling Salesperson Problem</a></b> |
# Index of Python Files
| **File** | **Description** | **Documentation**|
|:--------|:-------------------|----|
|[SET.py](https://github.com/norvig/pytudes/blob/master/py/SET.py)|Analyze the card game [SET](http://www.setgame.com/set).|[SET.html](http://norvig.com/SET.html)|
|[beal.py](https://github.com/norvig/pytudes/blob/master/py/beal.py)|Search for counterexamples to Beal's Conjecture|[beal.html](http://norvig.com/beal.html)
|[docex.py](https://github.com/norvig/pytudes/blob/master/py/docex.py)|A framework for running unit tests, similar to `doctest`.|
|[ibol.py](https://github.com/norvig/pytudes/blob/master/py/ibol.py)|An Exercise in Species Barcoding|[ibol.html](http://norvig.com/ibol.html)
|[lettercount.py](https://github.com/norvig/pytudes/blob/master/py/lettercount.py)|Convert Google Ngram Counts to Letter Counts|[mayzner.html](http://norvig.com/mayzner.html)
|[lis.py](https://github.com/norvig/pytudes/blob/master/py/lis.py)|Lisp Interpreter written in Python|[lispy.html](http://norvig.com/lispy.html)
|[lispy.py](https://github.com/norvig/pytudes/blob/master/py/lispy.py)|Even Better Lisp Interpreter written in Python|[lispy2.html](http://norvig.com/lispy2.html)
|[lispytest.py](https://github.com/norvig/pytudes/blob/master/py/lispytest.py)|Tests for Lisp Interpreters|
|[pal.py](https://github.com/norvig/pytudes/blob/master/py/pal.py)|Find long palindromes|[palindrome.html](http://norvig.com/palindrome.html)
|[pal2.py](https://github.com/norvig/pytudes/blob/master/py/pal2.py)|Find longer palindromes|[palindrome.html](http://norvig.com/palindrome.html)
|[pal3.py](https://github.com/norvig/pytudes/blob/master/py/pal3.py)|Find even longer palindromes|[palindrome.html](http://norvig.com/palindrome.html)
|[py2html.py](https://github.com/norvig/pytudes/blob/master/py/py2html.py)|Pretty-printer to format Python files as html|
|[spell.py](https://github.com/norvig/pytudes/blob/master/py/spell.py)|Spelling corrector|[spell-correct.html](http://norvig.com/spell-correct.html)
|[sudoku.py](https://github.com/norvig/pytudes/blob/master/py/sudoku.py)|Program to solve sudoku puzzles|[sudoku.html](http://norvig.com/sudoku.html)
|[testaccum.py](https://github.com/norvig/pytudes/blob/master/py/testaccum.py)|Tests for my failed Python `accumulation display` proposal|[pyacc.html](http://norvig.com/pyacc.html)
|[yaptu.py](https://github.com/norvig/pytudes/blob/master/py/yaptu.py)|Yet Another Python Templating Utility|
| File | Description | Documentation |
|:--|:----|----|
|[beal.py](/blob/master/py/beal.py)|*Search for counterexamples to Beal's Conjecture*|[documentation](http://norvig.com/beal.html)|
|[docex.py](/blob/master/py/docex.py)|*A framework for running unit tests, similar to `doctest`*||
|[ibol.py](/blob/master/py/ibol.py)|*An Exercise in Species Barcoding*|[documentation](http://norvig.com/ibol.html)|
|[lettercount.py](/blob/master/py/lettercount.py)|*Convert Google Ngram Counts to Letter Counts*|[documentation](http://norvig.com/mayzner.html)|
|[lis.py](/blob/master/py/lis.py)|*Lisp Interpreter written in Python*|[documentation](http://norvig.com/lispy.html)|
|[lispy.py](/blob/master/py/lispy.py)|*Even Better Lisp Interpreter written in Python*|[documentation](http://norvig.com/lispy2.html)|
|[lispytest.py](/blob/master/py/lispytest.py)|*Tests for Lisp Interpreters*||
|[pal.py](/blob/master/py/pal.py)|*Find long palindromes*|[documentation](http://norvig.com/palindrome.html)|
|[pal2.py](/blob/master/py/pal2.py)|*Find longer palindromes*|[documentation](http://norvig.com/palindrome.html)|
|[pal3.py](/blob/master/py/pal3.py)|*Find even longer palindromes*|[documentation](http://norvig.com/palindrome.html)|
|[pytudes.py](/blob/master/py/pytudes.py)|*Pre-process text to generate this README.md file.*||
|[py2html.py](/blob/master/py/py2html.py)|*Pretty-printer to format Python files as html*||
|[SET.py](/blob/master/py/SET.py)|*Analyze the card game SET*|[documentation](http://norvig.com/SET.html)|
|[spell.py](/blob/master/py/spell.py)|*Spelling corrector*|[documentation](http://norvig.com/spell-correct.html)|
|[sudoku.py](/blob/master/py/sudoku.py)|*Program to solve sudoku puzzles*|[documentation](http://norvig.com/sudoku.html)|
|[testaccum.py](/blob/master/py/testaccum.py)|*Tests for my failed Python `accumulation display` proposal*|[documentation](http://norvig.com/pyacc.html)|
|[yaptu.py](/blob/master/py/yaptu.py)|*Yet Another Python Templating Utility*||
# Etudes for Programmers
I got the idea for the "etudes" part of the name from
I got the idea for the *"etudes"* part of the name from
this [1978 book](https://books.google.com/books/about/Etudes_for_programmers.html?id=u89WAAAAMAAJ)
by [Charles Wetherell](http://demin.ws/blog/english/2012/08/25/interview-with-charles-wetherell/)
that was very influential to me when I was first learning to program.
by [Charles Wetherell](http://demin.ws/blog/english/2012/08/25/interview-with-charles-wetherell)
that was very influential to me when I was first learning to program. I still have my copy.
![](https://images-na.ssl-images-amazon.com/images/I/51ZnZH29dvL._SX394_BO1,204,203,200_.jpg)

File diff suppressed because one or more lines are too long

584
ipynb/Boggle.ipynb Normal file
View File

@ -0,0 +1,584 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"text-align: right\">Peter Norvig<br>January 2020</div>\n",
"\n",
"# Boggle\n",
"\n",
"![Boggle Board](https://www.puzzle-words.com/art/og/puzzle-words.png)\n",
"\n",
"The word game **Boggle** ([rules here](https://howdoyouplayit.com/boggle-rules-play-boggle/)) is a fun game to play with friends, but not so fun to play against a computer. It is all too simple for a computer to find *all* the valid words in a Boggle board (such as the one shown above) in less than a second. We'll see how to do that as a straightforward programming exercise. Then we'll consider a more challenging problem:\n",
"\n",
"# Inverse Boggle\n",
"\n",
"The goal of Inverse Boggle is to find an arrangement of letters on a board that scores the most points. It is called Inverse Boggle because, instead of \"give me a board and I'll find the words\" it is \"give me the word list and I'll find a board with lots of words.\" \n",
"\n",
"It is not feasible to try all possible boards ($26^{5 \\times 5} \\approx 10^{35}$ possible $5 \\times 5$ boards), so we will use **hill-climbing**, a heuristic search technique that does not guarantee finding an optimal solution.\n",
"\n",
"# The Boggle Board\n",
"\n",
"I'll represent a board as a dict where the keys are `(x, y)` coordinates of squares, and the values are individual letters (except that `QU` occupies a single square). A board will also hold:\n",
"- `board.squares`: a list of squares in row-major order.\n",
"- `board.neighbors`: a dict of `{square: neighbors}`. \n",
"- `board.format`: a string format method to print the board nicely using `__repr__`.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"import itertools\n",
"\n",
"class Board(dict):\n",
" \"\"\"A board is a dict of {(x, y): L}.\"\"\"\n",
" def __init__(self, letters=None):\n",
" letters = [('QU' if L == 'Q' else L) for L in letters if 'A' <= L <= 'Z']\n",
" n = int(len(letters) ** 0.5)\n",
" self.squares = [(x, y) for y in range(n) for x in range(n)]\n",
" self.neighbors = {s: neighbors(s, n) for s in self.squares}\n",
" self.format = ('\\n'.join(['{:2s}' * n] * n)).format\n",
" self.update(zip(self.squares, letters))\n",
" \n",
" def __repr__(self): \n",
" return self.format(*(self[s].capitalize() for s in self.squares))\n",
" \n",
"def neighbors(square, n) -> list:\n",
" \"\"\"All the squares that neighbor a square.\"\"\"\n",
" (x, y) = square\n",
" return [(x+dx, y+dy) for dx in (-1, 0, 1) for dy in (-1, 0, 1)\n",
" if 0 <= x+dx < n and 0 <= y+dy < n and not (dx == dy == 0)]"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"P U Z Z L \n",
"W O R D E \n",
"B O G G L \n",
"S E A R C \n",
"F I N D H "
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"board = Board('PUZZL WORDE BOGGL SEARC FINDH')\n",
"board"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# The word list and scoring\n",
"\n",
"We'll read in a word list, convert to uppercase, and keep all the words that are three letters or more. The variable `WORDS` holds the set of valid words. The function `total_score` computes the total score of a set of words. It does not take a board as an argument, and so it does not check if the words can actually be made on the board. It does check that the words are in the `WORDS` list of valid words."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 172820 enable1.txt\r\n"
]
}
],
"source": [
"! [ -e enable1.txt ] || curl -O http://norvig.com/ngrams/enable1.txt\n",
"! wc -w enable1.txt"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"172724"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def total_score(found, scores=[0, 0, 0, 1, 1, 2, 3, 5] + [11] * 99) -> int:\n",
" \"\"\"The total score for the words found, according to the rules.\"\"\"\n",
" return sum([scores[len(w)] for w in found & WORDS])\n",
"\n",
"WORDS = {w for w in open('enable1.txt').read().upper().split() if len(w) >= 3}\n",
"len(WORDS)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Finding all the words in a board\n",
"\n",
"The strategy for finding words:\n",
"- A word could start at any of the squares on the board, so consider each one.\n",
"- At each square, form a **path** consisting of just that square (for example, the top left square forms the path `[(0, 0)]`) and a **prefix string** consisting of the letters visited in the path; for this one-square path on `board` (the one with `PUZZLE` in it), the prefix string would be `'P'`. On a 5 by 5 board we would have 25 initial paths of length one.\n",
"- Now continue each path:\n",
" - If the prefix string is a word, record it: add it to the set `found`.\n",
" - If the prefix string is a prefix of some word in the dictionary, continue the path by visiting each neighboring square that has not already been visited in the path. For example, in `board`, the path `[(0, 0)]` with prefix `'P'` would be continued to three neighbors:\n",
" - `[(0, 0), (1, 0)]`, `'PU'`: here `'PU'` is a prefix of a word so continue this path.\n",
" - `[(0, 0), (0, 1)]`, `'PW'`: here `'PW'` is not a prefix of any word so do **not** continue this path.\n",
" - `[(0, 0), (1, 1)]`, `'PO'`: here `'PO'` is a prefix of a word so continue this path.\n",
" - One continuation of `'PO'` is as follows:\n",
" - `[(0, 0), (1, 1), (0, 1)]`, `'POW'`: here `'POW'` is both a word and a prefix, so record it and continue.\n",
"- We can precompute the set of all `PREFIXES` of all words in the word list."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def word_prefixes(words) -> set:\n",
" \"The set of all non-empty, non-full-word prefixes of each word in a word list.\"\n",
" return {word[:i] for word in words for i in range(1, len(word))}\n",
"\n",
"PREFIXES = word_prefixes(WORDS) # Precompute this once.\n",
"\n",
"def find_words(board, words=WORDS, prefixes=PREFIXES):\n",
" \"\"\"Find all words in a Boggle board by recursively continuing paths that are prefixes of words.\"\"\"\n",
" found = set() # Accumulate the words found in the set `found`\n",
" def continue_path(path, prefix):\n",
" if prefix in words:\n",
" found.add(prefix)\n",
" if prefix in prefixes:\n",
" for s in board.neighbors[path[-1]]:\n",
" if s not in path:\n",
" continue_path(path + [s], prefix + board[s])\n",
" # Body of find_words: Start paths from each of the squares on the board\n",
" for s in board.squares:\n",
" continue_path([s], board[s])\n",
" return found"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"How many words can we find? And how long does it take?"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 4.39 ms, sys: 43 µs, total: 4.43 ms\n",
"Wall time: 4.5 ms\n"
]
},
{
"data": {
"text/plain": [
"252"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%time len(find_words(board))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Can we see the words and summarize them? "
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"252 words found:\n",
"AGE AGED AGES AGGRO AGGROS AGO AIN AIS AND ANE ANES ANI ANIS ANISE ARC ARCH ARGLE ARGLED\n",
"BEAD BEAGLE BEAN BEAR BEARD BEG BEGAN BEGGAR BEGGED BEGROAN BEN BEND BOA BOAR BOARD BOG\n",
"BOGAN BOGGED BOGGLE BOGGLED BOO BOOR BOOS BOP BORDEL BOS BOURG BOW CRAG CRAGGED CRANE\n",
"CRANES DAG DAGGLE DAGGLED DAGO DAGOES DAGOS DAIS DARN DEGAGE DEL DRAG DRAGGED DRAGGLE\n",
"DRAGGLED DRAIN DROOP DROP EAGLE EAR EARL EARN EDGE EDGES EFS EGAD EGG EGGAR EGGED EGO EGOS\n",
"ELD END ENDARCH ENRAGE ENRAGED EOSIN FEAR FEN FENAGLE FENAGLED FEND FIAR FIE FIEND FIN\n",
"FINAGLE FINAGLED FIND FINE FINES GAD GAE GAEN GAES GAG GAGE GAGED GAGES GAIN GAN GANE\n",
"GANEF GANEFS GAR GARGLE GARGLED GARNI GEAR GED GEL GELD GEN GLED GOA GOAD GOB GOBO GOBOES\n",
"GOBOS GOBS GOES GOO GOOP GOOS GOOSE GOR GORGE GORGED GOURD GOURDE GRAD GRAIN GRAN GRAND\n",
"GROAN GROG GROUP GROW IFS INARCH LED LEDGE LEDGES LEG LEZ NAE NAG NAGGED NAIF NAIFS NAOS\n",
"NARC NARD NEAR NEB NEBS NEIF NEIFS OAR OBE OBES OBOE OBOES OES ORGAN ORGANISE ORZO OSE OUR\n",
"POOR POROSE POUR POW PUR PURGE PURGED PURGES PUZZLE PUZZLED RAD RAG RAGE RAGED RAGES\n",
"RAGGED RAGGLE RAIN RAISE RAN RAND RANI RANIS ROAD ROAN ROAR ROB ROBE ROBES ROBS ROE ROES\n",
"ROOSE ROSE ROSIN ROUP ROW SEA SEAR SEARCH SEG SEGGAR SEGO SEI SEIF SEN SEND SIN SINE SOAR\n",
"SOB SOGGED SORD SORGO SOW UPO URD URGE URGED URGES WOAD WOE WOES WOG WOO WOOS WOP WORD WOS\n",
"WURZEL ZED ZOO ZOOS\n",
"Score of 449 for 252 words on board:\n",
"P U Z Z L \n",
"W O R D E \n",
"B O G G L \n",
"S E A R C \n",
"F I N D H \n"
]
}
],
"source": [
"import textwrap\n",
"\n",
"def report(board, verbose=True):\n",
" found = find_words(board, WORDS, PREFIXES)\n",
" if verbose:\n",
" print(f'{len(found)} words found:') \n",
" print(textwrap.fill(' '.join(sorted(found)), width=90))\n",
" print(f'Score of {total_score(found)} for {len(found)} words on board:\\n{board}')\n",
" \n",
"report(board)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's test that the `Q` works, and that a 4x4 board works:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"125 words found:\n",
"ANE ANES ANEW ANSWER ANSWERS AWE AWES AWN AWNS AWRY ENOSIS ENS EON EONS ERS ESES ESS ESSES\n",
"ION IONS ITS NAE NAW NEIST NENE NEON NEONS NESS NESSES NEST NESTS NEW NEWNESS NEWS NOES\n",
"NOESIS NOISE NOISES NOS NOSE NOSES NOSIER OES ONE ONENESS ONERY ONES ONS OSE OSES OSIER\n",
"OSIERS QUA QUEAN QUEANS QUEST QUESTION QUESTIONER QUESTIONERS QUESTIONS QUESTS REI REIS\n",
"RENEST RENESTS RES RESIST REST RESTS REWAN REWAX SEA SEI SEIS SEISE SEISES SEN SENE SENSE\n",
"SENSES SER SERS SESSION SEW SEWAN SEWANS SEWN SEWS SIS SISES SIT SITS SNAW SNAWS SON SONE\n",
"SONES SONS SOS STIES SWAN SWANS TIE TIER TIERS TIES TIS WAE WAENESS WAES WAN WANE WANES\n",
"WANS WAX WAXY WEN WENS WEST WESTS WREN WRENS WREST WRESTS WRY\n",
"Score of 245 for 125 words on board:\n",
"QuE S T \n",
"A N S I \n",
"X W E O \n",
"Y R S N \n"
]
}
],
"source": [
"report(Board('QEST ANSI XWEO YRSN'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Inverse Boggle\n",
"\n",
"I'll tackle the Inverse Boggle problem with a **hill-climbing** approach:\n",
"- Start with some board.\n",
"- Make a random change to some letter on the board.\n",
"- Evaluate the score of the new board; if it is the best score so far, keep it.\n",
"- If not, revert the change.\n",
"- Repeat for a set number of iterations"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def boggle_hill_climbing(board, words=WORDS, prefixes=PREFIXES, repeat=500):\n",
" \"\"\"Solve Inverse Boggle by hill-climbing: find a high-scoring board by\n",
" starting with one and changing it.\"\"\"\n",
" board = Board(board[s] for s in board.squares) # Copy board, so we don't mutate original\n",
" best_score = total_score(find_words(board, words, prefixes))\n",
" for _ in range(repeat):\n",
" s, old_letter = mutate_boggle(board)\n",
" new_score = total_score(find_words(board, words, prefixes))\n",
" if new_score >= best_score:\n",
" best_score = new_score\n",
" else:\n",
" board[s] = old_letter # Change back\n",
" return board\n",
"\n",
"# The distribution of letters in the 16-cube version of the game\n",
"LETTERS = 3 * 'AABCDEEEGHIILMNOOPRSTUY' + 'AADEFFIJKKLLNNQRSSTTUVVWWXZ'\n",
"\n",
"def mutate_boggle(board, letters=LETTERS):\n",
" \"\"\"Make a random change to one letter in the board\"\"\"\n",
" s = random.choice(board.squares)\n",
" old_letter = board[s]\n",
" board[s] = random.choice(letters)\n",
" return s, old_letter\n",
"\n",
"def random_board(n=5, letters=LETTERS) -> Board:\n",
" \"\"\"Return a random Boggle board.\"\"\"\n",
" return Board(random.sample(letters, n * n))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's generate a random board and see if we can improve it:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"177 words found:\n",
"ABIOTIC ABO ALE ALOOF ALT ALTO ATT ATTIC AUK BABE BABOO BABOOL BABU BAKE BAL BALE BALK BAT\n",
"BATT BAUBEE BEE BEEP BIO BIOTIC BIPOD BOA BOAT BOD BOLA BOLE BOLT BOO BOOT BOP BOT BOTA\n",
"BOTT BOTTLE BOY BUB BUBAL BUBALE BUBO BUOY CITOLA CITOLE DIOL DIT DITA DITTO DOPY DOT\n",
"DOTTLE EAT EAU ELK FIB FOB FOOL FOOT FOOTBOY FOOTLE FOP HEP HIP HOB HOBO HOE HOOD HOOF\n",
"HOOP HOOT HOP HOPE HOY HYP HYPO IODIC IOTA KAB KAE KALE KAT KEA KELOID KLOOF KUE LAB LAKE\n",
"LAT LATI LEA LEAK LEK LEKU LOB LOBO LOO LOOF LOOP LOOPY LOOT LOT LOTA LOTI LOTIC LOTTO\n",
"LOUP LOUPE OAK OAT OBI OBOE OBOL OBOLE ODIC OFT OLE OLEA OOH OOT OOTID OPE OTIC OTTO OUPH\n",
"OUPHE PEE PEH PHI PIBAL POD POH POI POOD POOF POOH POOL POOP POT POTBOY POTTLE POTTO PUB\n",
"PUKE TAB TABOO TABU TAE TAEL TAKE TALE TALK TAO TAU TAUPE TIC TIT TITLE TOD TOIT TOLA TOLE\n",
"TOO TOOL TOOT TOOTLE TOP TOPI TOT TOTAL TOUPEE UKE UPO YIP YOB YOU\n",
"Score of 234 for 177 words on board:\n",
"C I T L E \n",
"D T O A K \n",
"F O B U B \n",
"P I O P E \n",
"I Y H E U \n"
]
}
],
"source": [
"board2 = random_board(5)\n",
"report(board2)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Score of 5782 for 1577 words on board:\n",
"V I S E N \n",
"D E T T S \n",
"R A L A R \n",
"S I M E C \n",
"E D N S H \n"
]
}
],
"source": [
"report(boggle_hill_climbing(board2), False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Impressive! We got roughly a ten-fold improvement in score after 500 repetitions.\n",
"We can do the same with our original `board`:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"252 words found:\n",
"AGE AGED AGES AGGRO AGGROS AGO AIN AIS AND ANE ANES ANI ANIS ANISE ARC ARCH ARGLE ARGLED\n",
"BEAD BEAGLE BEAN BEAR BEARD BEG BEGAN BEGGAR BEGGED BEGROAN BEN BEND BOA BOAR BOARD BOG\n",
"BOGAN BOGGED BOGGLE BOGGLED BOO BOOR BOOS BOP BORDEL BOS BOURG BOW CRAG CRAGGED CRANE\n",
"CRANES DAG DAGGLE DAGGLED DAGO DAGOES DAGOS DAIS DARN DEGAGE DEL DRAG DRAGGED DRAGGLE\n",
"DRAGGLED DRAIN DROOP DROP EAGLE EAR EARL EARN EDGE EDGES EFS EGAD EGG EGGAR EGGED EGO EGOS\n",
"ELD END ENDARCH ENRAGE ENRAGED EOSIN FEAR FEN FENAGLE FENAGLED FEND FIAR FIE FIEND FIN\n",
"FINAGLE FINAGLED FIND FINE FINES GAD GAE GAEN GAES GAG GAGE GAGED GAGES GAIN GAN GANE\n",
"GANEF GANEFS GAR GARGLE GARGLED GARNI GEAR GED GEL GELD GEN GLED GOA GOAD GOB GOBO GOBOES\n",
"GOBOS GOBS GOES GOO GOOP GOOS GOOSE GOR GORGE GORGED GOURD GOURDE GRAD GRAIN GRAN GRAND\n",
"GROAN GROG GROUP GROW IFS INARCH LED LEDGE LEDGES LEG LEZ NAE NAG NAGGED NAIF NAIFS NAOS\n",
"NARC NARD NEAR NEB NEBS NEIF NEIFS OAR OBE OBES OBOE OBOES OES ORGAN ORGANISE ORZO OSE OUR\n",
"POOR POROSE POUR POW PUR PURGE PURGED PURGES PUZZLE PUZZLED RAD RAG RAGE RAGED RAGES\n",
"RAGGED RAGGLE RAIN RAISE RAN RAND RANI RANIS ROAD ROAN ROAR ROB ROBE ROBES ROBS ROE ROES\n",
"ROOSE ROSE ROSIN ROUP ROW SEA SEAR SEARCH SEG SEGGAR SEGO SEI SEIF SEN SEND SIN SINE SOAR\n",
"SOB SOGGED SORD SORGO SOW UPO URD URGE URGED URGES WOAD WOE WOES WOG WOO WOOS WOP WORD WOS\n",
"WURZEL ZED ZOO ZOOS\n",
"Score of 449 for 252 words on board:\n",
"P U Z Z L \n",
"W O R D E \n",
"B O G G L \n",
"S E A R C \n",
"F I N D H \n"
]
}
],
"source": [
"report(board)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Score of 5690 for 1511 words on board:\n",
"L P C A P \n",
"N A R O M \n",
"D E T I L \n",
"S E S E N \n",
"R T N R G \n"
]
}
],
"source": [
"report(boggle_hill_climbing(board), False) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Again, roughly a ten-fold improvement. \n",
"\n",
"Now, let's start from a very high-scoring board, identified by Justin Boyan in [his Ph.D. thesis](https://www.ri.cmu.edu/publications/learning-evaluation-functions-for-global-optimization/), and see if we can improve it:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Score of 10112 for 2290 words on board:\n",
"R S T C S \n",
"D E I A E \n",
"G N L R P \n",
"E A T E S \n",
"M S S I D \n"
]
}
],
"source": [
"boyan = Board('RSTCS DEIAE GNLRP EATES MSSID')\n",
"report(boyan, False)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Score of 10112 for 2290 words on board:\n",
"R S T C S \n",
"D E I A E \n",
"G N L R P \n",
"E A T E S \n",
"M S S I D \n"
]
}
],
"source": [
"report(boggle_hill_climbing(boyan), False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sadly, we were not able to make an improvement in 500 repetitions. But that certainly is no guarantee that `boyan` is the best possible board. Here are some things to try to find a better board; maybe you can implement some of them, or try some ideas of your own:\n",
"\n",
"- **Genetic algorithms**: We used **mutation** of a single board, but we could also consider **crossover** where we keep a pool of boards and take the first half of one board and combine it with the second half of another.\n",
"- **Swaps**: We changed one letter at a time. But maybe there is no change of one letter that will improve a board, but there is a change involving two squares, either swapping them or mutating both of them.\n",
"- **Incremental score calculation**: We modified just one square and then tried to find all the words from scratch. Would it be faster to keep track of which squares contributed to which words, and only re-do the calculations for the one changed square? This would probably involve infixes of words rather than prefixes. Perhaps by keeping track of what each square contributes, we can make a better choice of which square to mutate.\n",
"- **Random restarts**: When is it best to continue searching from the current board, versus starting over from a new board?"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.0"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because it is too large Load Diff

View File

@ -4,25 +4,25 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"<div align=\"right\"><i>Peter Norvig<br>April 2015<br>Python 3: Feb 2019</i></div>\n",
"<div align=\"right\" style=\"text-align: right\"><i>Peter Norvig<br>April 2015<br>Python 3: Feb 2019<br>Steve's bus: Apr 2020<br>Mad Cheryl: May 2020</i></div>\n",
"\n",
"# When is Cheryl's Birthday?\n",
"\n",
"\n",
"[This puzzle](https://www.google.com/webhp?#q=cheryl%27s%20birthday) has been making the rounds:\n",
"**[This puzzle](https://www.google.com/webhp?#q=cheryl%27s%20birthday)** has been making the rounds:\n",
"\n",
"> 1. Albert and Bernard became friends with Cheryl, and want to know when her birthday is. Cheryl gave them a list of 10 possible dates:\n",
" May 15 May 16 May 19\n",
" June 17 June 18\n",
" July 14 July 16\n",
" August 14 August 15 August 17\n",
"> 2. Cheryl then tells Albert and Bernard separately the month and the day of the birthday respectively.\n",
"> 2. **Cheryl** then privately tells Albert the month and Bernard the day of her birthday.\n",
"> 3. **Albert**: \"I don't know when Cheryl's birthday is, and I know that Bernard does not know.\"\n",
"> 4. **Bernard**: \"At first I don't know when Cheryl's birthday is, but I know now.\"\n",
"> 5. **Albert**: \"Then I also know when Cheryl's birthday is.\"\n",
"> 6. So when is Cheryl's birthday?\n",
"\n",
"Let's work through this puzzle statement by statement.\n",
"Let's work through the puzzle line by line.\n",
"\n",
"\n",
"\n",
@ -54,9 +54,8 @@
"metadata": {},
"outputs": [],
"source": [
"def Month(date): return date.split()[0]\n",
"\n",
"def Day(date): return date.split()[1]"
"def month(date): return date.split()[0]\n",
"def day(date): return date.split()[1]"
]
},
{
@ -76,7 +75,7 @@
}
],
"source": [
"Month('May 15')"
"month('May 15')"
]
},
{
@ -96,7 +95,7 @@
}
],
"source": [
"Day('May 15')"
"day('May 15')"
]
},
{
@ -105,9 +104,19 @@
"source": [
"## 2. Cheryl then tells Albert and Bernard separately the month and the day of the birthday respectively.\n",
"\n",
"Now we have to think about what we're doing. We'll use a *set of dates* to represent a *belief set*: a person who has the belief set `{'August 15', 'May 15'}` *believes* that Cheryl's birthday is one of those two days. A person *knows* the birthdate when they get down to a belief set with only one possibility. \n",
"Now we have to think really carefully about what we're doing. The puzzle is tricky because we're dealing with two types of uncertainty:\n",
"\n",
"We can define the idea of Cheryl **telling** someone a component of her birthdate, and while we're at it, the idea of **knowing** a birthdate:"
"1. Albert and Bernard are uncertain about the birthdate. *(Cheryl knows something they don't know.)*\n",
"2. We, the puzzle solvers don't know what Albert and Bernard were told. *(They know something we don't know.)*\n",
"\n",
"If Cheryl tells Albert \"May\", then he believes the birthdate could be either May 15, May 16, or May 19. We'll call `{'May 15', 'May 16', 'May 19'}` his **belief state** about the birthdate. We will say that a person **knows** the birthdate when they get down to a belief state with exactly one possibility. The type 2 uncertainty is that we don't know that Albert was told \"May\", so we have uncertainty about his belief state. But we do know some statements about his belief state, and our task is to use those statements to solve the puzzle. \n",
"\n",
"The way we will deal with our uncertainty as puzzle solvers is by considering each of the ten dates one at a time and reasoning as follows: \n",
"- If this date were Cheryl's true birthdate, then we would know what Albert and Bernard were told: we would eliminate the type 2 uncertainty, and we could figure out their belief states. \n",
"- From that we could figure out if the statements are true (given this date). \n",
"- If the puzzle is correct and we don't make mistakes, then there will be only one date that makes all the statements true; that's Cheryl's birthday.\n",
"\n",
"We can define the idea of Cheryl having **told** someone a component of her birthdate, and the idea of **knowing** a birthdate as follows:"
]
},
{
@ -116,14 +125,14 @@
"metadata": {},
"outputs": [],
"source": [
"BeliefSet = set\n",
"BeliefState = set\n",
"\n",
"def tell(part, dates=dates) -> BeliefSet:\n",
" \"Cheryl tells a part of her birthdate to someone; return a set of possible dates.\"\n",
"def told(part: str) -> BeliefState:\n",
" \"\"\"Cheryl told a part of her birthdate to someone; return a belief state of possible dates.\"\"\"\n",
" return {date for date in dates if part in date}\n",
"\n",
"def know(beliefs) -> bool:\n",
" \"A person `knows` the answer if their belief set has only one possibility.\"\n",
"def know(beliefs: BeliefState) -> bool:\n",
" \"\"\"A person `knows` the answer if their belief state has only one possibility.\"\"\"\n",
" return len(beliefs) == 1"
]
},
@ -131,7 +140,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"For example: If Cheryl tells Albert that her birthday is in May, he would know there is a set of three possible birthdates:"
"(Note: one thing I dislike about my code is that `told` uses the global `dates`. Later we will see that `cheryls_birthday` does the same. For that to be an acceptable design choice, we need to think of `dates` as a constant, not a variable.)\n",
"\n",
"Let's see what happens as we consider the date `'May 15'`:\n",
"\n",
"Cheryl tells Albert `'May'` and Bernard `'15'`, giving them these belief states: "
]
},
{
@ -151,14 +164,7 @@
}
],
"source": [
"tell('May')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And if she tells Bernard that her birthday is on the 15th, he would know there are two possibilities:"
"told('May')"
]
},
{
@ -178,14 +184,14 @@
}
],
"source": [
"tell('15')"
"told('15')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With two possibilities, Bernard does not know the birthdate:"
"We can then check whether Albert and Bernard's statements are true, given this date. The first part of Albert's first statement is \"I don't know when Cheryl's birthday is.\" We can verify that that part of the statement is true (given that Albert was told \"May\"):"
]
},
{
@ -196,7 +202,7 @@
{
"data": {
"text/plain": [
"False"
"True"
]
},
"execution_count": 8,
@ -205,20 +211,25 @@
}
],
"source": [
"know(tell('15'))"
"not know(told('May'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Overall Strategy\n",
"If the rest of the statements worked out to be true, then `'May 15'` would be a solution to the puzzle. If not, it must be one of the other dates."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Overall Strategy\n",
"\n",
"If Cheryl tells Albert `'May'` then *he* knows there are three possibilities, but *we* (the puzzle solvers) don't know that, because we don't know what Cheryl said. \n",
"Here is the main function, `cheryls_birthday`, which computes the subset of dates in the global variable `dates` that satisfy statements 3 through 5. We will define a **statement** as a boolean function that takes a single date as input and returns true if the statement would be true in the condition that the given date is Cheryl's actual birthday. So a statement only has to consider one date at a time. But the code within a statement may have to consider the belief states of Albert and Bernard, to determine if the statement is true.\n",
"\n",
"So what can we do? We can consider *all* of the possible dates, one at a time. For example, first consider `'May 15'`. Cheryl tells Albert `'May'` and Bernard `'15'`, giving them the lists of possible birthdates shown above. We can then check whether statements 3 through 5 are true in this scenario. If they are, then `'May 15'` is a solution to the puzzle. Repeat the process for each of the other possible dates. If all goes well, there should be exactly one date for which all the statements are true. \n",
"\n",
"Here is the main function, `cheryls_birthday`, which takes a set of possible dates, and returns the subset of dates that satisfy statements 3 through 5. The function `satisfy` is similar to the builtin function `filter`: `satisfy` takes a collection of items (here a set of dates) and returns the subset that satisfies all the predicates:"
"The function `satisfy` takes a collection of dates and some statement(s) and returns the subset of dates that satisfies all the statements."
]
},
{
@ -227,16 +238,14 @@
"metadata": {},
"outputs": [],
"source": [
"def cheryls_birthday(dates=dates) -> BeliefSet:\n",
" \"Return a subset of the dates for which all three statements are true.\"\n",
" return satisfy(dates, statement3, statement4, statement5)\n",
"def cheryls_birthday() -> BeliefState:\n",
" \"\"\"Return a subset of the global `dates` for which all three statements are true.\"\"\"\n",
" return satisfy(dates, albert1, bernard1, albert2)\n",
"\n",
"def satisfy(items, *predicates):\n",
" \"Return the subset of items that satisfy all the predicates.\"\n",
" return {item for item in items\n",
" if all(pred(item) for pred in predicates)}\n",
"\n",
"## TO DO: define statement3, statement4, statement5"
"def satisfy(some_dates, *statements) -> BeliefState:\n",
" \"\"\"Return the subset of dates that satisfy all the statements.\"\"\"\n",
" return {date for date in some_dates\n",
" if all(statement(date) for statement in statements)}"
]
},
{
@ -250,9 +259,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The function `statement3` corresponds to the third statement in the problem. It takes as input a single possible birthdate (not a set) and returns `True` if Albert's statement is true for that birthdate. How do we go from Albert's English statement to a Python function? Let's paraphrase it in a form that uses the concepts we have defined:\n",
"The function `albert1` takes as input a single possible birthdate and returns `True` if Albert's statement is true for that birthdate. How do we go from Albert's English statement to a Python function? Let's paraphrase it in a form that uses the concepts we have defined:\n",
"\n",
"> **Albert**: After Cheryl told me the month of her birthdate, I didn't know her birthday. But for *any* of the possible dates, if Bernard was told the day of that date, he would not know Cheryl's birthday.\n",
"> **Albert**: After Cheryl **told** me the **month** of her birthdate, my **belief state** was such that I didn't **know** her birthday. And I know that Bernard does not know; in other words he could not make the statement that he knows. How do I know that? I can see that for all the possible dates in my **belief state**, if Bernard was **told** the **day** of that date, he would **not know** Cheryl's birthday.\n",
"\n",
"That I can translate directly into code:"
]
@ -263,18 +272,19 @@
"metadata": {},
"outputs": [],
"source": [
"def statement3(date) -> bool:\n",
" \"Albert: I don't know when Cheryl's birthday is, but I know that Bernard does not know too.\"\n",
" dates = tell(Month(date))\n",
" return (not know(dates) \n",
" and all(not know(tell(Day(d))) for d in dates))"
"def albert1(date) -> bool:\n",
" \"Albert: I don't know when Cheryl's birthday is, and I know that Bernard does not know.\"\n",
" albert_beliefs = told(month(date))\n",
" return not know(albert_beliefs) and not satisfy(albert_beliefs, bernard_knows)\n",
"\n",
"def bernard_knows(date) -> bool: return know(told(day(date))) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We haven't solved the puzzle yet, but let's take a peek and see which dates satisfy statement 3:"
"We haven't solved the puzzle yet, but let's take a peek and see which dates satisfy Albert's statement:"
]
},
{
@ -294,7 +304,7 @@
}
],
"source": [
"satisfy(dates, statement3)"
"satisfy(dates, albert1)"
]
},
{
@ -305,7 +315,7 @@
"\n",
"Again, a paraphrase:\n",
"\n",
"> **Bernard:** At first Cheryl told me the day, and I didn't know. Then, out of the possible dates, I considered just the dates for which Albert's statement 3 is true, and now I know."
"> **Bernard:** At first Cheryl **told** me the **day**, and I didn't **know**. After I heard Albert's **statement**, I updated my **belief state**, and now I **know**."
]
},
{
@ -314,17 +324,18 @@
"metadata": {},
"outputs": [],
"source": [
"def statement4(date):\n",
"def bernard1(date) -> bool:\n",
" \"Bernard: At first I don't know when Cheryl's birthday is, but I know now.\"\n",
" dates = tell(Day(date))\n",
" return (not know(dates) and know(satisfy(dates, statement3)))"
" at_first_beliefs = told(day(date))\n",
" after_beliefs = satisfy(at_first_beliefs, albert1)\n",
" return not know(at_first_beliefs) and know(after_beliefs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's see which dates satisfy both statement 3 and statement 4:"
"Let's see which dates satisfy both Albert and Bernard's statements:"
]
},
{
@ -344,18 +355,26 @@
}
],
"source": [
"satisfy(dates, statement3, statement4)"
"satisfy(dates, albert1, bernard1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wait a minute&mdash;I thought that Bernard **knew**?! Why are there three possible dates? Bernard does indeed know; it is just that we, the puzzle solvers, don't know. That's because Bernard knows something we don't know: the day. If Bernard was told `'15'` then he would know `'August 15'`; if he was told `'17'` he would know `'August 17'`, and if he was told `'16'` he would know `'July 16'`. *We* don't know because we don't know which of these is the case.\n",
"Wait a minute&mdash;I thought that Bernard **knew**?! Why are there three possible dates? \n",
"\n",
"Bernard does indeed know; it is just that we, the puzzle solvers, don't know. That's because Bernard knows something we don't know: the day. If Bernard was told `'15'` then he would know `'August 15'`; if he was told `'17'` he would know `'August 17'`, and if he was told `'16'` he would know `'July 16'`. *We* don't know because we don't know which of these is the case. We'll need more information (coming in statement `albert2`) before *we* know.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Albert: Then I also know when Cheryl's birthday is.\n",
"\n",
"Albert is saying that after hearing the month and Bernard's statement 4, he now knows Cheryl's birthday:"
"Albert is saying that after hearing the month and Bernard's statement, he now knows Cheryl's birthday:"
]
},
{
@ -364,9 +383,10 @@
"metadata": {},
"outputs": [],
"source": [
"def statement5(date):\n",
" \"Albert: Then I also know when Cheryl's birthday is.\"\n",
" return know(satisfy(tell(Month(date)), statement4))"
"def albert2(date) -> bool:\n",
" \"Albert: Then I also know when Cheryl's birthday is.\" \n",
" then = satisfy(told(month(date)), bernard1)\n",
" return know(then)"
]
},
{
@ -423,6 +443,97 @@
"source": [
"know(cheryls_birthday())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"___\n",
"\n",
"# New Puzzle: Steve's Bus\n",
"\n",
"Here's [another puzzle](https://www.reddit.com/r/riddles/comments/fw7h42/a_riddle_i_couldnt_solve/) that seems to have a very similar format:\n",
"\n",
"> 1. Steve tells Alice the hour of his bus departure and he tells Annie at which minute it leaves. He also tells them both that the bus leaves between 06:00 and 10:00.\n",
"> 2. Alice and Annie consult the timetable and find the following services between those two time: 06:32 06:43 06:50 07:17 07:46 08:19 08:32 09:17 09:19 09:50.\n",
"> 3. Alice then says “I dont know when Steves bus leaves but I am sure that neither does Annie”\n",
"> 4. Annie Replies “I didnt know his bus, but now I do”\n",
"> 5. Alice responds “Now I do as well!”\n",
"> 6. When is Steves bus?\n",
"\n",
"Upon closer inspection, not only is it a similar format, it is **exactly** the same puzzle, except that months are changed to hours and days to minutes. If we rewrite the times in the same format as `dates`, we can solve the problem without writing a single line of new code:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'08 32'}"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"times = '06:32 06:43 06:50 07:17 07:46 08:19 08:32 09:17 09:19 09:50'.split()\n",
"dates = [time.replace(':', ' ') for time in times]\n",
"\n",
"cheryls_birthday()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Steve took the 8:32 bus."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Another New Puzzle: Evil Mad Scientist Cheryl\n",
"\n",
"![](https://norvig.com/images/cheryl-trolley.png)\n",
"\n",
"Again, we can solve this problem just by changing the format of `dates`:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'C 3'}"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pads = 'A2 A3 A6 B4 B5 C1 C3 D1 D2 D4'.split()\n",
"dates = [L+' '+N for L,N in pads]\n",
"\n",
"cheryls_birthday()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Mad scientist Cheryl was refering to pad C3. (But may I point out that this Cheryl is not actually a mad scientist, just a [mad engineer](https://www.evilmadscientist.com/2015/evil-mad-engineers/). A true mad scientist would kill 25 people and use the other 25 as a control group.)"
]
}
],
"metadata": {
@ -441,7 +552,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.0"
"version": "3.7.7"
}
},
"nbformat": 4,

View File

@ -27,7 +27,7 @@
"source": [
"Pound a bunch of nails into a board, then stretch a rubber band around them and let the rubber band snap taut, like this:\n",
"\n",
"<img src=\"http://www.personal.kent.edu/~rmuhamma/Compgeometry/MyCG/Gifs-CompGeometry/ch2.gif\">\n",
"<img src=\"convexhull.jpg\">\n",
"\n",
"The rubber band has traced out the *convex hull* of the set of nails. It turns out this is an important problem with applications in computer graphics, robot motion planning, geographical information systems, ethology, and other areas.\n",
"More formally, we say that:\n",

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,12 +12,12 @@
" gives state-by-state, month-by-month presidential approval poll data. Within the web page there is some Javascript from which\n",
" we can extract the data we need. It looks like this:\n",
"\n",
" var mc_state_trend = [[\"Demographic\",\"January 1, 2017\",\"February 1, 2017\", ...]\n",
" var mc_state_trend = [[\"Demographic\",\"Jan-17\",\"\",\"Feb-17\",\"\", ...]\n",
" [\"Alabama\",\"62\",\"26\",\"65\",\"29\", ...], \n",
" ... ]\n",
" \n",
"The first row is a header (each date is a day at which polls were aggregated).\n",
"The subsequent rows each start with the state name, followed by the approval and disapproval percentages for each date. That is, if there are 34 dates, there will by 68 numbers. The row shown above is saying that on January 1, 2017, 62% of Alabamans approved and 26% disapproved; then on February 1, 2017, 65% approved and 29% disapproved, and so on. Our job is to extract this data and find ways to visualize and understand it.\n",
"The first row is a header (each date is a month at which polls were aggregated).\n",
"The subsequent rows each start with the state name, followed by the approval and disapproval percentages for each date. That is, if there are 34 dates, there will by 68 numbers. The row shown above is saying that in January, 2017, 62% of Alabamans approved and 26% disapproved; then in February, 2017, 65% approved and 29% disapproved, and so on. Our job is to extract this data and find ways to visualize and understand it.\n",
"\n",
"First fetch the page and save it locally:"
]
@ -57,7 +57,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Additional data: the variable `state_data` contains the [electoral votes by state](https://www.britannica.com/topic/United-States-Electoral-College-Votes-by-State-1787124) and the [partisan lean by state](https://github.com/fivethirtyeight/data/tree/master/partisan-lean) (how much more Republican (plus) or Democratic (minus) leaning the state is compared to the country as a whole, across recent elections). The variable `net_usa` has the [country-wide net presidential approval](https://projects.fivethirtyeight.com/trump-approval-ratings/) by month."
"Additional data: the variable `state_data` contains the [electoral votes by state](https://www.britannica.com/topic/United-States-Electoral-College-Votes-by-State-1787124) and the [partisan lean by state](https://github.com/fivethirtyeight/data/tree/master/partisan-lean) (how much more Republican (plus) or Democratic (minus) leaning the state is compared to the country as a whole, across recent elections). \n",
"\n",
"The variable `net_usa` has the [country-wide net presidential approval](https://projects.fivethirtyeight.com/trump-approval-ratings/) by month."
]
},
{
@ -67,7 +69,7 @@
"outputs": [],
"source": [
"# From https://github.com/fivethirtyeight/data/tree/master/partisan+lean\n",
"# a dict of {\"state name\": (electoral_votes, partisan_lean)}\n",
"# A dict of {\"state name\": (electoral_votes, partisan_lean)}\n",
"state_data = { \n",
" \"Alabama\": (9, +27), \"Alaska\": (3, +15), \"Arizona\": (11, +9), \n",
" \"Arkansas\": (6, +24), \"California\": (55, -24), \"Colorado\": (9, -1), \n",
@ -88,20 +90,20 @@
" \"West Virginia\": (5, +30), \"Wisconsin\": (10, +1), \"Wyoming\": (3, +47)}\n",
"\n",
"# From https://projects.fivethirtyeight.com/trump-approval-ratings/\n",
"# A dict of {'date': country-wide-net-approval}\n",
"# A dict of {'date': country-wide-net-approval}, taken from 1st of month.\n",
"net_usa = {\n",
" 'January 2017': 10, 'January 2018': -18, 'January 2019': -12, \n",
" 'February 2017': 0, 'February 2018': -15, 'February 2019': -16, \n",
" 'March 2017': -6, 'March 2018': -14, 'March 2019': -11, \n",
" 'April 2017': -13, 'April 2018': -13, 'April 2019': -11, \n",
" 'May 2017': -11, 'May 2018': -12, 'May 2019': -12, \n",
" 'June 2017': -16, 'June 2018': -11, 'June 2019': -12, \n",
" 'July 2017': -15, 'July 2018': -10, 'July 2019': -11, \n",
" 'August 2017': -19, 'August 2018': -12, 'August 2019': -10, \n",
" 'September 2017': -20, 'September 2018': -14, 'September 2019': -13, \n",
" 'October 2017': -17, 'October 2018': -11, 'October 2019': -13, \n",
" 'November 2017': -19, 'November 2018': -11, 'November 2019': -13,\n",
" 'December 2017': -18, 'December 2018': -10, 'December 2019': -12,\n",
" 'Jan-17': 10, 'Jan-18': -18, 'Jan-19': -12, 'Jan-20': -11,\n",
" 'Feb-17': 0, 'Feb-18': -15, 'Feb-19': -16, 'Feb-20': -10,\n",
" 'Mar-17': -6, 'Mar-18': -14, 'Mar-19': -11, \n",
" 'Apr-17': -13, 'Apr-18': -13, 'Apr-19': -11, \n",
" 'May-17': -11, 'May-18': -12, 'May-19': -12, \n",
" 'Jun-17': -16, 'Jun-18': -11, 'Jun-19': -12, \n",
" 'Jul-17': -15, 'Jul-18': -10, 'Jul-19': -11, \n",
" 'Aug-17': -19, 'Aug-18': -12, 'Aug-19': -10, \n",
" 'Sep-17': -20, 'Sep-18': -14, 'Sep-19': -13, \n",
" 'Oct-17': -17, 'Oct-18': -11, 'Oct-19': -13, \n",
" 'Nov-17': -19, 'Nov-18': -11, 'Nov-19': -13,\n",
" 'Dec-17': -18, 'Dec-18': -10, 'Dec-19': -12,\n",
" }"
]
},
@ -146,7 +148,7 @@
"\n",
"def margin(states, date=now) -> int:\n",
" \"What's the least swing that would lead to a majority?\"\n",
" return min(swing for swing in range(-50, 50) if EV(states, date, swing) >= 270)\n",
" return min(swing for swing in range(-50, 50) if EV(states, date, swing+0.1) >= 270)\n",
"\n",
"def net(state, date=now) -> int: return state.approvals[date] - state.disapprovals[date]\n",
"def undecided(state, date=now) -> int: return 100 - state.approvals[date] - state.disapprovals[date]\n",
@ -164,24 +166,25 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def labels(xlab, ylab): plt.xlabel(xlab); plt.ylabel(ylab); plt.legend()\n",
"\n",
"def grid(): plt.minorticks_on(); plt.grid(which='minor', ls=':', alpha=0.7)\n",
" \n",
"def header(head) -> str: return head + '\\n' + '-'.join('|' * head.count('|'))\n",
"\n",
"def markdown(fn) -> callable: return lambda *args: display(Markdown('\\n'.join(fn(*args))))\n",
"\n",
"def parp(state, date=now) -> int: return net(state, date) - state.lean "
"def parp(state, date=now) -> int: return net(state, date) - state.lean\n",
"\n",
"def grid(dates, xlab, ylab): \n",
" plt.minorticks_on(); plt.grid(which='minor', axis='y', ls=':', alpha=0.7)\n",
" plt.xticks(range(len(dates)), dates, rotation=90)\n",
" plt.xlabel(xlab); plt.ylabel(ylab); plt.legend()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
@ -192,16 +195,16 @@
" N = len(dates)\n",
" err = [[EV(states, date) - EV(states, date, -swing) for date in dates],\n",
" [EV(states, date, +swing) - EV(states, date) for date in dates]]\n",
" grid()\n",
" plt.plot(range(N), [270] * N, color='darkorange', label=\"270 EVs\", lw=2)\n",
" plt.errorbar(range(N), [EV(states, date) for date in dates], fmt='D-',\n",
" yerr=err, ecolor='grey', capsize=7, label='Trump EVs ±3% swing', lw=2)\n",
" labels('Months into term', 'Electoral Votes')"
" grid(dates, 'Date', 'Electoral Vptes')\n",
" #labels('Date', 'Electoral Votes')"
]
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
@ -210,11 +213,23 @@
" plt.rcParams[\"figure.figsize\"] = [10, 7]\n",
" plt.style.use('fivethirtyeight')\n",
" N = len(dates)\n",
" grid()\n",
" plt.plot(range(N), [0] * N, label='Net zero', color='darkorange')\n",
" plt.plot(range(N), [-margin(states, date) for date in dates], 'D-', label='Margin to 270')\n",
" plt.plot(range(N), [net_usa[date] for date in dates], 'go-', label='Country-wide Net')\n",
" labels('Months into term', 'Net popularity')"
" grid(dates, 'Date', 'Net popularity')"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def show_swings(swings=range(10)):\n",
" print('Swing EV Range')\n",
" for swing in swings:\n",
" s = swing + 0.5\n",
" print(f'±{s:3.1f}% {EV(states, swing=-s):3} to {EV(states, swing=s):3}')"
]
},
{
@ -226,7 +241,7 @@
"outputs": [],
"source": [
"@markdown\n",
"def show_states(states=states, d=now, ref='January 2017'):\n",
"def show_states(states=states, d=now, ref='Jan-17'):\n",
" \"A table of states, sorted by net approval, with electoral votes.\"\n",
" total = 0\n",
" yield header(f'|State|Net|Move|EV|ΣEV|+||?|𝝈|')\n",
@ -246,7 +261,7 @@
"outputs": [],
"source": [
"@markdown\n",
"def show_parp(states=states, dates=(now, 'January 2019', 'January 2018', 'January 2017')):\n",
"def show_parp(states=states, dates=(now, 'Jan-19', 'Jan-18', 'Jan-17')):\n",
" \"A table of states, sorted by Popularity Above Replacement President.\"\n",
" def year(date): return '' if date == now else \"'\" + date[-2:]\n",
" fields = [f\"PARP{year(date)}|(Net)\" for date in dates]\n",
@ -260,7 +275,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tests** (I really should have more)."
"**Tests** (I really should have more):"
]
},
{

272
ipynb/ElementSpelling.ipynb Normal file
View File

@ -0,0 +1,272 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"text-align: right\"><i>Peter Norvig<br>March 2020</i></div>\n",
"\n",
"# Elemental Spelling\n",
"\n",
"Here's a problem: \n",
"\n",
"> Given a word, decide if it can be spelled using only the symbols in the **[periodic table](https://en.wikipedia.org/wiki/Periodic_table)** of elements. For example, the word \"bananas\" can be spelled with \"BaNaNaS\" (Barium-Sodium-Sodium-Sulfur). Note that there can be multiple possible spellings for a word&mdash;\"coin\" could be \"CoIn\" (Cobalt-Indium) or \"COIN\" (Carbon-Oxygen-Iodine-Nitrogen). \n",
"\n",
"Here is a sketch of a recursive algorithm to solve the problem. A word is **spellable** if any of the following are true:\n",
"- The word is the empty word.\n",
"- The first 2 letters of the word (capitalized) form an element symbol, and the rest of the word is spellable.\n",
"- The first 1 letter of the word (capitalized) forms an element symbol, and the rest of the word is spellable.\n",
"\n",
"The input to `spellable` should be a string and the output is a boolean. Here is the code:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"def spellable(word: str) -> bool:\n",
" \"\"\"Can we spell `word` using the `symbols` of the elements?\"\"\"\n",
" return (word == ''\n",
" or word[:2].capitalize() in symbols and spellable(word[2:])\n",
" or word[:1].capitalize() in symbols and spellable(word[1:]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I felt a bit bad about repeating a line of code above&mdash;violating [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)&mdash;but using a subfunction or `any/for` would add complexity. Here are the 118 currently defined `symbols`. (Note that the symbols are all capitalized, so I capitalize `[word[:2]` and `word[:1]` in `spellable` to make sure they match.)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [],
"source": [
"symbols = set( # Elements in the periodic table\n",
" 'Ac Al Am Sb Ar As At Ba Bk Be Bi Bh B Br Cd Ca Cf C Ce Cs Cl Cr Co Cn Cu Cm Ds Db '\n",
" 'Dy Es Er Eu Fm Fl F Fr Gd Ga Ge Au Hf Hs He Ho H In I Ir Fe Kr La Lr Pb Li Lv Lu '\n",
" 'Mg Mn Mt Md Hg Mo Mc Nd Ne Np Ni Nh Nb N No Og Os O Pd P Pt Pu Po K Pr Pm Pa Ra Rn '\n",
" 'Re Rh Rg Rb Ru Rf Sm Sc Sg Se Si Ag Na Sr S Ta Tc Te Ts Tb Tl Th Tm Sn Ti W U V Xe '\n",
" 'Yb Y Zn Zr'.split())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now| test the function (on `'Bananas'` and `'hello'`):"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spellable('Bananas')"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spellable('hello')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That was easy. \n",
"\n",
"But maybe you'd like to see the actual spelling:`'BaNaNaS'`. The function `spelling` does that. The general idea is the same, except:\n",
" - We use the subfunction `first_rest_spelling` rather than repeating code.\n",
" - Both `spelling` and `first_rest_spelling` return either a string (the spelling) or `None` if no spelling is possible.\n",
" - There might be multiple possible spellings; only one is returned.\n",
" - We use `lru_cache` to avoid repeated computation and thereby speed up the function."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
"from functools import lru_cache\n",
"\n",
"@lru_cache()\n",
"def spelling(word):\n",
" \"The spelling for `word` using `symbols` of the elements; or None if fail.\"\n",
" return '' if word == '' else first_rest_spelling(word, 2) or first_rest_spelling(word, 1)\n",
"\n",
"def first_rest_spelling(word, k):\n",
" \"Resulting spelling from taking off first k characters of word; or None if fail.\"\n",
" first, rest = word[:k].capitalize(), word[k:]\n",
" if first in symbols and spelling(rest) is not None:\n",
" return first + spelling(rest)\n",
" else:\n",
" return None"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'BaNaNaS'"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spelling('bananas')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Testing\n",
"\n",
"Here I define `bad`, a list of words that are **not** spellable, and `good`, a list of words that **are**, and make some assertions:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"bad = 'hello world failure not an alternative'.split() # Unspellable words\n",
"\n",
"good = '''howdy sphere falure is notan option bananas \n",
" carbon iron silver silicon copper arsenic tin xenon bismuth\n",
" attention copernicus inconspicuous hyperbolic orbits functions\n",
" wonky nutso officious psychic unprofessional bilateralism \n",
" whippersnappers vichyssois bobbysocks alterabilities capabilities\n",
" biostatistical physics floccinaucinihilipilification'''.split() # Spellable words\n",
"\n",
"assert len(symbols) == 118\n",
"assert not any(spellable(w) or spelling(w) for w in bad) \n",
"assert all(spellable(w) and spelling(w) for w in good)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And here are the actual spellings for the good words:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'AlTeRaBiLiTiEs',\n",
" 'ArSeNiC',\n",
" 'AtTeNTiON',\n",
" 'BOBBYSOCKS',\n",
" 'BaNaNaS',\n",
" 'BiLaTeRaLiSm',\n",
" 'BiOsTaTiSTiCAl',\n",
" 'BiSmUTh',\n",
" 'CaPaBiLiTiEs',\n",
" 'CaRbON',\n",
" 'CoPErNiCuS',\n",
" 'CoPPEr',\n",
" 'FAlURe',\n",
" 'FUNCTiONS',\n",
" 'FlOCCInAuCInIHILiPILiFICaTiON',\n",
" 'HYPErBOLiC',\n",
" 'HoWDy',\n",
" 'IS',\n",
" 'InCoNSPICuOUS',\n",
" 'IrON',\n",
" 'NUTsO',\n",
" 'NoTaN',\n",
" 'OFFICIOUS',\n",
" 'OPtION',\n",
" 'ORbITs',\n",
" 'PHYSiCs',\n",
" 'PSYCHIC',\n",
" 'SPHeRe',\n",
" 'SiLiCoN',\n",
" 'SiLvEr',\n",
" 'TiN',\n",
" 'UNPrOFeSSiONAl',\n",
" 'VICHYSSOIS',\n",
" 'WHIPPErSNaPPErS',\n",
" 'WONKY',\n",
" 'XeNoN'}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{spelling(w) for w in good}"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -2249,7 +2249,7 @@
}
},
"source": [
"A table and a plot will give a feel for the `util` function. Notice the characteristics concave-down shape of the plot."
"A table and a plot will give a feel for the `util` function. Notice the characterisitc concave-down shape of the plot."
]
},
{
@ -3094,7 +3094,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.3"
"version": "3.7.0"
}
},
"nbformat": 4,

File diff suppressed because one or more lines are too long

524
ipynb/RiddlerLottery.ipynb Normal file
View File

@ -0,0 +1,524 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"<div style=\"text-align:right\">Peter Norvig<br>Nov 2019</div>\n",
"\n",
"# Riddler Lottery\n",
"\n",
"The 538 Riddler [poses](https://fivethirtyeight.com/features/can-you-decode-the-riddler-lottery/) this problem:\n",
"\n",
"> Five friends with a lot in common are playing the [Riddler Lottery](https://fivethirtyeight.com/features/can-you-decode-the-riddler-lottery/), in which each must choose exactly five numbers from 1 to 70. After they all picked their numbers,\n",
"- The first friend notices that no number was selected by two or more friends. \n",
"- The second friend observes that all 25 selected numbers are composite (i.e., not prime). \n",
"- The third friend points out that each selected number has at least two distinct prime factors. \n",
"- The fourth friend excitedly remarks that the product of selected numbers on each ticket is exactly the same. \n",
"- The fifth friend is left speechless. (You can tell why all these people are friends.)\n",
"\n",
"> 1. What is the product of the selected numbers on each ticket?\n",
"2. How many _different_ ways could the friends have selected five numbers each so that all their statements are true?\n",
"\n",
"# Preliminary Analysis\n",
"\n",
"The fourth friend's statement was a bit unclear, but I take it to mean that each friend multiplied together their own five numbers, and they all got the same product. To be concrete, here's an example of a solution in a simplified version of the problem where each friend only selects two tickets, not five:\n",
"\n",
" Friend Selection Product Factors\n",
" 1 ( 6, 60) 360 [2, 3] + [2, 2, 3, 5]\n",
" 2 (10, 36) 360 [2, 5] + [2, 2, 3, 3]\n",
" 3 (12, 30) 360 [2, 2, 3] + [2, 3, 5]\n",
" 4 (15, 24) 360 [3, 5] + [2, 2, 2, 3]\n",
" 5 (18, 20) 360 [2, 3, 3] + [2, 2, 5]\n",
"\n",
"And here's a list of the key concepts:\n",
"\n",
"- **number**: An integer from 1 to 70, e.g. the int `42`.\n",
"- **factors**: Every positive integer has a unique prime factorization, e.g. `factors(12) == [2, 2, 3]`: two distinct primes factors. But `factors(8) == [2, 2, 2]`: one distinct prime factor.\n",
"- **selection**: A collection of 5 numbers, e.g. the sorted tuple `(12, 15, 20, 28, 30)`.\n",
"- **product**: The result of multiplying together the 5 numbers in a selection, e.g. the int `3024000`.\n",
"- **candidate**: A candidate solution is a set of 5 selections e.g. `{( 6, 60), (10, 36), (12, 30), (15, 24), (18, 20)}` in my simplified version where each selection has only two numbers.\n",
"- **solution**: A solution is a candidate that satisifes each of the four friends' statements.\n",
"\n",
"Can I use brute force and enumerate all the possible candidates? \n",
"\n",
"There are (70 choose 5) × (65 choose 5) × (60 choose 5) × (55 choose 5) × (50 choose 5) / 5! or [about](https://www.wolframalpha.com/input/?i=%2870+choose+5%29+*+%2865+choose+5%29+*+%2860+choose+5%29+*+%2855+choose+5%29+*+%2850+choose+5%29+%2F+5%21) $10^{31}$ candidates, so no. We'll have to be more clever."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Valid Numbers\n",
"\n",
"There will be fewer candidates to consider if we can reduce the number of valid numbers to select from. The __third__ friend stated that the numbers all have at least two distinct prime factors. So let's find the numbers that have that property. (The numbers we are dealing with are small, so don't worry about the inefficiency of my function `factors`.)"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from itertools import combinations\n",
"from collections import Counter, defaultdict"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"41 {6, 10, 12, 14, 15, 18, 20, 21, 22, 24, 26, 28, 30, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45, 46, 48, 50, 51, 52, 54, 55, 56, 57, 58, 60, 62, 63, 65, 66, 68, 69, 70}\n"
]
}
],
"source": [
"def factors(n) -> list:\n",
" \"List of prime factors that multiply together to give n.\"\n",
" return ([] if n == 1\n",
" else next([p] + factors(n // p) \n",
" for p in range(2, n + 1) if n % p == 0))\n",
"\n",
"distinct = set\n",
"\n",
"numbers = {n for n in range(1, 71) if len(distinct(factors(n))) >= 2}\n",
"\n",
"print(len(numbers), numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Great; we got it down from 70 to 41 possible numbers.\n",
"\n",
"Now the __fourth__ friend's statement says that each friend picks five numbers that have the same product. \n",
"In my simplified version where the friends pick two numbers each, they all picked a selection with the product 360. The prime factorization of 360 is `[2, 2, 2, 3, 3, 5]`; that means that all five friends had to find a different way of allocating these factors to their numbers. \n",
"\n",
"For each number that is selected by any friend, and for each prime factor $p$ of that number, it must be the case that there are at least four other valid numbers that also have $p$ as a factor; otherwise the product couldn't be the same for all the friends. So let's count in how many numbers each prime appears:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Counter({2: 29,\n",
" 3: 20,\n",
" 5: 12,\n",
" 7: 8,\n",
" 11: 5,\n",
" 13: 4,\n",
" 17: 3,\n",
" 19: 2,\n",
" 23: 2,\n",
" 29: 1,\n",
" 31: 1})"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prime_counts = Counter(p for n in numbers for p in distinct(factors(n)))\n",
"\n",
"prime_counts"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This says that the prime factor 2 appears in 29 valid numbers and the prime factor 31 appears in only 1 valid number. Only factors that appear in at least 5 valid numbers can be part of a solution, so that's `{2, 3, 5, 7, 11}`.\n",
"\n",
"Let's update the set of valid numbers to contain only numbers $n$ such that every prime factor $p$ of $n$ appears in at least 5 valid numbers:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"28 {6, 10, 12, 14, 15, 18, 20, 21, 22, 24, 28, 30, 33, 35, 36, 40, 42, 44, 45, 48, 50, 54, 55, 56, 60, 63, 66, 70}\n"
]
}
],
"source": [
"numbers = {n for n in numbers if all(prime_counts[p] >= 5 for p in distinct(factors(n)))}\n",
"\n",
"print(len(numbers), numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are now only 28 valid numbers; a nice reduction from 70 to 41 to 28.\n",
"\n",
"# Valid Selections\n",
"\n",
"Now let's switch attention from individual numbers to selections of five numbers. There are (28 choose 5) = 98,280 possible selections; a manageable number. But that means there are (98,280 choose 5) = $\\approx 10^{23}$ candidate solutions; an unmanageable number.\n",
"\n",
"My first thought to reduce the number of candidates is to say that we should only consider candidates where all five selections in the candidate have the same product. To do that, we can group selections by product.\n",
"\n",
"We'll make `products` be a `dict` where each key is the product of the five numbers in a selection, and the corresponding value is a list of all the selections of five numbers with that product:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def multimap(items, key) -> dict:\n",
" \"A dict of {key(item): [item, ...]}\"\n",
" result = defaultdict(list)\n",
" for x in items:\n",
" result[key(x)].append(x)\n",
" return result\n",
"\n",
"def product(nums) -> int: \n",
" \"Multiply nums together (similar to sum(nums)).\"\n",
" result = 1\n",
" for num in nums:\n",
" result *= num\n",
" return result\n",
"\n",
"products = multimap(combinations(numbers, 5), key=product)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is an entry in the `products` dict:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[(6, 10, 12, 14, 30),\n",
" (6, 10, 12, 15, 28),\n",
" (6, 10, 12, 20, 21),\n",
" (6, 10, 14, 15, 24),\n",
" (6, 10, 14, 18, 20),\n",
" (6, 12, 14, 15, 20)]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"products[6 * 12 * 14 * 15 * 20]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This says there are 6 selections whose product is 6 * 12 * 14 * 15 * 20 = 302,400; if there were a way to choose 5 of these 6 with all distinct numbers, that would be a solution. Sadly, there is no such way. Since every one of the selections contains a 6; we can't even choose two disjoint selections, let alone five.\n",
"\n",
"Let's see how many different products there are, and how many have at least five selections:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(4042, 2407)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(products), len([n for n in products if len(products[n]) >= 5])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It seems reasonable to go through all the products and see if one of the 29,506 with 5 or more selections can come up with 5 disjoint selections. The function `k_disjoint(k, selections)` finds all ways to choose `k` different elements of `selections` such that there is no shared number among any selection. The function keeps track of a `partial_solution`&mdash;a set of previously-found selections&mdash;as it recursively searches for a complete solution. Any new selection must be disjoint from all the selections in `partial_solution`. "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def k_disjoint(k, selections, start=0, partial_solution=set()) -> list:\n",
" \"All ways of picking k elements of selections that have all disjoint members.\"\n",
" if len(partial_solution) == k:\n",
" yield partial_solution\n",
" elif len(partial_solution) + (len(selections) - start) >= k:\n",
" for i in range(start, len(selections)):\n",
" selection = selections[i]\n",
" if all(is_disjoint(selection, s) for s in partial_solution):\n",
" yield from k_disjoint(k, selections, i + 1, partial_solution | {selection})\n",
" \n",
"def is_disjoint(A, B) -> bool: return not any(a in B for a in A)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here's an example of how `k_disjoint` works. Out of the following six selections (which in this simplified example have only two numbers each, not five), there are two ways to pick five selections without having a duplicate number:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{(6, 60), (10, 36), (12, 30), (15, 24), (18, 20)},\n",
" {(6, 60), (10, 35), (12, 30), (15, 24), (18, 20)}]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"selections = [(6, 60), (10, 36), (12, 30), (15, 24), (18, 20), (10, 35)]\n",
"list(k_disjoint(5, selections))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But for the six selections whose product is 302,400, there are no disjoint solutions:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"selections = [(6, 10, 12, 14, 30),\n",
" (6, 10, 12, 15, 28),\n",
" (6, 10, 12, 20, 21),\n",
" (6, 10, 14, 15, 24),\n",
" (6, 10, 14, 18, 20),\n",
" (6, 12, 14, 15, 20)]\n",
"list(k_disjoint(5, selections))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we're ready to solve the problem.\n",
"\n",
"# 1. What is the product of the selected numbers on each ticket?\n",
"\n",
"That is, find the number `N` in `products` that can form 5 disjoint selections."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The product is 19,958,400; factors are [2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 5, 5, 7, 11]\n"
]
}
],
"source": [
"N = next(n for n in products if any(k_disjoint(5, products[n])))\n",
"\n",
"print(f'The product is {N:,d}; factors are {factors(N)}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 2. How many different ways could the friends have selected five numbers?\n",
"\n",
"I'll compute all of the results for `k_disjoint`, and see how many there are:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"There are 12,781 different ways.\n"
]
}
],
"source": [
"different_ways = list(k_disjoint(5, products[N]))\n",
"print(f'There are {len(different_ways):,d} different ways.')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That's too many to look at all of them, but I can peek at every thousandth one:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{(6, 15, 56, 60, 66),\n",
" (10, 14, 48, 54, 55),\n",
" (12, 18, 42, 44, 50),\n",
" (20, 21, 33, 36, 40),\n",
" (22, 24, 28, 30, 45)},\n",
" {(6, 18, 55, 56, 60),\n",
" (10, 21, 30, 48, 66),\n",
" (12, 22, 28, 50, 54),\n",
" (14, 20, 36, 44, 45),\n",
" (15, 24, 33, 40, 42)},\n",
" {(6, 20, 54, 55, 56),\n",
" (10, 21, 36, 44, 60),\n",
" (12, 28, 33, 40, 45),\n",
" (14, 18, 24, 50, 66),\n",
" (15, 22, 30, 42, 48)},\n",
" {(6, 21, 48, 50, 66),\n",
" (10, 24, 33, 45, 56),\n",
" (12, 18, 28, 55, 60),\n",
" (14, 20, 30, 44, 54),\n",
" (15, 22, 36, 40, 42)},\n",
" {(6, 22, 50, 54, 56),\n",
" (10, 14, 36, 60, 66),\n",
" (12, 18, 40, 42, 55),\n",
" (15, 21, 30, 44, 48),\n",
" (20, 24, 28, 33, 45)},\n",
" {(6, 24, 42, 50, 66),\n",
" (10, 18, 33, 56, 60),\n",
" (12, 28, 30, 36, 55),\n",
" (14, 15, 40, 44, 54),\n",
" (20, 21, 22, 45, 48)},\n",
" {(6, 28, 30, 60, 66),\n",
" (10, 15, 44, 54, 56),\n",
" (12, 22, 36, 42, 50),\n",
" (14, 24, 33, 40, 45),\n",
" (18, 20, 21, 48, 55)},\n",
" {(6, 28, 40, 45, 66),\n",
" (10, 30, 33, 42, 48),\n",
" (12, 21, 24, 55, 60),\n",
" (14, 18, 36, 44, 50),\n",
" (15, 20, 22, 54, 56)},\n",
" {(6, 28, 44, 50, 54),\n",
" (10, 21, 33, 48, 60),\n",
" (12, 14, 40, 45, 66),\n",
" (15, 22, 30, 36, 56),\n",
" (18, 20, 24, 42, 55)},\n",
" {(6, 30, 36, 55, 56),\n",
" (10, 15, 42, 48, 66),\n",
" (12, 28, 33, 40, 45),\n",
" (14, 20, 22, 54, 60),\n",
" (18, 21, 24, 44, 50)},\n",
" {(6, 30, 44, 45, 56),\n",
" (10, 15, 42, 48, 66),\n",
" (12, 14, 40, 54, 55),\n",
" (18, 24, 28, 33, 50),\n",
" (20, 21, 22, 36, 60)},\n",
" {(6, 33, 40, 45, 56),\n",
" (10, 12, 42, 60, 66),\n",
" (14, 22, 24, 50, 54),\n",
" (15, 28, 30, 36, 44),\n",
" (18, 20, 21, 48, 55)},\n",
" {(6, 36, 40, 42, 55),\n",
" (10, 20, 33, 54, 56),\n",
" (12, 18, 28, 50, 66),\n",
" (14, 15, 44, 45, 48),\n",
" (21, 22, 24, 30, 60)}]"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"different_ways[::1000]"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because one or more lines are too long

1532
ipynb/SpellingBee.ipynb Normal file

File diff suppressed because it is too large Load Diff

639
ipynb/StableMatching.ipynb Normal file

File diff suppressed because one or more lines are too long

477
ipynb/TourDe538.ipynb Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,440 +1,456 @@
Ride Thu, 6/14/2012 Coyote Creek Century with Juliet 8:08:15 100.07 mi 1,513 ft
Ride Sat, 5/13/2017 Morgan Hill iCare Classic 7:27:21 100.05 mi 4,596 ft
Ride Sat, 5/12/2018 ICare Classic, Morgan Hill 6:47:46 91.29 mi 4,160 ft
Ride Sat, 5/6/2017 Wine Country Century 7:15:22 89.49 mi 5,246 ft
Ride Fri, 8/10/2018 Bike Ride Northwest Day 6 6:14:18 84.70 mi 4,380 ft
Ride Sat, 10/1/2016 Half Moon Bay overnight campout 7:30:38 80.07 mi 6,039 ft
Ride Tue, 8/7/2018 Bike Ride Northwest Day 3 6:10:39 78.96 mi 5,092 ft
Ride Sun, 6/15/2014 Sierra to the Sea Day 1 5:34:06 78.53 mi 4,777 ft
Ride Sat, 4/23/2016 Wildflower Century 5:36:10 77.22 mi 4,193 ft
Ride Mon, 6/16/2014 Sierra to the Sea Day 2 5:35:06 74.68 mi 2,451 ft
Ride Fri, 8/12/2016 Half Moon Bay / Harvey's 73/7300 Birthday Ride 6:44:14 74.35 mi 6,610 ft
Ride Wed, 10/14/2015 Half Moon Bay 6:07:55 72.97 mi 7,644 ft
Ride Fri, 1/27/2017 Morning Ride 5:16:50 70.07 mi 2,539 ft
Ride Tue, 6/17/2014 Sierra to the Sea Day 3 4:48:41 68.64 mi 825 ft
Ride Thu, 8/9/2018 Bike Ride Northwest Day 5 4:58:07 68.41 mi 3,862 ft
Ride Sat, 4/15/2017 Pescadero 6:13:12 68.34 mi 6,130 ft
Ride Sun, 6/4/2017 Sequoia Challenge 6:17:25 66.52 mi 7,520 ft
Ride Wed, 8/5/2015 08/05/2015 Palo Alto, CA 4:56:15 66.33 mi 2,054 ft
Ride Fri, 8/28/2015 Pescadaro via OLH 5:18:51 66.01 mi 6,137 ft
Ride Fri, 11/20/2015 Los Gatos 5:29:40 65.73 mi 5,380 ft
Ride Sun, 6/3/2018 The Sequoia 5:58:15 64.92 mi 6,677 ft
Ride Thu, 6/19/2014 Sierra to the Sea Day 5 4:32:55 63.69 mi 2,584 ft
Ride Mon, 8/3/2015 08/03/2015 Palo Alto, CA 4:45:09 63.61 mi 1,877 ft
Ride Fri, 6/10/2016 Morning Ride 4:21:38 62.84 mi 3,196 ft
Ride Sun, 6/2/2013 Woodside Loop and Baylands 4:27:42 62.14 mi 2,169 ft
Ride Wed, 6/12/2013 ride 4:39:46 61.39 mi 2,207 ft
Ride Sat, 4/18/2015 Tunitas + Lobitos Creeks 5:14:38 61.27 mi 6,611 ft
Ride Sat, 11/11/2017 Los Gatos / Bay Trail 4:26:24 60.74 mi 1,316 ft
Ride Sun, 4/17/2016 La Honda / Skyline 5:10:41 60.35 mi 4,551 ft
Ride Mon, 7/3/2017 Los Gatos / Bay Trail 4:25:41 60.28 mi 1,329 ft
Ride Sat, 4/16/2016 OLH - San Gregorio - Tunitas 4:57:40 60.12 mi 4,744 ft
Ride Sun, 1/29/2017 Los Gatos / Guadalupe / San Tomas / Bay Trails 4:33:38 60.11 mi 1,447 ft
Ride Mon, 11/26/2018 Lunch Ride Los Gatos 4:21:35 60.03 mi 1,070 ft
Ride Sat, 10/31/2015 Saratoga Ramble 5:01:57 59.10 mi 3,528 ft
Ride Sat, 8/18/2018 Tour de Menlo 4:03:48 58.81 mi 2,467 ft
Ride Sat, 4/26/2014 OLH - Tunitas Creek 5:15:26 58.69 mi 6,742 ft
Ride Sat, 6/15/2013 Palo Alto to Santa Cruz 4:27:54 58.42 mi 4,431 ft
Ride Sat, 10/11/2014 OLH / La Honda / Tunitas 5:05:23 58.29 mi 6,044 ft
Ride Sat, 7/9/2016 Lunch Ride 3:50:23 58.23 mi 4,042 ft
Ride Wed, 6/18/2014 Sierra to the Sea Day 4 4:57:53 57.64 mi 5,561 ft
Ride Tue, 7/7/2015 07/07/2015 Palo Alto, CA 4:13:20 57.60 mi 1,280 ft
Ride Mon, 8/6/2018 Bike Ride Northwest Day 2 4:35:48 57.58 mi 4,514 ft
Ride Fri, 6/20/2014 Sierra to the Sea Day 6 4:33:39 56.91 mi 4,453 ft
Ride Sat, 6/10/2017 Los Gatos / Creek Trails 3:50:24 56.28 mi 1,365 ft
Ride Sat, 3/4/2017 Lunch Ride 3:58:25 56.26 mi 1,378 ft
Ride Sun, 8/5/2018 Bike Ride Northwest Day 1 3:34:42 55.77 mi 1,824 ft
Ride Sat, 8/13/2016 Petaluma - Point Reyes 4:30:12 54.75 mi 5,286 ft
Ride Sun, 6/7/2015 Tour de Cure 3:59:47 54.65 mi 2,748 ft
Ride Wed, 12/16/2015 Los Gatos 4:00:41 53.86 mi 2,595 ft
Ride Fri, 6/2/2017 Morning Ride 3:57:45 53.49 mi 1,375 ft
Ride Sun, 6/8/2014 Tour de Cure 75K 4:03:05 53.10 mi 2,596 ft
Ride Sun, 11/6/2016 Los Gatos 3:38:28 52.49 mi 1,263 ft
Ride Fri, 2/10/2017 Morning Ride 3:52:39 52.02 mi 1,739 ft
Ride Sat, 2/23/2019 Crystal Springs Dam 4:02:11 51.93 mi 1,946 ft
Ride Fri, 8/11/2017 Saratoga with Peter H 4:30:31 51.74 mi 2,871 ft
Ride Sat, 2/4/2017 Canada Rd 3:46:34 51.66 mi 1,762 ft
Ride Sun, 6/9/2013 Silicon Valley Tour de Cure 75K 3:58:37 51.63 mi 2,929 ft
Ride Sun, 9/3/2017 Morning Ride 4:22:45 51.31 mi 2,526 ft
Ride Mon, 7/27/2015 Palo Alto Cycling 3:48:23 50.93 mi 1,306 ft
Ride Wed, 7/29/2015 07/29/2015 Palo Alto, CA 3:47:53 50.92 mi 1,873 ft
Ride Sun, 6/26/2016 Afternoon Ride 3:16:33 50.78 mi 1,181 ft
Ride Mon, 8/10/2015 08/10/2015 Palo Alto, CA 3:41:54 50.73 mi 1,325 ft
Ride Sat, 10/15/2016 Los Gatos 3:28:16 50.64 mi 1,368 ft
Ride Tue, 12/19/2017 Los Gatos 3:46:37 50.49 mi 1,929 ft
Ride Fri, 6/28/2013 Kaffeehaus San Mateo 3:38:11 50.38 mi 1,028 ft
Ride Thu, 10/5/2017 Big Sur 4:33:33 50.38 mi 4,528 ft
Ride Sun, 11/20/2016 Lunch Ride 3:32:01 50.13 mi 1,847 ft
Ride Sat, 1/13/2018 Canada to Coyote Point 3:59:24 50.09 mi 1,499 ft
Ride Fri, 1/13/2017 Los Gatos Creek 3:52:28 50.01 mi 1,598 ft
Ride Sun, 6/11/2017 Tour de Cure with Juliet 4:11:01 49.49 mi 2,713 ft
Ride Sun, 9/11/2016 Santa Cruz via Mountain Charlie Rd 4:03:04 48.78 mi 3,300 ft
Ride Sat, 7/23/2016 Morning Ride 3:30:50 48.45 mi 1,063 ft
Ride Fri, 3/25/2016 Morning Ride 3:39:53 47.93 mi 2,438 ft
Ride Sat, 8/20/2016 Tour de Menlo with Juliet 3:30:24 47.52 mi 2,133 ft
Ride Sat, 8/15/2015 08/15/2015 Palo Alto, CA 3:35:03 47.12 mi 1,158 ft
Ride Wed, 11/11/2015 Morning Ride 3:45:01 46.54 mi 2,559 ft
Ride Fri, 7/21/2017 Morning Ride 3:35:00 46.19 mi 748 ft
Ride Sat, 9/2/2017 Canada / Sheep Camp Trail 3:45:45 45.72 mi 2,034 ft
Ride Mon, 1/19/2015 Canada Rd, etc. 2:57:14 45.64 mi 1,836 ft
Ride Sun, 11/27/2016 Morning Ride 3:04:11 45.60 mi 1,378 ft
Ride Sun, 7/2/2017 Afternoon Ride 3:10:38 45.30 mi 581 ft
Ride Sat, 10/3/2015 Los Gatos 2:59:26 45.21 mi 1,148 ft
Ride Sat, 7/2/2016 Morning Ride 3:23:20 45.21 mi 1,991 ft
Ride Mon, 1/16/2017 Morning Ride 3:34:28 45.15 mi 1,434 ft
Ride Sun, 1/15/2017 Lunch Ride 3:18:47 45.12 mi 1,873 ft
Ride Sun, 4/9/2017 Lunch Ride 3:10:28 44.76 mi 636 ft
Ride Sun, 10/15/2017 Los Gatos 2:51:53 44.71 mi 1,437 ft
Ride Tue, 10/8/2013 work 3:22:55 44.58 mi 961 ft
Ride Thu, 7/16/2015 07/16/2015 Mountain View, California 3:29:45 44.34 mi 1,339 ft
Ride Sat, 10/28/2017 Mindego Ridge Winery 4:17:45 44.28 mi 4,331 ft
Ride Sun, 9/2/2018 Saratoga 3:31:02 44.04 mi 1,900 ft
Ride Fri, 9/23/2016 Los Gatos 2:53:22 43.93 mi 1,339 ft
Ride Sat, 7/25/2015 07/25/2015 Palo Alto, California 4:02:27 43.62 mi 4,819 ft
Ride Sat, 7/8/2017 Dumbarton / Niles 3:14:55 43.54 mi 627 ft
Ride Sat, 6/9/2018 Kings Mountain 3:41:47 43.47 mi 3,543 ft
Ride Fri, 3/10/2017 Morning Ride 4:13:51 43.22 mi 4,554 ft
Ride Sat, 3/26/2016 Morning Ride 3:36:57 43.18 mi 3,173 ft
Ride Sun, 10/18/2015 Afternoon Ride 3:00:02 43.04 mi 2,323 ft
Ride Sat, 9/20/2014 Kings Mountain 3:26:36 43.00 mi 3,299 ft
Ride Sat, 8/11/2018 Bike Ride Northwest Day 7 3:25:20 42.39 mi 2,241 ft
Ride Sat, 3/18/2017 Morning Ride 3:37:01 42.29 mi 2,231 ft
Ride Sat, 7/11/2015 07/11/2015 Walnut Creek, California 3:09:38 42.29 mi 3,284 ft
Ride Thu, 1/22/2015 OLH, etc. 3:25:09 42.15 mi 2,957 ft
Ride Sun, 11/9/2014 11/09/2014 Palo Alto, CA 3:24:57 42.10 mi 3,096 ft
Ride Sun, 5/15/2016 Afternoon Ride 2:55:51 41.98 mi 1,693 ft
Ride Sat, 3/25/2017 Morning Ride 3:37:45 41.94 mi 2,874 ft
Ride Sun, 6/14/2015 06/14/2015 Palo Alto, CA 2:46:38 41.67 mi 1,086 ft
Ride Sat, 7/28/2018 Coyote Hills / Dumbarton 3:25:39 41.66 mi 1,463 ft
Ride Sun, 8/7/2016 Afternoon Ride 2:42:58 41.19 mi 1,526 ft
Ride Sat, 2/11/2017 Morning Ride 3:19:34 41.16 mi 2,172 ft
Ride Sun, 12/3/2017 Bay Trail 2:53:58 40.86 mi 568 ft
Ride Mon, 5/29/2017 Morning Ride 3:52:04 40.83 mi 3,678 ft
Ride Sat, 2/18/2017 Lunch Ride 3:01:13 40.82 mi 630 ft
Ride Wed, 11/9/2016 Lunch Ride 2:52:06 40.82 mi 1,667 ft
Ride Sat, 5/20/2017 Lunch Ride 3:07:43 40.82 mi 709 ft
Ride Sat, 3/24/2018 Morning Ride 2:56:29 40.69 mi 561 ft
Ride Sun, 11/19/2017 Belmont / Bay Trail 2:53:36 40.60 mi 453 ft
Ride Sun, 3/26/2017 Lunch Ride 2:54:38 40.57 mi 518 ft
Ride Sat, 5/25/2019 Crestview 3:17:16 40.56 mi 2,890 ft
Ride Sat, 9/16/2017 Tour de Coop 3:24:53 40.55 mi 1,125 ft
Ride Thu, 9/3/2015 Morning Ride 3:02:58 40.40 mi 1,181 ft
Ride Sun, 2/5/2017 Morning Ride 2:53:48 40.40 mi 1,608 ft
Ride Sun, 10/23/2016 Lunch Ride 2:41:33 40.33 mi 1,699 ft
Ride Sat, 12/8/2018 Morning Ride 3:27:10 40.33 mi 2,300 ft
Ride Sun, 2/12/2017 Lunch Ride 3:39:38 40.27 mi 2,717 ft
Ride Sat, 12/2/2017 Dumbarton / Niles 2:57:48 40.26 mi 610 ft
Ride Sun, 11/12/2017 Morning Ride 2:54:57 40.24 mi 656 ft
Ride Sat, 11/15/2014 Kings Mountain again 3:25:14 40.18 mi 2,952 ft
Ride Sun, 10/9/2016 Lunch Ride 2:41:06 40.14 mi 1,106 ft
Ride Mon, 11/6/2017 Morning Ride 2:48:12 40.13 mi 509 ft
Ride Sat, 12/17/2016 Morning Ride 3:11:31 40.10 mi 2,133 ft
Ride Fri, 1/12/2018 Morning Ride 2:58:30 40.09 mi 427 ft
Ride Sun, 12/18/2016 Morning Ride 3:04:01 40.03 mi 1,886 ft
Ride Sat, 6/11/2016 Ring of Fire (Partial; Garmin troubles) 3:00:00 40.00 mi 0 ft
Ride Sun, 1/25/2015 Canada Rd 2:45:51 39.99 mi 1,772 ft
Ride Sat, 6/21/2014 Sierra to the Sea Day 7 3:36:53 39.77 mi 3,325 ft
Ride Sat, 9/23/2017 Canada / Crestview 3:25:13 39.77 mi 2,444 ft
Ride Fri, 12/27/2013 OLH to Page Mill 3:24:14 39.73 mi 3,365 ft
Ride Sat, 4/30/2016 Ridge to Bridge MTB 5:40:57 39.67 mi 5,377 ft
Ride Fri, 10/30/2015 OLH - West Alpine 3:28:46 39.51 mi 4,505 ft
Ride Sat, 9/5/2015 Ring of Fire 3:31:55 39.18 mi 3,553 ft
Ride Wed, 8/28/2013 Train Track Crash 2:50:55 39.16 mi 928 ft
Ride Sun, 2/22/2015 Canada Rd Plus 2:57:25 39.14 mi 1,834 ft
Ride Sat, 4/8/2017 Lunch Ride 3:28:52 39.09 mi 3,117 ft
Ride Sat, 11/14/2015 Morning Ride 3:09:31 39.00 mi 2,254 ft
Ride Sun, 3/1/2015 Canada Rd 2:38:11 38.81 mi 1,856 ft
Ride Wed, 11/22/2017 Morning Ride 2:55:22 38.77 mi 892 ft
Ride Sat, 10/29/2016 Morning Ride 3:14:22 38.72 mi 2,201 ft
Ride Sat, 4/6/2019 Coyote Hills / Dumbarton 2:50:52 38.71 mi 650 ft
Ride Wed, 7/22/2015 07/22/2015 Palo Alto, CA 2:55:45 38.68 mi 627 ft
Ride Mon, 6/22/2015 06/22/2015 Palo Alto, CA 2:51:10 38.55 mi 725 ft
Ride Fri, 12/16/2016 Morning Ride 2:54:02 38.46 mi 1,053 ft
Ride Sun, 10/7/2018 Westridge, Canada with Ted 3:09:25 38.29 mi 1,667 ft
Ride Sat, 2/21/2015 Kings Mountain 3:13:05 38.21 mi 3,015 ft
Ride Sat, 9/12/2015 Morning Ride 3:06:03 38.20 mi 3,342 ft
Ride Sun, 2/15/2015 Canada Rd 2:35:29 38.06 mi 1,470 ft
Ride Sun, 11/16/2014 Canada Rd 2:30:41 37.88 mi 1,548 ft
Ride Sun, 10/5/2014 Canada Road Sunday 2:39:39 37.84 mi 1,644 ft
Ride Mon, 11/23/2015 Home from Pigeon Point 3:29:29 37.70 mi 3,133 ft
Ride Sat, 1/6/2018 Morning Ride 2:53:45 37.66 mi 1,503 ft
Ride Sun, 1/7/2018 Dumbarton / Niles 2:39:17 37.64 mi 472 ft
Ride Sun, 12/1/2013 Mt Hamilton 3:47:01 37.56 mi 4,921 ft
Ride Sun, 3/18/2018 Lunch Ride 2:47:48 37.52 mi 827 ft
Ride Sat, 9/22/2018 Morning Ride 2:52:01 37.45 mi 1,798 ft
Ride Sun, 10/22/2017 Afternoon Ride 2:33:27 37.41 mi 1,024 ft
Ride Sun, 5/4/2014 MTV-Woodside 2:41:21 37.41 mi 1,495 ft
Ride Sat, 1/23/2016 Morning Ride 3:39:10 37.35 mi 2,949 ft
Ride Sun, 9/16/2018 Dumbarton / Niles 2:38:41 37.26 mi 722 ft
Ride Sun, 6/10/2018 Canada / Emerald Hills 3:21:31 37.26 mi 2,621 ft
Ride Mon, 5/4/2015 Commute 2:43:06 37.25 mi 189 ft
Ride Sat, 7/7/2018 Runymede Trail 3:01:51 37.07 mi 1,834 ft
Ride Sun, 12/11/2016 Afternoon Ride 2:34:13 37.05 mi 1,463 ft
Ride Sun, 11/29/2015 Mt Hamilton 3:40:48 37.00 mi 4,902 ft
Ride Sat, 1/30/2016 Morning Ride 2:31:37 36.88 mi 1,345 ft
Ride Mon, 2/18/2019 Niles 2:39:39 36.83 mi 571 ft
Ride Sun, 1/21/2018 Alviso 2:35:58 36.82 mi 404 ft
Ride Sat, 8/26/2017 Lunch Ride 2:41:49 36.71 mi 1,115 ft
Ride Sat, 9/13/2014 Emerald Hills 2:55:30 36.71 mi 2,143 ft
Ride Sun, 5/22/2016 Lunch Ride 2:12:20 36.68 mi 1,332 ft
Ride Sat, 11/25/2017 Mt. Hamilton 3:41:22 36.65 mi 4,806 ft
Ride Fri, 10/13/2017 Voyage 2:46:15 36.63 mi 505 ft
Ride Tue, 5/12/2015 05/12/2015 Palo Alto, CA 2:47:15 36.54 mi 499 ft
Ride Mon, 4/20/2015 Commute 2:57:44 36.31 mi 119 ft
Ride Fri, 5/13/2016 Morning Ride 2:41:03 36.21 mi 456 ft
Ride Sat, 12/3/2016 Morning Ride 3:08:37 36.20 mi 2,881 ft
Ride Sun, 12/7/2014 12/07/2014 Palo Alto, CA 2:43:35 36.16 mi 1,569 ft
Ride Sun, 9/30/2018 Canada 2:48:41 36.12 mi 1,480 ft
Ride Fri, 3/30/2018 Morning Ride 2:38:07 36.11 mi 285 ft
Ride Sun, 11/5/2017 Lunch Ride 3:24:01 35.91 mi 2,231 ft
Ride Sun, 2/8/2015 Commute 2:28:46 35.85 mi 1,142 ft
Ride Tue, 6/26/2018 Return from Chabot Lake with Juliet MTB 3:21:30 35.81 mi 817 ft
Ride Sat, 3/30/2019 Morning Ride 3:23:55 35.78 mi 2,861 ft
Ride Sun, 11/22/2015 Pigeon Point 3:14:41 35.73 mi 3,230 ft
Ride Mon, 9/19/2016 Sunnyvale 2:37:04 35.68 mi 745 ft
Ride Thu, 4/9/2015 Palo Alto Cycling 2:35:47 35.51 mi 117 ft
Ride Sun, 8/21/2016 Canada 2:20:21 35.42 mi 1,348 ft
Ride Tue, 10/28/2014 Woodside 2:36:36 35.39 mi 1,560 ft
Ride Sun, 1/27/2019 Crystal Springs Dam Bridge 2:28:21 35.38 mi 1,480 ft
Ride Thu, 4/26/2018 San Mateo 2:38:26 35.31 mi 666 ft
Ride Thu, 12/22/2016 Half Moon Bay 2:53:00 35.26 mi 2,671 ft
Ride Tue, 6/7/2016 Los Altos 2:41:13 35.18 mi 1,070 ft
Ride Sat, 3/15/2014 Canada - Steve 2:51:50 35.01 mi 1,607 ft
Ride Sun, 12/6/2015 Canada Rd 2:15:05 34.67 mi 1,237 ft
Ride Wed, 9/13/2017 Healdburg / Jimtown 2:08:03 34.45 mi 912 ft
Ride Thu, 10/2/2014 10/02/2014 Mountain View, California 3:08:06 34.28 mi 2,864 ft
Ride Sat, 2/1/2014 OLH to Page Mill 3:07:43 34.26 mi 3,099 ft
Ride Sat, 10/21/2017 Pescadero 5:20:08 67.05 mi 4,938 ft
Ride Sat, 7/30/2016 Morning Ride 2:50:15 34.19 mi 2,799 ft
Ride Mon, 7/10/2017 Morning Ride 2:38:59 34.18 mi 984 ft
Ride Thu, 7/31/2014 Work commute 2:36:53 34.15 mi 564 ft
Ride Sat, 12/15/2018 Lunch Ride 2:50:19 34.10 mi 1,683 ft
Ride Sat, 1/20/2018 Afternoon Ride 2:28:18 34.02 mi 525 ft
Ride Sun, 12/4/2016 Morning Ride 2:29:29 34.00 mi 1,161 ft
Ride Sat, 10/8/2016 Lunch Ride 2:25:17 33.95 mi 1,375 ft
Ride Thu, 10/16/2014 Commute 2:40:01 33.76 mi 615 ft
Ride Fri, 11/11/2016 OLH 2:44:59 33.69 mi 2,175 ft
Ride Sat, 9/3/2016 Morning Ride 3:07:28 33.65 mi 2,792 ft
Ride Sun, 4/19/2015 Canada Rd 2:23:17 33.56 mi 1,466 ft
Ride Sat, 8/12/2017 Afternoon Ride 2:41:24 33.54 mi 633 ft
Ride Sat, 2/14/2015 Rancho San Antonio 2:45:23 33.53 mi 2,075 ft
Ride Sat, 2/27/2016 Morning Ride 2:47:15 33.51 mi 2,005 ft
Ride Sun, 9/17/2017 Canada and off road 2:36:04 33.50 mi 1,772 ft
Ride Sat, 2/24/2018 Los Altos Hills on and off road 3:10:47 33.49 mi 2,343 ft
Ride Sun, 1/26/2014 Canada Rd 2:05:59 33.12 mi 1,446 ft
Ride Sat, 4/27/2019 Morning Ride 2:31:48 33.04 mi 1,722 ft
Ride Sun, 5/12/2019 Peninsula Bike Trail to Bay Trail 2:29:05 33.00 mi 846 ft
Ride Sun, 8/20/2017 Lunch Ride 2:23:02 32.96 mi 1,407 ft
Ride Sun, 3/6/2016 Lunch Ride 2:57:54 32.86 mi 2,566 ft
Ride Sun, 3/5/2017 Lunch Ride 2:57:09 32.82 mi 1,713 ft
Ride Sat, 12/16/2017 Morning Ride 2:48:58 32.72 mi 2,320 ft
Ride Tue, 8/23/2016 Morning Ride 2:32:15 32.61 mi 354 ft
Ride Fri, 12/9/2016 Morning Ride 3:04:52 32.55 mi 2,365 ft
Ride Sun, 11/1/2015 Lunch Ride 2:20:35 32.49 mi 1,553 ft
Ride Sat, 4/14/2018 Lunch Ride 2:21:49 32.43 mi 666 ft
Ride Fri, 1/26/2018 Morning Ride 2:23:38 32.41 mi 295 ft
Ride Sun, 8/16/2015 08/16/2015 Palo Alto, CA 2:29:53 32.40 mi 1,650 ft
Ride Sat, 5/9/2015 OLH 2:30:17 32.33 mi 2,788 ft
Ride Wed, 1/31/2018 Morning Ride 3:04:15 32.28 mi 2,526 ft
Ride Sat, 4/4/2015 Rancho San Antonio 2:55:37 32.26 mi 2,136 ft
Ride Sat, 3/16/2019 Morning Ride 2:58:43 32.26 mi 2,316 ft
Ride Sun, 12/27/2015 Canada Rd with Juliet 2:24:56 32.22 mi 1,491 ft
Ride Mon, 5/18/2015 05/18/2015 Palo Alto, CA 2:24:45 32.22 mi 807 ft
Ride Sun, 4/8/2018 Lunch Ride 2:21:54 32.18 mi 1,296 ft
Ride Sun, 5/3/2015 Canada Rd 2:12:24 32.02 mi 1,384 ft
Ride Wed, 6/17/2015 06/17/2015 Mountain View, California 2:13:42 32.01 mi 650 ft
Ride Sat, 11/7/2015 Crystal Springs Part 1 2:15:00 32.00 mi 0 ft
Ride Sun, 8/23/2015 Afternoon Ride 2:24:29 31.90 mi 2,444 ft
Ride Sun, 4/12/2015 Palo Alto Cycling 2:01:49 31.76 mi 1,210 ft
Ride Sun, 11/13/2016 Afternoon Ride 2:23:46 31.71 mi 1,273 ft
Ride Fri, 10/7/2016 Morning Ride 2:26:46 31.65 mi 2,382 ft
Ride Thu, 8/15/2013 08/15/2013 Palo Alto, CA 2:19:27 31.64 mi 555 ft
Ride Wed, 10/30/2013 work 2:22:00 31.55 mi 995 ft
Ride Sun, 9/18/2016 Morning Ride 2:20:46 31.48 mi 1,506 ft
Ride Tue, 6/18/2013 work etc (headwinds) 2:03:34 31.48 mi 809 ft
Ride Sun, 3/11/2018 Lunch Ride 2:23:14 31.42 mi 686 ft
Ride Sat, 3/12/2016 Lunch Ride 2:10:01 31.39 mi 1,198 ft
Ride Sun, 1/22/2017 Dumbarton 2:23:36 31.27 mi 591 ft
Ride Sat, 9/29/2018 Kings 2:36:47 31.23 mi 1,949 ft
Ride Sat, 3/23/2019 Morning Ride 2:33:27 31.19 mi 1,529 ft
Ride Sat, 4/13/2019 Alviso 2:10:04 31.18 mi 397 ft
Ride Mon, 11/10/2014 Commute 2:22:39 31.18 mi 800 ft
Ride Mon, 5/25/2015 05/25/2015 Palo Alto, CA 2:19:26 31.14 mi 1,591 ft
Ride Mon, 2/16/2015 Portola Valley Loop 2:09:15 31.11 mi 1,283 ft
Ride Wed, 9/21/2016 Morning Ride 2:16:04 31.09 mi 551 ft
Ride Sat, 3/11/2017 Afternoon Ride 2:19:05 31.04 mi 1,368 ft
Ride Sun, 7/29/2018 Lunch Ride 2:26:56 30.92 mi 1,578 ft
Ride Fri, 11/10/2017 Lunch Ride 2:20:46 30.92 mi 312 ft
Ride Mon, 6/12/2017 Morning Ride 2:20:16 30.90 mi 554 ft
Ride Thu, 12/29/2016 Morning Ride 2:13:46 30.86 mi 1,083 ft
Ride Sat, 4/22/2017 Lunch Ride 2:21:31 30.80 mi 1,237 ft
Ride Mon, 9/7/2015 Healdsburg 2:09:43 30.76 mi 1,037 ft
Ride Sat, 1/14/2017 Afternoon Ride 2:12:37 30.71 mi 466 ft
Ride Mon, 9/4/2017 Kings Mountain 2:40:57 30.69 mi 2,431 ft
Ride Sun, 8/9/2015 08/09/2015 Palo Alto, CA 2:15:39 30.66 mi 1,348 ft
Ride Sat, 12/12/2015 Lunch Ride 2:16:20 30.56 mi 1,434 ft
Ride Thu, 12/28/2017 Morning Ride 2:17:38 30.54 mi 430 ft
Ride Mon, 1/30/2017 Morning Ride 2:19:47 30.49 mi 696 ft
Ride Sun, 8/27/2017 Lunch Ride 2:16:36 30.47 mi 774 ft
Ride Fri, 10/12/2018 Lunch Ride 2:16:25 30.44 mi 348 ft
Ride Mon, 6/25/2018 Chabot Lake Overnight Bike packing with Juliet MTB 3:02:49 30.41 mi 1,250 ft
Ride Sat, 2/28/2015 Palo Alto Cycling 2:40:24 30.37 mi 2,535 ft
Ride Sat, 6/24/2017 Afternoon Ride 2:06:05 30.31 mi 338 ft
Ride Tue, 6/2/2015 06/02/2015 Mountain View, California 2:09:58 30.30 mi 784 ft
Ride Mon, 5/27/2019 Canada / Sheep camp / Water dog lake 2:37:11 30.27 mi 1,444 ft
Ride Sat, 1/2/2016 Los Altos Hills 2:29:48 30.27 mi 1,952 ft
Ride Fri, 6/23/2017 Morning Ride 2:17:16 30.27 mi 515 ft
Ride Sun, 4/2/2017 Morning Ride 2:20:47 30.23 mi 407 ft
Ride Sat, 8/25/2018 Morning Ride 2:22:42 30.22 mi 2,257 ft
Ride Sun, 9/14/2014 09/14/2014 Palo Alto, CA 2:06:48 30.18 mi 1,204 ft
Ride Sat, 8/19/2017 Lunch Ride 2:06:39 30.17 mi 627 ft
Ride Sun, 3/12/2017 Morning Ride 2:18:13 30.16 mi 600 ft
Ride Sat, 11/21/2015 Morning Ride 2:20:54 30.16 mi 1,499 ft
Ride Wed, 10/12/2016 Morning Ride 2:19:51 30.11 mi 328 ft
Ride Sun, 7/8/2018 Lunch Ride 2:12:19 30.04 mi 768 ft
Ride Tue, 11/22/2016 Morning Ride 2:18:57 30.04 mi 518 ft
Ride Wed, 3/4/2015 Commute 2:15:56 30.02 mi 1,125 ft
Ride Sun, 3/25/2018 Dumbarton / Coyote Hills / Middle of the Bay 2:31:51 30.01 mi 709 ft
Ride Tue, 5/31/2016 Google Bike around campus 2:30:00 30.00 mi 0 ft
Ride Tue, 2/14/2017 Morning Ride 2:22:16 29.92 mi 463 ft
Ride Sun, 5/31/2015 05/31/2015 Palo Alto, California 2:35:24 29.85 mi 2,568 ft
Ride Tue, 11/1/2016 Morning Ride 2:15:55 29.78 mi 515 ft
Ride Sun, 6/5/2016 Morning Ride 2:18:02 29.70 mi 1,293 ft
Ride Sun, 5/26/2019 San Carlos / Bay Trail 2:17:51 29.55 mi 620 ft
Ride Thu, 8/13/2015 08/13/2015 Palo Alto, CA 2:13:58 29.51 mi 778 ft
Ride Sun, 7/14/2013 07/14/2013 Palo Alto, CA 2:23:07 29.34 mi 739 ft
Ride Sun, 4/3/2016 Lunch Ride 2:03:38 29.31 mi 810 ft
Ride Mon, 3/9/2015 Palo Alto Cycling 1:57:50 29.21 mi 115 ft
Ride Fri, 2/23/2018 Morning Ride 2:08:28 29.21 mi 166 ft
Ride Sat, 10/7/2017 Afternoon Ride 2:02:43 29.18 mi 394 ft
Ride Sat, 1/17/2015 Palo Alto Cycling 2:08:38 29.06 mi 1,587 ft
Ride Sat, 1/18/2014 Los Altos Hills 2:32:23 29.03 mi 1,918 ft
Ride Sat, 6/20/2015 06/20/2015 Palo Alto, CA 2:06:45 28.87 mi 1,650 ft
Ride Sun, 11/3/2013 work 2:11:08 28.74 mi 841 ft
Ride Wed, 8/16/2017 Lunch Ride 2:15:48 28.67 mi 732 ft
Ride Sat, 1/9/2016 Morning Ride 2:07:35 28.54 mi 1,289 ft
Ride Sat, 9/19/2015 Los Altos Hills 2:31:24 28.51 mi 1,611 ft
Ride Wed, 8/12/2015 08/12/2015 Palo Alto, CA 2:02:14 28.48 mi 751 ft
Ride Sun, 7/23/2017 Morning Ride 2:57:35 28.43 mi 2,562 ft
Ride Mon, 8/24/2015 Morning Ride 2:10:31 28.35 mi 427 ft
Ride Wed, 6/26/2013 work 2:01:29 28.33 mi 627 ft
Ride Sun, 4/28/2019 Arastradsero 2:29:59 28.21 mi 1,355 ft
Ride Sun, 7/19/2015 07/19/2015 Palo Alto, CA 2:00:27 28.17 mi 1,037 ft
Ride Fri, 12/28/2018 Lunch Ride with Juliet 2:17:33 28.10 mi 331 ft
Ride Tue, 5/17/2016 Morning Ride 2:06:55 28.09 mi 551 ft
Ride Wed, 10/5/2016 Morning Ride 2:10:47 28.05 mi 276 ft
Ride Wed, 6/22/2016 Morning Ride 2:00:40 27.99 mi 374 ft
Ride Mon, 3/16/2015 Commute 2:06:51 27.97 mi 70 ft
Ride Sun, 2/25/2018 Lunch Ride 2:10:54 27.88 mi 958 ft
Ride Sat, 10/27/2018 Morning Ride 2:29:13 27.88 mi 1,581 ft
Ride Mon, 6/20/2016 Morning Ride 1:52:13 27.77 mi 531 ft
Ride Sun, 12/2/2018 Lunch Ride 2:34:14 27.72 mi 1,506 ft
Ride Mon, 3/19/2018 Morning Ride 2:06:35 27.62 mi 860 ft
Ride Mon, 12/17/2018 Morning Ride 2:16:12 27.60 mi 246 ft
Ride Sat, 10/14/2017 Afternoon Ride 2:04:53 27.52 mi 387 ft
Ride Sat, 10/18/2014 Bikepacking Monte Bello 2:54:20 27.44 mi 2,992 ft
Ride Sun, 4/15/2018 Morning Ride 2:17:16 27.43 mi 1,667 ft
Ride Mon, 4/10/2017 Morning Ride 2:03:19 27.40 mi 282 ft
Ride Tue, 5/7/2019 Lunch Ride 2:07:55 27.37 mi 820 ft
Ride Sun, 11/25/2018 Afternoon Ride 1:56:26 27.34 mi 203 ft
Ride Wed, 4/29/2015 Palo Alto Cycling 2:03:14 27.33 mi 75 ft
Ride Tue, 1/17/2017 Morning Ride 2:07:17 27.30 mi 535 ft
Ride Tue, 9/30/2014 09/30/2014 Palo Alto, CA 2:07:33 27.28 mi 636 ft
Ride Sat, 9/17/2016 TourDeCoop.org 2:04:27 27.24 mi 479 ft
Ride Sat, 1/12/2019 Lunch Ride 1:58:47 27.24 mi 1,079 ft
Ride Mon, 3/30/2015 Woodside Loop 1:47:46 27.22 mi 1,081 ft
Ride Tue, 11/17/2015 Afternoon Ride 2:04:48 27.12 mi 830 ft
Ride Sun, 8/13/2017 Woodside Loop (Battery ran down) 2:00:00 27.00 mi 1,000 ft
Ride Sat, 1/31/2015 Alpine Rd 2:29:26 26.98 mi 2,362 ft
Ride Sun, 2/28/2016 Woodside Loop 1:43:34 26.93 mi 843 ft
Ride Sun, 4/1/2018 Lunch Ride 2:30:35 26.92 mi 1,831 ft
Workout Wed, 5/3/2017 Palo Alto Road Cycling 2:06:05 26.86 mi 1,411 ft
Ride Fri, 9/9/2016 Morning Ride 1:56:44 26.85 mi 1,086 ft
Ride Sun, 9/23/2018 Morning Ride 1:57:48 26.83 mi 512 ft
Ride Sat, 10/4/2014 Woodside Loop 1:52:09 26.71 mi 1,213 ft
Ride Thu, 5/7/2015 Commute 1:55:36 26.58 mi 68 ft
Ride Sun, 3/24/2019 Afternoon Ride 2:42:45 26.57 mi 2,267 ft
Ride Thu, 8/31/2017 Morning Ride 2:00:02 26.54 mi 817 ft
Ride Sun, 4/24/2016 Afternoon Ride 1:50:08 26.53 mi 935 ft
Ride Sun, 12/17/2017 Lunch Ride 1:50:51 26.53 mi 217 ft
Ride Tue, 7/4/2017 Lunch Ride 1:53:04 26.50 mi 459 ft
Ride Tue, 5/8/2018 Afternoon Ride 2:34:11 26.50 mi 1,791 ft
Ride Tue, 5/26/2015 05/26/2015 Palo Alto, CA 2:01:47 26.50 mi 591 ft
Ride Thu, 8/4/2016 Afternoon Ride 1:55:47 26.49 mi 659 ft
Ride Wed, 6/8/2016 Lunch Ride 1:48:19 26.48 mi 597 ft
Ride Sun, 9/9/2018 Lunch Ride 2:00:49 26.46 mi 479 ft
Ride Sat, 5/21/2016 Maker Faire 1:57:14 26.44 mi 207 ft
Ride Wed, 4/1/2015 Commute 1:55:15 26.44 mi 71 ft
Ride Wed, 6/29/2016 Morning Ride 1:49:33 26.39 mi 561 ft
Ride Sat, 9/9/2017 Lunch Ride 1:59:01 26.38 mi 1,112 ft
Ride Fri, 6/3/2016 Morning Ride 1:56:57 26.38 mi 502 ft
Ride Fri, 3/31/2017 Morning Ride 2:01:41 26.36 mi 495 ft
Ride Sat, 12/13/2014 Westridge (up the back way; after the rain) 2:01:01 26.35 mi 1,495 ft
Ride Sun, 4/14/2019 Huddart - Paul Hopkins 2:08:57 26.34 mi 1,601 ft
Ride Sat, 6/29/2013 Untitled 1:48:50 26.33 mi 1,091 ft
Ride Sat, 4/25/2015 Woodside 1:54:30 26.28 mi 1,220 ft
Ride Tue, 1/31/2017 Morning Ride 2:01:18 26.27 mi 384 ft
Ride Tue, 6/28/2016 Morning Ride 1:55:31 26.27 mi 571 ft
Ride Sat, 5/19/2018 Morning Ride 2:00:24 26.25 mi 958 ft
Ride Thu, 6/30/2016 Morning Ride 1:52:16 26.24 mi 499 ft
Ride Sun, 2/19/2017 Afternoon Ride 1:56:46 26.23 mi 1,138 ft
Ride Thu, 8/18/2016 Morning Ride 1:58:06 26.23 mi 712 ft
Ride Sun, 4/20/2014 Afternoon Ride 1:52:52 26.23 mi 102 ft
Ride Sun, 5/10/2015 Arastadero 1:48:58 26.22 mi 1,148 ft
Ride Thu, 5/19/2016 Morning Ride 1:52:56 26.14 mi 938 ft
Ride Mon, 12/31/2018 Morning Ride 2:00:24 26.11 mi 545 ft
Ride Fri, 12/15/2017 Afternoon Ride 1:51:36 26.07 mi 226 ft
Ride Sun, 1/18/2015 Palo Alto Cycling 1:38:16 26.02 mi 1,257 ft
Ride Mon, 10/3/2016 Morning Ride 1:54:45 26.01 mi 646 ft
Ride Tue, 7/14/2015 07/14/2015 Mountain View, California 2:02:36 25.99 mi 502 ft
Ride Fri, 12/18/2015 Lunch Ride 1:58:57 25.93 mi 909 ft
Ride Tue, 6/13/2017 Morning Ride 1:55:30 25.89 mi 289 ft
Ride Mon, 8/4/2014 To the Sea 2:17:11 25.87 mi 2,080 ft
Ride Wed, 9/25/2013 work and with kris 2:07:40 25.81 mi 593 ft
Ride Sun, 9/13/2015 Afternoon Ride 1:49:31 25.78 mi 1,152 ft
Ride Sun, 3/3/2019 Afternoon Ride 2:00:06 25.73 mi 390 ft
Ride Thu, 1/26/2017 Morning Ride 1:56:09 25.67 mi 259 ft
Ride Sun, 5/6/2018 Morning Ride 1:55:05 25.67 mi 1,148 ft
Ride Sat, 7/29/2017 Lunch Ride 1:53:50 25.65 mi 988 ft
Ride Wed, 8/20/2014 08/20/2014 Palo Alto, CA 1:49:35 25.61 mi 497 ft
Ride Sat, 4/19/2014 Lunch Ride 1:49:37 25.60 mi 1,111 ft
Ride Mon, 9/3/2018 Afternoon Ride 1:59:08 25.54 mi 331 ft
Ride Mon, 6/2/2014 Work 1:59:33 25.51 mi 627 ft
Ride Fri, 12/11/2015 Afternoon Ride 2:12:40 25.44 mi 1,719 ft
Ride Fri, 2/9/2018 Morning Ride 1:56:11 25.41 mi 318 ft
Ride Sun, 8/26/2018 Afternoon Ride 1:49:16 25.37 mi 377 ft
Ride Sun, 7/9/2017 Afternoon Ride 1:50:30 25.36 mi 682 ft
Ride Mon, 6/26/2017 Morning Ride 1:52:26 25.34 mi 226 ft
Ride Fri, 9/14/2018 Lunch Ride 1:50:05 25.34 mi 541 ft
Ride Fri, 3/1/2019 Morning Ride 2:00:23 25.34 mi 121 ft
Ride Sun, 1/13/2019 Lunch Ride 1:51:40 25.33 mi 715 ft
Ride Wed, 4/12/2017 Morning Ride 1:59:22 25.33 mi 686 ft
Ride Wed, 6/14/2017 Morning Ride 1:51:32 25.33 mi 308 ft
Ride Sat, 11/28/2015 Lunch Ride 1:43:56 25.31 mi 1,122 ft
Ride Mon, 11/27/2017 Morning Ride 1:55:39 25.31 mi 184 ft
Ride Fri, 11/28/2014 11/28/2014 Palo Alto, CA 1:45:13 25.30 mi 1,112 ft
Ride Fri, 4/13/2018 Morning Ride 1:53:39 25.28 mi 344 ft
Ride Fri, 9/15/2017 Morning Ride 1:56:55 25.28 mi 384 ft
Ride Tue, 12/31/2013 Woodside Loop - Last Ride of the Year 1:45:28 25.27 mi 1,207 ft
Ride Sun, 10/14/2018 Lunch Ride 2:13:13 25.23 mi 1,421 ft
Ride Sat, 11/22/2014 Atherton to Woodside 1:43:10 25.21 mi 1,095 ft
Ride Sat, 6/4/2016 Morning Ride 1:57:23 25.19 mi 922 ft
Ride Mon, 8/8/2016 Morning Ride 1:52:38 25.17 mi 423 ft
Ride Fri, 11/6/2015 Morning Ride 1:51:45 25.15 mi 538 ft
Ride Mon, 12/18/2017 Afternoon Ride 1:52:56 25.15 mi 741 ft
Ride Fri, 6/24/2016 Afternoon Ride 1:35:25 25.11 mi 623 ft
Ride Sat, 6/25/2016 Afternoon Ride 1:44:43 25.10 mi 568 ft
Ride Sat, 1/25/2014 Woodside 1:33:38 25.08 mi 1,243 ft
Ride Tue, 4/29/2014 Woodside 1:46:03 25.08 mi 886 ft
Ride Thu, 4/13/2017 Morning Ride 2:06:13 25.06 mi 266 ft
Ride Sun, 6/12/2016 Morning Ride 1:49:25 25.04 mi 387 ft
Ride Thu, 6/8/2017 Morning Ride 1:56:19 25.02 mi 285 ft
Ride Thu, 10/13/2016 Morning Ride 1:47:30 25.02 mi 253 ft
Ride Sun, 1/19/2014 01/19/2014 Palo Alto, CA 1:36:55 25.01 mi 1,201 ft
Ride Mon, 9/5/2016 Afternoon Ride 1:42:56 25.00 mi 902 ft
Ride Sat, 11/8/2014 Atherton - Woodside 1:40:51 25.00 mi 972 ft
Ride Sun, 12/10/2017 Exercise Bike 1:30:00 25.00 mi 0 ft
#Ride Date Title mi ft
# 2020 Eddington rides only
Ride Sun, 1/26/2020 Los Gatos / Bay 5:18:15 67.98 mi 1,286 ft
Ride Fri, 1/31/2020 Bay Trail / Guadalupe / Geocaching 5:21:39 69.39 mi 1,125 ft
# 2019 Eddington rides only
Ride Sun, 6/2/2019 The Sequoia 6:40:43 77.51 mi 6,467 ft
Ride Sat, 6/8/2019 Morning Ride 2:53:08 34.42 mi 1,824 ft
Ride Sat, 6/15/2019 Morning Ride 2:29:38 29.98 mi 1,785 ft
Ride Sat, 6/15/2019 Morning Ride 2:29:38 29.98 mi 1,785 ft
Ride Fri, 7/5/2019 Crystal Springs 4:30:55 64.05 mi 1,965 ft
Ride Sun, 8/18/2019 Afternoon Ride 2:06:52 27.14 mi 331 ft
Ride Sat, 8/24/2019 Tour de Fox 4:43:39 68.66 mi 2,097 ft
Ride Sat, 8/31/2019 San Gregorio via OLH 5:13:21 65.31 mi 4,026 ft
Ride Fri, 9/6/2019 San Mateo / Bay Trail 4:59:29 68.01 mi 1,158 ft
Ride Sat, 11/16/2019 Canada / Crystal Dam / Sawyer Camp / Bay Trail 5:18:46 66.08 mi 1,972 ft
# 2012 to 2018 rides over 25 miles
Ride Fri, 1/9/2012 Otago Rail Trail 8:00:00 101.00 mi 2,200 ft
Ride Thu, 6/14/2012 Coyote Creek Century with Juliet 8:08:15 100.07 mi 1,513 ft
Ride Sat, 5/13/2017 Morgan Hill iCare Classic 7:27:21 100.05 mi 4,596 ft
Ride Sat, 5/12/2018 ICare Classic, Morgan Hill 6:47:46 91.29 mi 4,160 ft
Ride Sat, 5/6/2017 Wine Country Century 7:15:22 89.49 mi 5,246 ft
Ride Fri, 8/10/2018 Bike Ride Northwest Day 6 6:14:18 84.70 mi 4,380 ft
Ride Sat, 10/1/2016 Half Moon Bay overnight campout 7:30:38 80.07 mi 6,039 ft
Ride Tue, 8/7/2018 Bike Ride Northwest Day 3 6:10:39 78.96 mi 5,092 ft
Ride Sun, 6/15/2014 Sierra to the Sea Day 1 5:34:06 78.53 mi 4,777 ft
Ride Sat, 4/23/2016 Wildflower Century 5:36:10 77.22 mi 4,193 ft
Ride Mon, 6/16/2014 Sierra to the Sea Day 2 5:35:06 74.68 mi 2,451 ft
Ride Fri, 8/12/2016 Half Moon Bay / Harvey's 73/7300 Birthday Ride 6:44:14 74.35 mi 6,610 ft
Ride Wed, 10/14/2015 Half Moon Bay 6:07:55 72.97 mi 7,644 ft
Ride Fri, 1/27/2017 Morning Ride 5:16:50 70.07 mi 2,539 ft
Ride Tue, 6/17/2014 Sierra to the Sea Day 3 4:48:41 68.64 mi 825 ft
Ride Thu, 8/9/2018 Bike Ride Northwest Day 5 4:58:07 68.41 mi 3,862 ft
Ride Sat, 4/15/2017 Pescadero 6:13:12 68.34 mi 6,130 ft
Ride Sun, 6/4/2017 Sequoia Challenge 6:17:25 66.52 mi 7,520 ft
Ride Wed, 8/5/2015 08/05/2015 Palo Alto, CA 4:56:15 66.33 mi 2,054 ft
Ride Fri, 8/28/2015 Pescadaro via OLH 5:18:51 66.01 mi 6,137 ft
Ride Fri, 11/20/2015 Los Gatos 5:29:40 65.73 mi 5,380 ft
Ride Sun, 6/3/2018 The Sequoia 5:58:15 64.92 mi 6,677 ft
Ride Thu, 6/19/2014 Sierra to the Sea Day 5 4:32:55 63.69 mi 2,584 ft
Ride Mon, 8/3/2015 08/03/2015 Palo Alto, CA 4:45:09 63.61 mi 1,877 ft
Ride Fri, 6/10/2016 Morning Ride 4:21:38 62.84 mi 3,196 ft
Ride Sun, 6/2/2013 Woodside Loop and Baylands 4:27:42 62.14 mi 2,169 ft
Ride Wed, 6/12/2013 ride 4:39:46 61.39 mi 2,207 ft
Ride Sat, 4/18/2015 Tunitas + Lobitos Creeks 5:14:38 61.27 mi 6,611 ft
Ride Sat, 11/11/2017 Los Gatos / Bay Trail 4:26:24 60.74 mi 1,316 ft
Ride Sun, 4/17/2016 La Honda / Skyline 5:10:41 60.35 mi 4,551 ft
Ride Mon, 7/3/2017 Los Gatos / Bay Trail 4:25:41 60.28 mi 1,329 ft
Ride Sat, 4/16/2016 OLH - San Gregorio - Tunitas 4:57:40 60.12 mi 4,744 ft
Ride Sun, 1/29/2017 Los Gatos / Guadalupe / San Tomas / Bay Trails 4:33:38 60.11 mi 1,447 ft
Ride Mon, 11/26/2018 Lunch Ride Los Gatos 4:21:35 60.03 mi 1,070 ft
Ride Sat, 10/31/2015 Saratoga Ramble 5:01:57 59.10 mi 3,528 ft
Ride Sat, 8/18/2018 Tour de Menlo 4:03:48 58.81 mi 2,467 ft
Ride Sat, 4/26/2014 OLH - Tunitas Creek 5:15:26 58.69 mi 6,742 ft
Ride Sat, 6/15/2013 Palo Alto to Santa Cruz 4:27:54 58.42 mi 4,431 ft
Ride Sat, 10/11/2014 OLH / La Honda / Tunitas 5:05:23 58.29 mi 6,044 ft
Ride Sat, 7/9/2016 Lunch Ride 3:50:23 58.23 mi 4,042 ft
Ride Wed, 6/18/2014 Sierra to the Sea Day 4 4:57:53 57.64 mi 5,561 ft
Ride Tue, 7/7/2015 07/07/2015 Palo Alto, CA 4:13:20 57.60 mi 1,280 ft
Ride Mon, 8/6/2018 Bike Ride Northwest Day 2 4:35:48 57.58 mi 4,514 ft
Ride Fri, 6/20/2014 Sierra to the Sea Day 6 4:33:39 56.91 mi 4,453 ft
Ride Sat, 6/10/2017 Los Gatos / Creek Trails 3:50:24 56.28 mi 1,365 ft
Ride Sat, 3/4/2017 Lunch Ride 3:58:25 56.26 mi 1,378 ft
Ride Sun, 8/5/2018 Bike Ride Northwest Day 1 3:34:42 55.77 mi 1,824 ft
Ride Sat, 8/13/2016 Petaluma - Point Reyes 4:30:12 54.75 mi 5,286 ft
Ride Sun, 6/7/2015 Tour de Cure 3:59:47 54.65 mi 2,748 ft
Ride Wed, 12/16/2015 Los Gatos 4:00:41 53.86 mi 2,595 ft
Ride Fri, 6/2/2017 Morning Ride 3:57:45 53.49 mi 1,375 ft
Ride Sun, 6/8/2014 Tour de Cure 75K 4:03:05 53.10 mi 2,596 ft
Ride Sun, 11/6/2016 Los Gatos 3:38:28 52.49 mi 1,263 ft
Ride Fri, 2/10/2017 Morning Ride 3:52:39 52.02 mi 1,739 ft
Ride Sat, 2/23/2019 Crystal Springs Dam 4:02:11 51.93 mi 1,946 ft
Ride Fri, 8/11/2017 Saratoga with Peter H 4:30:31 51.74 mi 2,871 ft
Ride Sat, 2/4/2017 Canada Rd 3:46:34 51.66 mi 1,762 ft
Ride Sun, 6/9/2013 Silicon Valley Tour de Cure 75K 3:58:37 51.63 mi 2,929 ft
Ride Sun, 9/3/2017 Morning Ride 4:22:45 51.31 mi 2,526 ft
Ride Mon, 7/27/2015 Palo Alto Cycling 3:48:23 50.93 mi 1,306 ft
Ride Wed, 7/29/2015 07/29/2015 Palo Alto, CA 3:47:53 50.92 mi 1,873 ft
Ride Sun, 6/26/2016 Afternoon Ride 3:16:33 50.78 mi 1,181 ft
Ride Mon, 8/10/2015 08/10/2015 Palo Alto, CA 3:41:54 50.73 mi 1,325 ft
Ride Sat, 10/15/2016 Los Gatos 3:28:16 50.64 mi 1,368 ft
Ride Tue, 12/19/2017 Los Gatos 3:46:37 50.49 mi 1,929 ft
Ride Fri, 6/28/2013 Kaffeehaus San Mateo 3:38:11 50.38 mi 1,028 ft
Ride Thu, 10/5/2017 Big Sur 4:33:33 50.38 mi 4,528 ft
Ride Sun, 11/20/2016 Lunch Ride 3:32:01 50.13 mi 1,847 ft
Ride Sat, 1/13/2018 Canada to Coyote Point 3:59:24 50.09 mi 1,499 ft
Ride Fri, 1/13/2017 Los Gatos Creek 3:52:28 50.01 mi 1,598 ft
Ride Sun, 6/11/2017 Tour de Cure with Juliet 4:11:01 49.49 mi 2,713 ft
Ride Sun, 9/11/2016 Santa Cruz via Mountain Charlie Rd 4:03:04 48.78 mi 3,300 ft
Ride Sat, 7/23/2016 Morning Ride 3:30:50 48.45 mi 1,063 ft
Ride Fri, 3/25/2016 Morning Ride 3:39:53 47.93 mi 2,438 ft
Ride Sat, 8/20/2016 Tour de Menlo with Juliet 3:30:24 47.52 mi 2,133 ft
Ride Sat, 8/15/2015 08/15/2015 Palo Alto, CA 3:35:03 47.12 mi 1,158 ft
Ride Wed, 11/11/2015 Morning Ride 3:45:01 46.54 mi 2,559 ft
Ride Fri, 7/21/2017 Morning Ride 3:35:00 46.19 mi 748 ft
Ride Sat, 9/2/2017 Canada / Sheep Camp Trail 3:45:45 45.72 mi 2,034 ft
Ride Mon, 1/19/2015 Canada Rd, etc. 2:57:14 45.64 mi 1,836 ft
Ride Sun, 11/27/2016 Morning Ride 3:04:11 45.60 mi 1,378 ft
Ride Sun, 7/2/2017 Afternoon Ride 3:10:38 45.30 mi 581 ft
Ride Sat, 10/3/2015 Los Gatos 2:59:26 45.21 mi 1,148 ft
Ride Sat, 7/2/2016 Morning Ride 3:23:20 45.21 mi 1,991 ft
Ride Mon, 1/16/2017 Morning Ride 3:34:28 45.15 mi 1,434 ft
Ride Sun, 1/15/2017 Lunch Ride 3:18:47 45.12 mi 1,873 ft
Ride Sun, 4/9/2017 Lunch Ride 3:10:28 44.76 mi 636 ft
Ride Sun, 10/15/2017 Los Gatos 2:51:53 44.71 mi 1,437 ft
Ride Tue, 10/8/2013 work 3:22:55 44.58 mi 961 ft
Ride Thu, 7/16/2015 07/16/2015 Mountain View, California 3:29:45 44.34 mi 1,339 ft
Ride Sat, 10/28/2017 Mindego Ridge Winery 4:17:45 44.28 mi 4,331 ft
Ride Sun, 9/2/2018 Saratoga 3:31:02 44.04 mi 1,900 ft
Ride Fri, 9/23/2016 Los Gatos 2:53:22 43.93 mi 1,339 ft
Ride Sat, 7/25/2015 07/25/2015 Palo Alto, California 4:02:27 43.62 mi 4,819 ft
Ride Sat, 7/8/2017 Dumbarton / Niles 3:14:55 43.54 mi 627 ft
Ride Sat, 6/9/2018 Kings Mountain 3:41:47 43.47 mi 3,543 ft
Ride Fri, 3/10/2017 Morning Ride 4:13:51 43.22 mi 4,554 ft
Ride Sat, 3/26/2016 Morning Ride 3:36:57 43.18 mi 3,173 ft
Ride Sun, 10/18/2015 Afternoon Ride 3:00:02 43.04 mi 2,323 ft
Ride Sat, 9/20/2014 Kings Mountain 3:26:36 43.00 mi 3,299 ft
Ride Sat, 8/11/2018 Bike Ride Northwest Day 7 3:25:20 42.39 mi 2,241 ft
Ride Sat, 3/18/2017 Morning Ride 3:37:01 42.29 mi 2,231 ft
Ride Sat, 7/11/2015 07/11/2015 Walnut Creek, California 3:09:38 42.29 mi 3,284 ft
Ride Thu, 1/22/2015 OLH, etc. 3:25:09 42.15 mi 2,957 ft
Ride Sun, 11/9/2014 11/09/2014 Palo Alto, CA 3:24:57 42.10 mi 3,096 ft
Ride Sun, 5/15/2016 Afternoon Ride 2:55:51 41.98 mi 1,693 ft
Ride Sat, 3/25/2017 Morning Ride 3:37:45 41.94 mi 2,874 ft
Ride Sun, 6/14/2015 06/14/2015 Palo Alto, CA 2:46:38 41.67 mi 1,086 ft
Ride Sat, 7/28/2018 Coyote Hills / Dumbarton 3:25:39 41.66 mi 1,463 ft
Ride Sun, 8/7/2016 Afternoon Ride 2:42:58 41.19 mi 1,526 ft
Ride Sat, 2/11/2017 Morning Ride 3:19:34 41.16 mi 2,172 ft
Ride Sun, 12/3/2017 Bay Trail 2:53:58 40.86 mi 568 ft
Ride Mon, 5/29/2017 Morning Ride 3:52:04 40.83 mi 3,678 ft
Ride Sat, 2/18/2017 Lunch Ride 3:01:13 40.82 mi 630 ft
Ride Wed, 11/9/2016 Lunch Ride 2:52:06 40.82 mi 1,667 ft
Ride Sat, 5/20/2017 Lunch Ride 3:07:43 40.82 mi 709 ft
Ride Sat, 3/24/2018 Morning Ride 2:56:29 40.69 mi 561 ft
Ride Sun, 11/19/2017 Belmont / Bay Trail 2:53:36 40.60 mi 453 ft
Ride Sun, 3/26/2017 Lunch Ride 2:54:38 40.57 mi 518 ft
Ride Sat, 5/25/2019 Crestview 3:17:16 40.56 mi 2,890 ft
Ride Sat, 9/16/2017 Tour de Coop 3:24:53 40.55 mi 1,125 ft
Ride Thu, 9/3/2015 Morning Ride 3:02:58 40.40 mi 1,181 ft
Ride Sun, 2/5/2017 Morning Ride 2:53:48 40.40 mi 1,608 ft
Ride Sun, 10/23/2016 Lunch Ride 2:41:33 40.33 mi 1,699 ft
Ride Sat, 12/8/2018 Morning Ride 3:27:10 40.33 mi 2,300 ft
Ride Sun, 2/12/2017 Lunch Ride 3:39:38 40.27 mi 2,717 ft
Ride Sat, 12/2/2017 Dumbarton / Niles 2:57:48 40.26 mi 610 ft
Ride Sun, 11/12/2017 Morning Ride 2:54:57 40.24 mi 656 ft
Ride Sat, 11/15/2014 Kings Mountain again 3:25:14 40.18 mi 2,952 ft
Ride Sun, 10/9/2016 Lunch Ride 2:41:06 40.14 mi 1,106 ft
Ride Mon, 11/6/2017 Morning Ride 2:48:12 40.13 mi 509 ft
Ride Sat, 12/17/2016 Morning Ride 3:11:31 40.10 mi 2,133 ft
Ride Fri, 1/12/2018 Morning Ride 2:58:30 40.09 mi 427 ft
Ride Sun, 12/18/2016 Morning Ride 3:04:01 40.03 mi 1,886 ft
Ride Sat, 6/11/2016 Ring of Fire (Partial; Garmin troubles) 3:00:00 40.00 mi 0 ft
Ride Sun, 1/25/2015 Canada Rd 2:45:51 39.99 mi 1,772 ft
Ride Sat, 6/21/2014 Sierra to the Sea Day 7 3:36:53 39.77 mi 3,325 ft
Ride Sat, 9/23/2017 Canada / Crestview 3:25:13 39.77 mi 2,444 ft
Ride Fri, 12/27/2013 OLH to Page Mill 3:24:14 39.73 mi 3,365 ft
Ride Sat, 4/30/2016 Ridge to Bridge MTB 5:40:57 39.67 mi 5,377 ft
Ride Fri, 10/30/2015 OLH - West Alpine 3:28:46 39.51 mi 4,505 ft
Ride Sat, 9/5/2015 Ring of Fire 3:31:55 39.18 mi 3,553 ft
Ride Wed, 8/28/2013 Train Track Crash 2:50:55 39.16 mi 928 ft
Ride Sun, 2/22/2015 Canada Rd Plus 2:57:25 39.14 mi 1,834 ft
Ride Sat, 4/8/2017 Lunch Ride 3:28:52 39.09 mi 3,117 ft
Ride Sat, 11/14/2015 Morning Ride 3:09:31 39.00 mi 2,254 ft
Ride Sun, 3/1/2015 Canada Rd 2:38:11 38.81 mi 1,856 ft
Ride Wed, 11/22/2017 Morning Ride 2:55:22 38.77 mi 892 ft
Ride Sat, 10/29/2016 Morning Ride 3:14:22 38.72 mi 2,201 ft
Ride Sat, 4/6/2019 Coyote Hills / Dumbarton 2:50:52 38.71 mi 650 ft
Ride Wed, 7/22/2015 07/22/2015 Palo Alto, CA 2:55:45 38.68 mi 627 ft
Ride Mon, 6/22/2015 06/22/2015 Palo Alto, CA 2:51:10 38.55 mi 725 ft
Ride Fri, 12/16/2016 Morning Ride 2:54:02 38.46 mi 1,053 ft
Ride Sun, 10/7/2018 Westridge, Canada with Ted 3:09:25 38.29 mi 1,667 ft
Ride Sat, 2/21/2015 Kings Mountain 3:13:05 38.21 mi 3,015 ft
Ride Sat, 9/12/2015 Morning Ride 3:06:03 38.20 mi 3,342 ft
Ride Sun, 2/15/2015 Canada Rd 2:35:29 38.06 mi 1,470 ft
Ride Sun, 11/16/2014 Canada Rd 2:30:41 37.88 mi 1,548 ft
Ride Sun, 10/5/2014 Canada Road Sunday 2:39:39 37.84 mi 1,644 ft
Ride Mon, 11/23/2015 Home from Pigeon Point 3:29:29 37.70 mi 3,133 ft
Ride Sat, 1/6/2018 Morning Ride 2:53:45 37.66 mi 1,503 ft
Ride Sun, 1/7/2018 Dumbarton / Niles 2:39:17 37.64 mi 472 ft
Ride Sun, 12/1/2013 Mt Hamilton 3:47:01 37.56 mi 4,921 ft
Ride Sun, 3/18/2018 Lunch Ride 2:47:48 37.52 mi 827 ft
Ride Sat, 9/22/2018 Morning Ride 2:52:01 37.45 mi 1,798 ft
Ride Sun, 10/22/2017 Afternoon Ride 2:33:27 37.41 mi 1,024 ft
Ride Sun, 5/4/2014 MTV-Woodside 2:41:21 37.41 mi 1,495 ft
Ride Sat, 1/23/2016 Morning Ride 3:39:10 37.35 mi 2,949 ft
Ride Sun, 9/16/2018 Dumbarton / Niles 2:38:41 37.26 mi 722 ft
Ride Sun, 6/10/2018 Canada / Emerald Hills 3:21:31 37.26 mi 2,621 ft
Ride Mon, 5/4/2015 Commute 2:43:06 37.25 mi 189 ft
Ride Sat, 7/7/2018 Runymede Trail 3:01:51 37.07 mi 1,834 ft
Ride Sun, 12/11/2016 Afternoon Ride 2:34:13 37.05 mi 1,463 ft
Ride Sun, 11/29/2015 Mt Hamilton 3:40:48 37.00 mi 4,902 ft
Ride Sat, 1/30/2016 Morning Ride 2:31:37 36.88 mi 1,345 ft
Ride Mon, 2/18/2019 Niles 2:39:39 36.83 mi 571 ft
Ride Sun, 1/21/2018 Alviso 2:35:58 36.82 mi 404 ft
Ride Sat, 8/26/2017 Lunch Ride 2:41:49 36.71 mi 1,115 ft
Ride Sat, 9/13/2014 Emerald Hills 2:55:30 36.71 mi 2,143 ft
Ride Sun, 5/22/2016 Lunch Ride 2:12:20 36.68 mi 1,332 ft
Ride Sat, 11/25/2017 Mt. Hamilton 3:41:22 36.65 mi 4,806 ft
Ride Fri, 10/13/2017 Voyage 2:46:15 36.63 mi 505 ft
Ride Tue, 5/12/2015 05/12/2015 Palo Alto, CA 2:47:15 36.54 mi 499 ft
Ride Mon, 4/20/2015 Commute 2:57:44 36.31 mi 119 ft
Ride Fri, 5/13/2016 Morning Ride 2:41:03 36.21 mi 456 ft
Ride Sat, 12/3/2016 Morning Ride 3:08:37 36.20 mi 2,881 ft
Ride Sun, 12/7/2014 12/07/2014 Palo Alto, CA 2:43:35 36.16 mi 1,569 ft
Ride Sun, 9/30/2018 Canada 2:48:41 36.12 mi 1,480 ft
Ride Fri, 3/30/2018 Morning Ride 2:38:07 36.11 mi 285 ft
Ride Sun, 11/5/2017 Lunch Ride 3:24:01 35.91 mi 2,231 ft
Ride Sun, 2/8/2015 Commute 2:28:46 35.85 mi 1,142 ft
Ride Tue, 6/26/2018 Return from Chabot Lake with Juliet MTB 3:21:30 35.81 mi 817 ft
Ride Sat, 3/30/2019 Morning Ride 3:23:55 35.78 mi 2,861 ft
Ride Sun, 11/22/2015 Pigeon Point 3:14:41 35.73 mi 3,230 ft
Ride Mon, 9/19/2016 Sunnyvale 2:37:04 35.68 mi 745 ft
Ride Thu, 4/9/2015 Palo Alto Cycling 2:35:47 35.51 mi 117 ft
Ride Sun, 8/21/2016 Canada 2:20:21 35.42 mi 1,348 ft
Ride Tue, 10/28/2014 Woodside 2:36:36 35.39 mi 1,560 ft
Ride Sun, 1/27/2019 Crystal Springs Dam Bridge 2:28:21 35.38 mi 1,480 ft
Ride Thu, 4/26/2018 San Mateo 2:38:26 35.31 mi 666 ft
Ride Thu, 12/22/2016 Half Moon Bay 2:53:00 35.26 mi 2,671 ft
Ride Tue, 6/7/2016 Los Altos 2:41:13 35.18 mi 1,070 ft
Ride Sat, 3/15/2014 Canada - Steve 2:51:50 35.01 mi 1,607 ft
Ride Sun, 12/6/2015 Canada Rd 2:15:05 34.67 mi 1,237 ft
Ride Wed, 9/13/2017 Healdburg / Jimtown 2:08:03 34.45 mi 912 ft
Ride Thu, 10/2/2014 10/02/2014 Mountain View, California 3:08:06 34.28 mi 2,864 ft
Ride Sat, 2/1/2014 OLH to Page Mill 3:07:43 34.26 mi 3,099 ft
Ride Sat, 10/21/2017 Pescadero 5:20:08 67.05 mi 4,938 ft
Ride Sat, 7/30/2016 Morning Ride 2:50:15 34.19 mi 2,799 ft
Ride Mon, 7/10/2017 Morning Ride 2:38:59 34.18 mi 984 ft
Ride Thu, 7/31/2014 Work commute 2:36:53 34.15 mi 564 ft
Ride Sat, 12/15/2018 Lunch Ride 2:50:19 34.10 mi 1,683 ft
Ride Sat, 1/20/2018 Afternoon Ride 2:28:18 34.02 mi 525 ft
Ride Sun, 12/4/2016 Morning Ride 2:29:29 34.00 mi 1,161 ft
Ride Sat, 10/8/2016 Lunch Ride 2:25:17 33.95 mi 1,375 ft
Ride Thu, 10/16/2014 Commute 2:40:01 33.76 mi 615 ft
Ride Fri, 11/11/2016 OLH 2:44:59 33.69 mi 2,175 ft
Ride Sat, 9/3/2016 Morning Ride 3:07:28 33.65 mi 2,792 ft
Ride Sun, 4/19/2015 Canada Rd 2:23:17 33.56 mi 1,466 ft
Ride Sat, 8/12/2017 Afternoon Ride 2:41:24 33.54 mi 633 ft
Ride Sat, 2/14/2015 Rancho San Antonio 2:45:23 33.53 mi 2,075 ft
Ride Sat, 2/27/2016 Morning Ride 2:47:15 33.51 mi 2,005 ft
Ride Sun, 9/17/2017 Canada and off road 2:36:04 33.50 mi 1,772 ft
Ride Sat, 2/24/2018 Los Altos Hills on and off road 3:10:47 33.49 mi 2,343 ft
Ride Sun, 1/26/2014 Canada Rd 2:05:59 33.12 mi 1,446 ft
Ride Sat, 4/27/2019 Morning Ride 2:31:48 33.04 mi 1,722 ft
Ride Sun, 5/12/2019 Peninsula Bike Trail to Bay Trail 2:29:05 33.00 mi 846 ft
Ride Sun, 8/20/2017 Lunch Ride 2:23:02 32.96 mi 1,407 ft
Ride Sun, 3/6/2016 Lunch Ride 2:57:54 32.86 mi 2,566 ft
Ride Sun, 3/5/2017 Lunch Ride 2:57:09 32.82 mi 1,713 ft
Ride Sat, 12/16/2017 Morning Ride 2:48:58 32.72 mi 2,320 ft
Ride Tue, 8/23/2016 Morning Ride 2:32:15 32.61 mi 354 ft
Ride Fri, 12/9/2016 Morning Ride 3:04:52 32.55 mi 2,365 ft
Ride Sun, 11/1/2015 Lunch Ride 2:20:35 32.49 mi 1,553 ft
Ride Sat, 4/14/2018 Lunch Ride 2:21:49 32.43 mi 666 ft
Ride Fri, 1/26/2018 Morning Ride 2:23:38 32.41 mi 295 ft
Ride Sun, 8/16/2015 08/16/2015 Palo Alto, CA 2:29:53 32.40 mi 1,650 ft
Ride Sat, 5/9/2015 OLH 2:30:17 32.33 mi 2,788 ft
Ride Wed, 1/31/2018 Morning Ride 3:04:15 32.28 mi 2,526 ft
Ride Sat, 4/4/2015 Rancho San Antonio 2:55:37 32.26 mi 2,136 ft
Ride Sat, 3/16/2019 Morning Ride 2:58:43 32.26 mi 2,316 ft
Ride Sun, 12/27/2015 Canada Rd with Juliet 2:24:56 32.22 mi 1,491 ft
Ride Mon, 5/18/2015 05/18/2015 Palo Alto, CA 2:24:45 32.22 mi 807 ft
Ride Sun, 4/8/2018 Lunch Ride 2:21:54 32.18 mi 1,296 ft
Ride Sun, 5/3/2015 Canada Rd 2:12:24 32.02 mi 1,384 ft
Ride Wed, 6/17/2015 06/17/2015 Mountain View, California 2:13:42 32.01 mi 650 ft
Ride Sat, 11/7/2015 Crystal Springs Part 1 2:15:00 32.00 mi 0 ft
Ride Sun, 8/23/2015 Afternoon Ride 2:24:29 31.90 mi 2,444 ft
Ride Sun, 4/12/2015 Palo Alto Cycling 2:01:49 31.76 mi 1,210 ft
Ride Sun, 11/13/2016 Afternoon Ride 2:23:46 31.71 mi 1,273 ft
Ride Fri, 10/7/2016 Morning Ride 2:26:46 31.65 mi 2,382 ft
Ride Thu, 8/15/2013 08/15/2013 Palo Alto, CA 2:19:27 31.64 mi 555 ft
Ride Wed, 10/30/2013 work 2:22:00 31.55 mi 995 ft
Ride Sun, 9/18/2016 Morning Ride 2:20:46 31.48 mi 1,506 ft
Ride Tue, 6/18/2013 work etc (headwinds) 2:03:34 31.48 mi 809 ft
Ride Sun, 3/11/2018 Lunch Ride 2:23:14 31.42 mi 686 ft
Ride Sat, 3/12/2016 Lunch Ride 2:10:01 31.39 mi 1,198 ft
Ride Sun, 1/22/2017 Dumbarton 2:23:36 31.27 mi 591 ft
Ride Sat, 9/29/2018 Kings 2:36:47 31.23 mi 1,949 ft
Ride Sat, 3/23/2019 Morning Ride 2:33:27 31.19 mi 1,529 ft
Ride Sat, 4/13/2019 Alviso 2:10:04 31.18 mi 397 ft
Ride Mon, 11/10/2014 Commute 2:22:39 31.18 mi 800 ft
Ride Mon, 5/25/2015 05/25/2015 Palo Alto, CA 2:19:26 31.14 mi 1,591 ft
Ride Mon, 2/16/2015 Portola Valley Loop 2:09:15 31.11 mi 1,283 ft
Ride Wed, 9/21/2016 Morning Ride 2:16:04 31.09 mi 551 ft
Ride Sat, 3/11/2017 Afternoon Ride 2:19:05 31.04 mi 1,368 ft
Ride Sun, 7/29/2018 Lunch Ride 2:26:56 30.92 mi 1,578 ft
Ride Fri, 11/10/2017 Lunch Ride 2:20:46 30.92 mi 312 ft
Ride Mon, 6/12/2017 Morning Ride 2:20:16 30.90 mi 554 ft
Ride Thu, 12/29/2016 Morning Ride 2:13:46 30.86 mi 1,083 ft
Ride Sat, 4/22/2017 Lunch Ride 2:21:31 30.80 mi 1,237 ft
Ride Mon, 9/7/2015 Healdsburg 2:09:43 30.76 mi 1,037 ft
Ride Sat, 1/14/2017 Afternoon Ride 2:12:37 30.71 mi 466 ft
Ride Mon, 9/4/2017 Kings Mountain 2:40:57 30.69 mi 2,431 ft
Ride Sun, 8/9/2015 08/09/2015 Palo Alto, CA 2:15:39 30.66 mi 1,348 ft
Ride Sat, 12/12/2015 Lunch Ride 2:16:20 30.56 mi 1,434 ft
Ride Thu, 12/28/2017 Morning Ride 2:17:38 30.54 mi 430 ft
Ride Mon, 1/30/2017 Morning Ride 2:19:47 30.49 mi 696 ft
Ride Sun, 8/27/2017 Lunch Ride 2:16:36 30.47 mi 774 ft
Ride Fri, 10/12/2018 Lunch Ride 2:16:25 30.44 mi 348 ft
Ride Mon, 6/25/2018 Chabot Lake Overnight Bike packing with Juliet MTB 3:02:49 30.41 mi 1,250 ft
Ride Sat, 2/28/2015 Palo Alto Cycling 2:40:24 30.37 mi 2,535 ft
Ride Sat, 6/24/2017 Afternoon Ride 2:06:05 30.31 mi 338 ft
Ride Tue, 6/2/2015 06/02/2015 Mountain View, California 2:09:58 30.30 mi 784 ft
Ride Mon, 5/27/2019 Canada / Sheep camp / Water dog lake 2:37:11 30.27 mi 1,444 ft
Ride Sat, 1/2/2016 Los Altos Hills 2:29:48 30.27 mi 1,952 ft
Ride Fri, 6/23/2017 Morning Ride 2:17:16 30.27 mi 515 ft
Ride Sun, 4/2/2017 Morning Ride 2:20:47 30.23 mi 407 ft
Ride Sat, 8/25/2018 Morning Ride 2:22:42 30.22 mi 2,257 ft
Ride Sun, 9/14/2014 09/14/2014 Palo Alto, CA 2:06:48 30.18 mi 1,204 ft
Ride Sat, 8/19/2017 Lunch Ride 2:06:39 30.17 mi 627 ft
Ride Sun, 3/12/2017 Morning Ride 2:18:13 30.16 mi 600 ft
Ride Sat, 11/21/2015 Morning Ride 2:20:54 30.16 mi 1,499 ft
Ride Wed, 10/12/2016 Morning Ride 2:19:51 30.11 mi 328 ft
Ride Sun, 7/8/2018 Lunch Ride 2:12:19 30.04 mi 768 ft
Ride Tue, 11/22/2016 Morning Ride 2:18:57 30.04 mi 518 ft
Ride Wed, 3/4/2015 Commute 2:15:56 30.02 mi 1,125 ft
Ride Sun, 3/25/2018 Dumbarton / Coyote Hills / Middle of the Bay 2:31:51 30.01 mi 709 ft
Ride Tue, 5/31/2016 Google Bike around campus 2:30:00 30.00 mi 0 ft
Ride Tue, 2/14/2017 Morning Ride 2:22:16 29.92 mi 463 ft
Ride Sun, 5/31/2015 05/31/2015 Palo Alto, California 2:35:24 29.85 mi 2,568 ft
Ride Tue, 11/1/2016 Morning Ride 2:15:55 29.78 mi 515 ft
Ride Sun, 6/5/2016 Morning Ride 2:18:02 29.70 mi 1,293 ft
Ride Sun, 5/26/2019 San Carlos / Bay Trail 2:17:51 29.55 mi 620 ft
Ride Thu, 8/13/2015 08/13/2015 Palo Alto, CA 2:13:58 29.51 mi 778 ft
Ride Sun, 7/14/2013 07/14/2013 Palo Alto, CA 2:23:07 29.34 mi 739 ft
Ride Sun, 4/3/2016 Lunch Ride 2:03:38 29.31 mi 810 ft
Ride Mon, 3/9/2015 Palo Alto Cycling 1:57:50 29.21 mi 115 ft
Ride Fri, 2/23/2018 Morning Ride 2:08:28 29.21 mi 166 ft
Ride Sat, 10/7/2017 Afternoon Ride 2:02:43 29.18 mi 394 ft
Ride Sat, 1/17/2015 Palo Alto Cycling 2:08:38 29.06 mi 1,587 ft
Ride Sat, 1/18/2014 Los Altos Hills 2:32:23 29.03 mi 1,918 ft
Ride Sat, 6/20/2015 06/20/2015 Palo Alto, CA 2:06:45 28.87 mi 1,650 ft
Ride Sun, 11/3/2013 work 2:11:08 28.74 mi 841 ft
Ride Wed, 8/16/2017 Lunch Ride 2:15:48 28.67 mi 732 ft
Ride Sat, 1/9/2016 Morning Ride 2:07:35 28.54 mi 1,289 ft
Ride Sat, 9/19/2015 Los Altos Hills 2:31:24 28.51 mi 1,611 ft
Ride Wed, 8/12/2015 08/12/2015 Palo Alto, CA 2:02:14 28.48 mi 751 ft
Ride Sun, 7/23/2017 Morning Ride 2:57:35 28.43 mi 2,562 ft
Ride Mon, 8/24/2015 Morning Ride 2:10:31 28.35 mi 427 ft
Ride Wed, 6/26/2013 work 2:01:29 28.33 mi 627 ft
Ride Sun, 4/28/2019 Arastradsero 2:29:59 28.21 mi 1,355 ft
Ride Sun, 7/19/2015 07/19/2015 Palo Alto, CA 2:00:27 28.17 mi 1,037 ft
Ride Fri, 12/28/2018 Lunch Ride with Juliet 2:17:33 28.10 mi 331 ft
Ride Tue, 5/17/2016 Morning Ride 2:06:55 28.09 mi 551 ft
Ride Wed, 10/5/2016 Morning Ride 2:10:47 28.05 mi 276 ft
Ride Wed, 6/22/2016 Morning Ride 2:00:40 27.99 mi 374 ft
Ride Mon, 3/16/2015 Commute 2:06:51 27.97 mi 70 ft
Ride Sun, 2/25/2018 Lunch Ride 2:10:54 27.88 mi 958 ft
Ride Sat, 10/27/2018 Morning Ride 2:29:13 27.88 mi 1,581 ft
Ride Mon, 6/20/2016 Morning Ride 1:52:13 27.77 mi 531 ft
Ride Sun, 12/2/2018 Lunch Ride 2:34:14 27.72 mi 1,506 ft
Ride Mon, 3/19/2018 Morning Ride 2:06:35 27.62 mi 860 ft
Ride Mon, 12/17/2018 Morning Ride 2:16:12 27.60 mi 246 ft
Ride Sat, 10/14/2017 Afternoon Ride 2:04:53 27.52 mi 387 ft
Ride Sat, 10/18/2014 Bikepacking Monte Bello 2:54:20 27.44 mi 2,992 ft
Ride Sun, 4/15/2018 Morning Ride 2:17:16 27.43 mi 1,667 ft
Ride Mon, 4/10/2017 Morning Ride 2:03:19 27.40 mi 282 ft
Ride Tue, 5/7/2019 Lunch Ride 2:07:55 27.37 mi 820 ft
Ride Sun, 11/25/2018 Afternoon Ride 1:56:26 27.34 mi 203 ft
Ride Wed, 4/29/2015 Palo Alto Cycling 2:03:14 27.33 mi 75 ft
Ride Tue, 1/17/2017 Morning Ride 2:07:17 27.30 mi 535 ft
Ride Tue, 9/30/2014 09/30/2014 Palo Alto, CA 2:07:33 27.28 mi 636 ft
Ride Sat, 9/17/2016 TourDeCoop.org 2:04:27 27.24 mi 479 ft
Ride Sat, 1/12/2019 Lunch Ride 1:58:47 27.24 mi 1,079 ft
Ride Mon, 3/30/2015 Woodside Loop 1:47:46 27.22 mi 1,081 ft
Ride Tue, 11/17/2015 Afternoon Ride 2:04:48 27.12 mi 830 ft
Ride Sun, 8/13/2017 Woodside Loop (Battery ran down) 2:00:00 27.00 mi 1,000 ft
Ride Sat, 1/31/2015 Alpine Rd 2:29:26 26.98 mi 2,362 ft
Ride Sun, 2/28/2016 Woodside Loop 1:43:34 26.93 mi 843 ft
Ride Sun, 4/1/2018 Lunch Ride 2:30:35 26.92 mi 1,831 ft
Workout Wed, 5/3/2017 Palo Alto Road Cycling 2:06:05 26.86 mi 1,411 ft
Ride Fri, 9/9/2016 Morning Ride 1:56:44 26.85 mi 1,086 ft
Ride Sun, 9/23/2018 Morning Ride 1:57:48 26.83 mi 512 ft
Ride Sat, 10/4/2014 Woodside Loop 1:52:09 26.71 mi 1,213 ft
Ride Thu, 5/7/2015 Commute 1:55:36 26.58 mi 68 ft
Ride Sun, 3/24/2019 Afternoon Ride 2:42:45 26.57 mi 2,267 ft
Ride Thu, 8/31/2017 Morning Ride 2:00:02 26.54 mi 817 ft
Ride Sun, 4/24/2016 Afternoon Ride 1:50:08 26.53 mi 935 ft
Ride Sun, 12/17/2017 Lunch Ride 1:50:51 26.53 mi 217 ft
Ride Tue, 7/4/2017 Lunch Ride 1:53:04 26.50 mi 459 ft
Ride Tue, 5/8/2018 Afternoon Ride 2:34:11 26.50 mi 1,791 ft
Ride Tue, 5/26/2015 05/26/2015 Palo Alto, CA 2:01:47 26.50 mi 591 ft
Ride Thu, 8/4/2016 Afternoon Ride 1:55:47 26.49 mi 659 ft
Ride Wed, 6/8/2016 Lunch Ride 1:48:19 26.48 mi 597 ft
Ride Sun, 9/9/2018 Lunch Ride 2:00:49 26.46 mi 479 ft
Ride Sat, 5/21/2016 Maker Faire 1:57:14 26.44 mi 207 ft
Ride Wed, 4/1/2015 Commute 1:55:15 26.44 mi 71 ft
Ride Wed, 6/29/2016 Morning Ride 1:49:33 26.39 mi 561 ft
Ride Sat, 9/9/2017 Lunch Ride 1:59:01 26.38 mi 1,112 ft
Ride Fri, 6/3/2016 Morning Ride 1:56:57 26.38 mi 502 ft
Ride Fri, 3/31/2017 Morning Ride 2:01:41 26.36 mi 495 ft
Ride Sat, 12/13/2014 Westridge (up the back way; after the rain) 2:01:01 26.35 mi 1,495 ft
Ride Sun, 4/14/2019 Huddart - Paul Hopkins 2:08:57 26.34 mi 1,601 ft
Ride Sat, 6/29/2013 Untitled 1:48:50 26.33 mi 1,091 ft
Ride Sat, 4/25/2015 Woodside 1:54:30 26.28 mi 1,220 ft
Ride Tue, 1/31/2017 Morning Ride 2:01:18 26.27 mi 384 ft
Ride Tue, 6/28/2016 Morning Ride 1:55:31 26.27 mi 571 ft
Ride Sat, 5/19/2018 Morning Ride 2:00:24 26.25 mi 958 ft
Ride Thu, 6/30/2016 Morning Ride 1:52:16 26.24 mi 499 ft
Ride Sun, 2/19/2017 Afternoon Ride 1:56:46 26.23 mi 1,138 ft
Ride Thu, 8/18/2016 Morning Ride 1:58:06 26.23 mi 712 ft
Ride Sun, 4/20/2014 Afternoon Ride 1:52:52 26.23 mi 102 ft
Ride Sun, 5/10/2015 Arastadero 1:48:58 26.22 mi 1,148 ft
Ride Thu, 5/19/2016 Morning Ride 1:52:56 26.14 mi 938 ft
Ride Mon, 12/31/2018 Morning Ride 2:00:24 26.11 mi 545 ft
Ride Fri, 12/15/2017 Afternoon Ride 1:51:36 26.07 mi 226 ft
Ride Sun, 1/18/2015 Palo Alto Cycling 1:38:16 26.02 mi 1,257 ft
Ride Mon, 10/3/2016 Morning Ride 1:54:45 26.01 mi 646 ft
Ride Tue, 7/14/2015 07/14/2015 Mountain View, California 2:02:36 25.99 mi 502 ft
Ride Fri, 12/18/2015 Lunch Ride 1:58:57 25.93 mi 909 ft
Ride Tue, 6/13/2017 Morning Ride 1:55:30 25.89 mi 289 ft
Ride Mon, 8/4/2014 To the Sea 2:17:11 25.87 mi 2,080 ft
Ride Wed, 9/25/2013 work and with kris 2:07:40 25.81 mi 593 ft
Ride Sun, 9/13/2015 Afternoon Ride 1:49:31 25.78 mi 1,152 ft
Ride Sun, 3/3/2019 Afternoon Ride 2:00:06 25.73 mi 390 ft
Ride Thu, 1/26/2017 Morning Ride 1:56:09 25.67 mi 259 ft
Ride Sun, 5/6/2018 Morning Ride 1:55:05 25.67 mi 1,148 ft
Ride Sat, 7/29/2017 Lunch Ride 1:53:50 25.65 mi 988 ft
Ride Wed, 8/20/2014 08/20/2014 Palo Alto, CA 1:49:35 25.61 mi 497 ft
Ride Sat, 4/19/2014 Lunch Ride 1:49:37 25.60 mi 1,111 ft
Ride Mon, 9/3/2018 Afternoon Ride 1:59:08 25.54 mi 331 ft
Ride Mon, 6/2/2014 Work 1:59:33 25.51 mi 627 ft
Ride Fri, 12/11/2015 Afternoon Ride 2:12:40 25.44 mi 1,719 ft
Ride Fri, 2/9/2018 Morning Ride 1:56:11 25.41 mi 318 ft
Ride Sun, 8/26/2018 Afternoon Ride 1:49:16 25.37 mi 377 ft
Ride Sun, 7/9/2017 Afternoon Ride 1:50:30 25.36 mi 682 ft
Ride Mon, 6/26/2017 Morning Ride 1:52:26 25.34 mi 226 ft
Ride Fri, 9/14/2018 Lunch Ride 1:50:05 25.34 mi 541 ft
Ride Fri, 3/1/2019 Morning Ride 2:00:23 25.34 mi 121 ft
Ride Sun, 1/13/2019 Lunch Ride 1:51:40 25.33 mi 715 ft
Ride Wed, 4/12/2017 Morning Ride 1:59:22 25.33 mi 686 ft
Ride Wed, 6/14/2017 Morning Ride 1:51:32 25.33 mi 308 ft
Ride Sat, 11/28/2015 Lunch Ride 1:43:56 25.31 mi 1,122 ft
Ride Mon, 11/27/2017 Morning Ride 1:55:39 25.31 mi 184 ft
Ride Fri, 11/28/2014 11/28/2014 Palo Alto, CA 1:45:13 25.30 mi 1,112 ft
Ride Fri, 4/13/2018 Morning Ride 1:53:39 25.28 mi 344 ft
Ride Fri, 9/15/2017 Morning Ride 1:56:55 25.28 mi 384 ft
Ride Tue, 12/31/2013 Woodside Loop - Last Ride of the Year 1:45:28 25.27 mi 1,207 ft
Ride Sun, 10/14/2018 Lunch Ride 2:13:13 25.23 mi 1,421 ft
Ride Sat, 11/22/2014 Atherton to Woodside 1:43:10 25.21 mi 1,095 ft
Ride Sat, 6/4/2016 Morning Ride 1:57:23 25.19 mi 922 ft
Ride Mon, 8/8/2016 Morning Ride 1:52:38 25.17 mi 423 ft
Ride Fri, 11/6/2015 Morning Ride 1:51:45 25.15 mi 538 ft
Ride Mon, 12/18/2017 Afternoon Ride 1:52:56 25.15 mi 741 ft
Ride Fri, 6/24/2016 Afternoon Ride 1:35:25 25.11 mi 623 ft
Ride Sat, 6/25/2016 Afternoon Ride 1:44:43 25.10 mi 568 ft
Ride Sat, 1/25/2014 Woodside 1:33:38 25.08 mi 1,243 ft
Ride Tue, 4/29/2014 Woodside 1:46:03 25.08 mi 886 ft
Ride Thu, 4/13/2017 Morning Ride 2:06:13 25.06 mi 266 ft
Ride Sun, 6/12/2016 Morning Ride 1:49:25 25.04 mi 387 ft
Ride Thu, 6/8/2017 Morning Ride 1:56:19 25.02 mi 285 ft
Ride Thu, 10/13/2016 Morning Ride 1:47:30 25.02 mi 253 ft
Ride Sun, 1/19/2014 01/19/2014 Palo Alto, CA 1:36:55 25.01 mi 1,201 ft
Ride Mon, 9/5/2016 Afternoon Ride 1:42:56 25.00 mi 902 ft
Ride Sat, 11/8/2014 Atherton - Woodside 1:40:51 25.00 mi 972 ft
Ride Sun, 12/10/2017 Exercise Bike 1:30:00 25.00 mi 0 ft
Ride Sun, 6/2/2019 The Sequoia 6:40:43 77.51 mi 6,467 ft
Ride Sat, 6/8/2019 Morning Ride 2:53:08 34.42 mi 1,824 ft
Ride Sat, 6/15/2019 Morning Ride 2:29:38 29.98 mi 1,785 ft
Ride Fri, 7/5/2019 Morning Ride 4:30:55 64.05 mi 1,965 ft
Can't render this file because it has a wrong number of fields in line 3.

View File

@ -1,64 +1,56 @@
Alma Mountain Charlie, 3.12, 875, 31:49
Alpine Westridge, 0.76, 99, 3:26, 3:29, 3:43
Alpine last kicker, 0.39, 114, 3:11, 3:36, 5:19
Arastradero, 1.19, 193, 6:41, 7:49, 7:55
Bring it On Home, 10.81, 237, 37:19
Cabrillo Hmy S, 0.46, 153, 4:31
Canada South Interval, 1.42, 54, 4:50, 5:08, 5:20
Canada to College, 1.37, 119, 5:55, 6:23, 6:35
Canon, 0.90, 295, 11:05, 11:56
Canon to No Cycling, 0.75, 198, 5:14, 5:41
El Camino, 0.50, 15, 2:11, 2:19, 2:22
Foothill Arastradero, 1.13, 37, 3:31, 3:44, 3:51
Foothill Homestead, 1.22, 126, 5:31, 6:36, 6:53
Foothill Magdalena, 1.66, 44, 5:51, 6:21, 6:29
Foothill Miramonte, 1.64, 39, 4:55, 5:29, 5:43
Haskins, 1.51, 566, 17:48, 18:43, 18:48
Highway 9, 0.56, 260, 8:33
Highway 9 Mantalvo, 0.45, 35, 1:36, 1:48, 1:53
Huddart, 0.92, 385, 10:15, 11:18, 12:55
Joaquin, 0.33, 253, 6:19, 7:27
Kaboom Portola Rd, 0.67, 102, 3:14, 3:26, 3:33
Kings half way, 2.89, 820, 27:26, 29:45, 32:43
Kings to Skeggs, 1.10, 273, 9:08, 10:39, 10:54
Limantour Spit, 0.47, 303, 5:20
Limantour steepest, 0.20, 159, 5:41
Lobitas Creek, 0.96, 430, 12:15, 12:07
Lower Redwood Gulch, 1.03, 474, 13:00
Mapache, 0.24, 111, 2:52, 2:56
Mt Eden climb, 1.02, 272, 8:32, 9:43, 11:59
Mt Eden to Archery, 0.54, 180, 5:20, 5:39, 6:12
Old La Honda, 2.98, 1255, 28:49, 34:04, 36:45
Old La Honda Mile 1, 0.99, 370, 8:02, 9:36, 9:51
Page Mill to Moody, 0.21, 112, 2:50, 2:55
Page Mill to Ventana, 0.47, 196, 4:54, 5:32, 6:05
Peach Hill, 0.60, 248, 7:30, 7:50
Pescadero Bean Hollow, 2.77, 51, 8:07, 10:48
Pomponio Creek, 0.38, 122, 2:47, 2:56, 3:16
Red Hill KOM, 0.34, 161, 4:15
Redwood Gulch hits, 0.18, 151, 3:36
Redwood Gulch wall, 0.43, 258, 6:39
Sand Gill Sharon-top, 0.85, 136, 4:04, 4:16, 4:24
Sand Hill 280 to horse, 0.49, 95, 2:34, 2:44, 2:51
Sand Hill Alpine to 280, 1.67, 180, 7:02, 7:40, 8:02
Stair Step, 0.32, 175, 5:25
Stanford Ave, 0.63, 85, 3:14, 3:32, 3:49
Stevens Canyon, 2.85, 127, 12:51, 13:46, 18:12
Stevens Country Park, 1.22, 112, 5:43, 5:59, 7:00
Stirrup Wall, 0.17, 125, 3:47, 4:33
Stirrup to Moon, 0.36, 159, 3:41, 6:07
Summit Rd Climb, 0.60, 275, 8:00
Tepa, 0.58, 248, 6:34, 7:14, 8:21
The Boneyard, 1.48, 135, 6:05, 6:36, 7:00
Try not to fall back, 0.71, 410, 12:45
Tunitas Knoll to Star, 1.55, 339, 12:28, 12:56, 13:51
Tunitas flattens, 0.42, 166, 3:16, 3:25, 3:51
Tunitas lower climb, 1.30, 421, 13:34, 16:00, 17:33
Tunitas steep, 1.20, 599, 16:16, 17:48, 20:44
Vickrey Fruitvale, 0.99, 68, 3:41, 3:58, 4:27
West Alpine switchback, 0.78, 322, 8:56, 10:52, 11:56
Westridge, 0.68, 385, 8:28, 9:52, 10:19
Westridge 3min, 0.37, 240, 4:41, 5:35, 6:31
Westridge Hill 1, 0.51, 104, 3:19, 3:58
Westridge Hill 2, 0.51, 166, 4:42, 5:10, 5:21
Woodside Climb, 1.71, 295, 8:05, 9:00, 9:35
Alma Mountain Charlie,          3.12,  875, 31:49
Alpine Westridge,               0.76,   99, 3:26, 3:29, 3:43
Alpine last kicker,             0.39,  114, 3:11, 3:36, 5:19
Arastradero,                    1.19,  193, 6:41, 7:49, 7:55
Cabrillo Hmy S,                 0.46,  153, 4:31
Canada to College,              1.37,  119, 5:55, 6:23, 6:35
Canon,                          0.90,  295, 11:04, 11:55
Canon to No Cycling,            0.75,  198, 5:14, 5:41
Foothill Homestead,             1.22,  126, 5:31, 6:36, 6:53
Haskins,                        1.51,  566, 17:47, 18:43, 18:47
Highway 9,                      0.56,  260, 8:33
Highway 9 Mantalvo,             0.45,   35, 1:36, 1:48, 1:53
Huddart,                        0.92,  385, 10:15, 11:18, 12:55
Joaquin,                        0.33,  253, 6:19, 7:27
Kaboom Portola Rd,              0.67,  102, 3:14, 3:26, 3:33
Kings half way,                 2.89,  820, 27:26, 29:45, 32:43
Kings to Skeggs,                1.10,  273, 9:07, 10:39, 10:53
Limantour Spit,                 0.47,  303, 5:19
Limantour steepest,             0.20,  159, 5:41
Lobitas Creek,                  0.96,  430, 12:15, 12:07
Lower Redwood Gulch,            1.03,  474, 13:00
Mapache,                        0.24,  111, 2:52, 2:55
Mt Eden climb,                  1.02,  272, 8:32, 9:43, 11:59
Mt Eden to Archery,             0.54,  180, 5:19, 5:39, 6:12
Old La Honda,                   2.98, 1255, 28:49, 34:03, 36:44
Old La Honda Mile 1,            0.99,  370, 8:02, 9:36, 9:51
Page Mill to Moody,             0.21,  112, 2:50, 2:55
Page Mill to Ventana,           0.47,  196, 4:54, 5:32, 6:05
Peach Hill,                     0.60,  248, 7:30, 7:50
Pomponio Creek,                 0.38,  122, 2:47, 2:55, 3:16
Red Hill KOM,                   0.34,  161, 4:15
Redwood Gulch hits,             0.18,  151, 3:36
Redwood Gulch wall,             0.43,  258, 6:39
Sand Gill Sharon-top,           0.85,  136, 4:03, 4:16, 4:24
Sand Hill 280 to horse,         0.49,   95, 2:34, 2:44, 2:51
Sand Hill Alpine to 280,        1.67,  180, 7:02, 7:39, 8:02
Stair Step,                     0.32,  175, 5:25
Stanford Ave,                   0.63,   85, 3:14, 3:32, 3:49
Stevens Country Park,           1.22,  112, 5:42, 5:58, 7:00
Stirrup Wall,                   0.17,  125, 3:47, 4:33
Stirrup to Moon,                0.36,  159, 3:41, 6:07
Summit Rd Climb,                0.60,  275, 8:00
Tepa,                           0.58,  248, 6:34, 7:14, 8:21
The Boneyard,                   1.48,  135, 6:05, 6:36, 7:00
Try not to fall back,           0.71,  410, 12:45
Tunitas Knoll to Star,          1.55,  339, 12:28, 12:56, 13:51
Tunitas flattens,               0.42,  166, 3:16, 3:25, 3:51
Tunitas lower climb,            1.30,  421, 13:34, 16:00, 17:33
Tunitas steep,                  1.20,  599, 16:15, 17:47, 20:44
Vickrey Fruitvale,              0.99,   68, 3:41, 3:58, 4:26
West Alpine switchback,         0.78,  322, 8:56, 10:52, 11:55
Westridge,                      0.68,  385, 8:28, 9:52, 10:18
Westridge 3min,                 0.37,  240, 4:41, 5:35, 6:31
Westridge Hill 1,               0.51,  104, 3:19, 3:58
Westridge Hill 2,               0.51,  166, 4:42, 5:10, 5:20
Woodside Climb,                 1.71,  295, 8:05, 9:00, 9:35
1 Alma Mountain Charlie, 3.12, 875, 31:49 Alma Mountain Charlie,          3.12,  875, 31:49
2 Alpine Westridge, 0.76, 99, 3:26, 3:29, 3:43 Alpine Westridge,               0.76,   99, 3:26, 3:29, 3:43
3 Alpine last kicker, 0.39, 114, 3:11, 3:36, 5:19 Alpine last kicker,             0.39,  114, 3:11, 3:36, 5:19
4 Arastradero, 1.19, 193, 6:41, 7:49, 7:55 Arastradero,                    1.19,  193, 6:41, 7:49, 7:55
5 Bring it On Home, 10.81, 237, 37:19 Cabrillo Hmy S,                 0.46,  153, 4:31
6 Cabrillo Hmy S, 0.46, 153, 4:31 Canada to College,              1.37,  119, 5:55, 6:23, 6:35
7 Canada South Interval, 1.42, 54, 4:50, 5:08, 5:20 Canon,                          0.90,  295, 11:04, 11:55
8 Canada to College, 1.37, 119, 5:55, 6:23, 6:35 Canon to No Cycling,            0.75,  198, 5:14, 5:41
9 Canon, 0.90, 295, 11:05, 11:56 Foothill Homestead,             1.22,  126, 5:31, 6:36, 6:53
10 Canon to No Cycling, 0.75, 198, 5:14, 5:41 Haskins,                        1.51,  566, 17:47, 18:43, 18:47
11 El Camino, 0.50, 15, 2:11, 2:19, 2:22 Highway 9,                      0.56,  260, 8:33
12 Foothill Arastradero, 1.13, 37, 3:31, 3:44, 3:51 Highway 9 Mantalvo,             0.45,   35, 1:36, 1:48, 1:53
13 Foothill Homestead, 1.22, 126, 5:31, 6:36, 6:53 Huddart,                        0.92,  385, 10:15, 11:18, 12:55
14 Foothill Magdalena, 1.66, 44, 5:51, 6:21, 6:29 Joaquin,                        0.33,  253, 6:19, 7:27
15 Foothill Miramonte, 1.64, 39, 4:55, 5:29, 5:43 Kaboom Portola Rd,              0.67,  102, 3:14, 3:26, 3:33
16 Haskins, 1.51, 566, 17:48, 18:43, 18:48 Kings half way,                 2.89,  820, 27:26, 29:45, 32:43
17 Highway 9, 0.56, 260, 8:33 Kings to Skeggs,                1.10,  273, 9:07, 10:39, 10:53
18 Highway 9 Mantalvo, 0.45, 35, 1:36, 1:48, 1:53 Limantour Spit,                 0.47,  303, 5:19
19 Huddart, 0.92, 385, 10:15, 11:18, 12:55 Limantour steepest,             0.20,  159, 5:41
20 Joaquin, 0.33, 253, 6:19, 7:27 Lobitas Creek,                  0.96,  430, 12:15, 12:07
21 Kaboom Portola Rd, 0.67, 102, 3:14, 3:26, 3:33 Lower Redwood Gulch,            1.03,  474, 13:00
22 Kings half way, 2.89, 820, 27:26, 29:45, 32:43 Mapache,                        0.24,  111, 2:52, 2:55
23 Kings to Skeggs, 1.10, 273, 9:08, 10:39, 10:54 Mt Eden climb,                  1.02,  272, 8:32, 9:43, 11:59
24 Limantour Spit, 0.47, 303, 5:20 Mt Eden to Archery,             0.54,  180, 5:19, 5:39, 6:12
25 Limantour steepest, 0.20, 159, 5:41 Old La Honda,                   2.98, 1255, 28:49, 34:03, 36:44
26 Lobitas Creek, 0.96, 430, 12:15, 12:07 Old La Honda Mile 1,            0.99,  370, 8:02, 9:36, 9:51
27 Lower Redwood Gulch, 1.03, 474, 13:00 Page Mill to Moody,             0.21,  112, 2:50, 2:55
28 Mapache, 0.24, 111, 2:52, 2:56 Page Mill to Ventana,           0.47,  196, 4:54, 5:32, 6:05
29 Mt Eden climb, 1.02, 272, 8:32, 9:43, 11:59 Peach Hill,                     0.60,  248, 7:30, 7:50
30 Mt Eden to Archery, 0.54, 180, 5:20, 5:39, 6:12 Pomponio Creek,                 0.38,  122, 2:47, 2:55, 3:16
31 Old La Honda, 2.98, 1255, 28:49, 34:04, 36:45 Red Hill KOM,                   0.34,  161, 4:15
32 Old La Honda Mile 1, 0.99, 370, 8:02, 9:36, 9:51 Redwood Gulch hits,             0.18,  151, 3:36
33 Page Mill to Moody, 0.21, 112, 2:50, 2:55 Redwood Gulch wall,             0.43,  258, 6:39
34 Page Mill to Ventana, 0.47, 196, 4:54, 5:32, 6:05 Sand Gill Sharon-top,           0.85,  136, 4:03, 4:16, 4:24
35 Peach Hill, 0.60, 248, 7:30, 7:50 Sand Hill 280 to horse,         0.49,   95, 2:34, 2:44, 2:51
36 Pescadero Bean Hollow, 2.77, 51, 8:07, 10:48 Sand Hill Alpine to 280,        1.67,  180, 7:02, 7:39, 8:02
37 Pomponio Creek, 0.38, 122, 2:47, 2:56, 3:16 Stair Step,                     0.32,  175, 5:25
38 Red Hill KOM, 0.34, 161, 4:15 Stanford Ave,                   0.63,   85, 3:14, 3:32, 3:49
39 Redwood Gulch hits, 0.18, 151, 3:36 Stevens Country Park,           1.22,  112, 5:42, 5:58, 7:00
40 Redwood Gulch wall, 0.43, 258, 6:39 Stirrup Wall,                   0.17,  125, 3:47, 4:33
41 Sand Gill Sharon-top, 0.85, 136, 4:04, 4:16, 4:24 Stirrup to Moon,                0.36,  159, 3:41, 6:07
42 Sand Hill 280 to horse, 0.49, 95, 2:34, 2:44, 2:51 Summit Rd Climb,                0.60,  275, 8:00
43 Sand Hill Alpine to 280, 1.67, 180, 7:02, 7:40, 8:02 Tepa,                           0.58,  248, 6:34, 7:14, 8:21
44 Stair Step, 0.32, 175, 5:25 The Boneyard,                   1.48,  135, 6:05, 6:36, 7:00
45 Stanford Ave, 0.63, 85, 3:14, 3:32, 3:49 Try not to fall back,           0.71,  410, 12:45
46 Stevens Canyon, 2.85, 127, 12:51, 13:46, 18:12 Tunitas Knoll to Star,          1.55,  339, 12:28, 12:56, 13:51
47 Stevens Country Park, 1.22, 112, 5:43, 5:59, 7:00 Tunitas flattens,               0.42,  166, 3:16, 3:25, 3:51
48 Stirrup Wall, 0.17, 125, 3:47, 4:33 Tunitas lower climb,            1.30,  421, 13:34, 16:00, 17:33
49 Stirrup to Moon, 0.36, 159, 3:41, 6:07 Tunitas steep,                  1.20,  599, 16:15, 17:47, 20:44
50 Summit Rd Climb, 0.60, 275, 8:00 Vickrey Fruitvale,              0.99,   68, 3:41, 3:58, 4:26
51 Tepa, 0.58, 248, 6:34, 7:14, 8:21 West Alpine switchback,         0.78,  322, 8:56, 10:52, 11:55
52 The Boneyard, 1.48, 135, 6:05, 6:36, 7:00 Westridge,                      0.68,  385, 8:28, 9:52, 10:18
53 Try not to fall back, 0.71, 410, 12:45 Westridge 3min,                 0.37,  240, 4:41, 5:35, 6:31
54 Tunitas Knoll to Star, 1.55, 339, 12:28, 12:56, 13:51 Westridge Hill 1,               0.51,  104, 3:19, 3:58
55 Tunitas flattens, 0.42, 166, 3:16, 3:25, 3:51 Westridge Hill 2,               0.51,  166, 4:42, 5:10, 5:20
56 Tunitas lower climb, 1.30, 421, 13:34, 16:00, 17:33 Woodside Climb,                 1.71,  295, 8:05, 9:00, 9:35
Tunitas steep, 1.20, 599, 16:16, 17:48, 20:44
Vickrey Fruitvale, 0.99, 68, 3:41, 3:58, 4:27
West Alpine switchback, 0.78, 322, 8:56, 10:52, 11:56
Westridge, 0.68, 385, 8:28, 9:52, 10:19
Westridge 3min, 0.37, 240, 4:41, 5:35, 6:31
Westridge Hill 1, 0.51, 104, 3:19, 3:58
Westridge Hill 2, 0.51, 166, 4:42, 5:10, 5:21
Woodside Climb, 1.71, 295, 8:05, 9:00, 9:35

BIN
ipynb/convexhull.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

559
ipynb/flipping.ipynb Normal file

File diff suppressed because one or more lines are too long

1
ipynb/natalie.txt Normal file

File diff suppressed because one or more lines are too long

View File

@ -257,7 +257,7 @@
"metadata": {},
"source": [
"The main point of the `Panama` class is to allow the `.search()` method to search for a long palindrome. The [overall strategy](http://norvig.com/pal-alg.html#v3) is explained elsewhere. The search\n",
"adds a lett1er at a time to both left and right side, backtracking when necessary. The state of the search is represented by the four fields `.left, .L, .R, .right`, for example:\n",
"adds a letter at a time to both left and right side, backtracking when necessary. The state of the search is represented by the four fields `.left, .L, .R, .right`, for example:\n",
" \n",
" A man, a plan, a cam, a yak, a yam, a canal, Panama\n",
" \n",
@ -272,7 +272,7 @@
"\n",
" cat(left + [L]) == cat([R] + right)[::-1]\n",
" \n",
"The `search` method would be more straightforward if we could write it is a recursive function. But Python does not perform well when recursing to 100 thousand levels deep. So instead I manually manage a *stack* of *commands* that tell the search what to do and to undo. A command can be a `DoCommand`, which consists of a list of letters describing all the possible actions we could take at this point. Actions include letters that could be added (only the letters that make a legitimate prefix of a known phrase in `.L` and a legitimate suffix of a known phrase in `.R`). Actions can also include the character `','`, which is used to dignal that `.L` is a complete word and should be moved ontot he `.left` list, or `';'` to signal the same for `.R/.right`. A command can also be a `UndoCommand`, which consists of a single character; one that was previously chosen to be done. So the list of characters in a `DoCommand` constitutes a choice point; we first choose one, and continue deeper from there, but we put on the command stack an `UndoCommand` to reverse the effects of the action, and put back the remaining characters to try instead, if the first character doesn't work out. Note that we pop characters off the end of a `DoCommand`, so the last character is the first one tried.\n",
"The `search` method would be more straightforward if we could write it is a recursive function. But Python does not perform well when recursing to 100 thousand levels deep. So instead I manually manage a *stack* of *commands* that tell the search what to do and to undo. A command can be a `DoCommand`, which consists of a list of letters describing all the possible actions we could take at this point. Actions include letters that could be added (only the letters that make a legitimate prefix of a known phrase in `.L` and a legitimate suffix of a known phrase in `.R`). Actions can also include the character `','`, which is used to signal that `.L` is a complete word and should be moved onto the `.left` list, or `';'` to signal the same for `.R/.right`. A command can also be a `UndoCommand`, which consists of a single character; one that was previously chosen to be done. So the list of characters in a `DoCommand` constitutes a choice point; we first choose one, and continue deeper from there, but we put on the command stack an `UndoCommand` to reverse the effects of the action, and put back the remaining characters to try instead, if the first character doesn't work out. Note that we pop characters off the end of a `DoCommand`, so the last character is the first one tried.\n",
"\n",
"Let's see how it works:"
]
@ -452,7 +452,7 @@
"- I'm not sure when to end a word and when to try to continue to a longer word; experimentation here would be useful.\n",
"- The program is deterministic, and thus always finds the same plaindrome. that's boring; can some randomness be introduced?\n",
"- Can we make more interesting phrases? Include determiners other than \"a\" and \"an\"; include adjectives, etc.\n",
"- The counts of prefixes and suffixes include all the phrases in the dictionary. But we are not allowed to repeat a phrase, so can we modify the code to subtract one from the counts every time a phrase is used (and add onr back in when we backtrack over the phrase)?\n",
"- The counts of prefixes and suffixes include all the phrases in the dictionary. But we are not allowed to repeat a phrase, so can we modify the code to subtract one from the counts every time a phrase is used (and add one back in when we backtrack over the phrase)?\n",
"\n",
"Perhaps you can find other areas to explore.\n",
"\n",

1956
ipynb/poker.ipynb Normal file

File diff suppressed because one or more lines are too long

139
ipynb/portman.py Normal file
View File

@ -0,0 +1,139 @@
# Generate a portmantout word
# Peter Norvig
# See https://github.com/norvig/pytudes/blob/master/ipynb/Portmantout.ipynb
from collections import defaultdict, Counter
from typing import List, Tuple, Set, Dict, Any
Word = str
class Wordset(set): """A set of words."""
Step = Tuple[int, str] # An (overlap, word) pair.
OVERLAP, WORD = 0, 1 # Indexes of the two parts of a Step.
Path = List[Step] # A list of steps.
Bridge = (int, Step,...) # An excess letter count and step(s), e.g. (1, (2, 'arrow')).
EXCESS, STEPS = 0, slice(1, None) # Indexes of the two parts of a bridge.
W = Wordset(open('wordlist.asc').read().split())
def portman(P: Path) -> Word:
"""Compute the portmantout string S from the path P."""
return ''.join(word[overlap:] for (overlap, word) in P)
def natalie(W: Wordset, start=None) -> Path:
"""Return a portmantout path containing all words in W."""
precompute(W)
word = start or first(W.unused)
used(W, word)
P = [(0, word)]
while W.unused:
steps = unused_step(W, word) or bridging_steps(W, word)
for (overlap, word) in steps:
P.append((overlap, word))
used(W, word)
return P
def unused_step(W: Wordset, prev_word: Word) -> List[Step]:
"""Return [(overlap, unused_word)] or []."""
for suf in suffixes(prev_word):
for unused_word in W.startswith.get(suf, ()):
overlap = len(suf)
return [(overlap, unused_word)]
return []
def bridging_steps(W: Wordset, prev_word: Word) -> List[Step]:
"""The steps from the shortest bridge that bridges
from a suffix of prev_word to a prefix of an unused word."""
bridge = min(W.bridges[suf][pre]
for suf in suffixes(prev_word) if suf in W.bridges
for pre in W.bridges[suf] if W.startswith[pre])
return bridge[STEPS]
def precompute(W):
"""Precompute and cache data structures for W. The .subwords and .bridges
data structures are static and only need to be computed once; .unused and
.startswith are dynamic and must be recomputed on each call to `natalie`."""
if not hasattr(W, 'subwords') or not hasattr(W, 'bridges'):
W.subwords = subwords(W)
W.bridges = build_bridges(W)
W.unused = W - W.subwords
W.startswith = compute_startswith(W.unused)
def used(W, word):
"""Remove word from `W.unused` and, for each prefix, from `W.startswith[pre]`."""
assert word in W, f'used "{word}", which is not in the word set'
if word in W.unused:
W.unused.remove(word)
for pre in prefixes(word):
W.startswith[pre].remove(word)
if not W.startswith[pre]:
del W.startswith[pre]
def first(iterable, default=None): return next(iter(iterable), default)
def multimap(pairs) -> Dict[Any, set]:
"""Given (key, val) pairs, make a dict of {key: {val,...}}."""
result = defaultdict(set)
for key, val in pairs:
result[key].add(val)
return result
def compute_startswith(words) -> Dict[str, Set[Word]]:
"""A dict mapping a prefix to all the words it starts:
{'somet': {'something', 'sometimes'},...}."""
return multimap((pre, w) for w in words for pre in prefixes(w))
def subwords(W: Wordset) -> Set[str]:
"""All the words in W that are subparts of some other word."""
return {subword for w in W for subword in subparts(w) & W}
def suffixes(word) -> List[str]:
"""All non-empty proper suffixes of word, longest first."""
return [word[i:] for i in range(1, len(word))]
def prefixes(word) -> List[str]:
"""All non-empty proper prefixes of word."""
return [word[:i] for i in range(1, len(word))]
def subparts(word) -> Set[str]:
"""All non-empty proper substrings of word"""
return {word[i:j]
for i in range(len(word))
for j in range(i + 1, len(word) + (i > 0))}
def splits(word) -> List[Tuple[int, str, str]]:
"""A sequence of (excess, pre, suf) tuples."""
return [(excess, word[:i], word[i+excess:])
for excess in range(len(word) - 1)
for i in range(1, len(word) - excess)]
def try_bridge(bridges, pre, suf, excess, word, step2=None):
"""Store a new bridge if it has less excess than the previous bridges[pre][suf]."""
if suf not in bridges[pre] or excess < bridges[pre][suf][EXCESS]:
bridge = (excess, (len(pre), word))
if step2: bridge += (step2,)
bridges[pre][suf] = bridge
def build_bridges(W: Wordset, maxlen=5, end='qujvz'):
"""A table of bridges[pre][suf] == (excess, (overlap, word)), e.g.
bridges['ar']['c'] == (0, (2, 'arc'))."""
bridges = defaultdict(dict)
shortwords = [w for w in W if len(w) <= maxlen + (w[-1] in end)]
shortstartswith = compute_startswith(shortwords)
# One-word bridges
for word in shortwords:
for excess, pre, suf, in splits(word):
try_bridge(bridges, pre, suf, excess, word)
# Two-word bridges
for word1 in shortwords:
for suf in suffixes(word1):
for word2 in shortstartswith[suf]:
excess = len(word1) + len(word2) - len(suf) - 2
A, B = word1[0], word2[-1]
if A != B:
step2 = (len(suf), word2)
try_bridge(bridges, A, B, excess, word1, step2)
return bridges
if __name__ == "__main__":
W = Wordset(open('wordlist.asc').read().split())
print(portman(natalie(W)))

File diff suppressed because one or more lines are too long

147
py/pytudes.py Normal file
View File

@ -0,0 +1,147 @@
# Run "python pytudes.py" to create README.md for pytudes
def nb(title, url, comment=''):
"""Make a markdown table entry for a jupyter/ipython notebook."""
urlb = f'/blob/master/ipynb/{url}'
co = f'[co](https://colab.research.google.com/github/norvig/pytudes{urlb})'
dn = f'[dn](https://beta.deepnote.org/launch?template=python_3.6&url=https%3A%2F%2Fgithub.com%2Fnorvig%2Fpytudes%2Fblob%2Fmaster%2Fipynb%2F{url}) '
my = f'[my](https://mybinder.org/v2/gh/norvig/pytudes/master?filepath=ipynb%2F{url})'
nb = f'[nb](https://nbviewer.jupyter.org/github/norvig/pytudes{urlb})'
ti = f'<b><a href="ipynb/{url}" title="{comment}">{title}</a></b>'
return f'| {co} {dn} {my} {nb} | {ti} |'
def py(url, description, doc=''):
"""Make a markdown table entry for a .py file."""
if doc: doc = f'[documentation]({doc})'
return f'|[{url}](/blob/master/py/{url})|*{description}*|{doc}|'
body = f'''
<div align="right" style="text-align: right"><i>Peter Norvig<br><a href="https://github.com/norvig/pytudes/blob/master/LICENSE">MIT License</a><br>2015-2020</i></div>
# pytudes
"An *étude* (a French word meaning *study*) is an instrumental musical composition, usually short, of considerable difficulty, and designed to provide practice material for perfecting a particular musical skill." &mdash; [Wikipedia](https://en.wikipedia.org/wiki/%C3%89tude)
This project contains **pytudes**&mdash;Python programs, usually short, for perfecting particular programming skills.
Some programs are in Jupyter (`.ipynb`) notebooks, some in `.py` files. For each notebook you can:
- Click on [co](https://colab.research.google.com) to **run** the file on Colab
- Click on [dn](https://deepnote.com) to **run** the notebook on DeepNote
- Click on [my](https://mybinder.org) to **run** the notebook on MyBinder
- Click on [nb](https://nbviewer.jupyter.org/) to **view** the notebook on NBViewer
- Click on the title to **view** the notebook on github.
- Hover over the title to **view** a description.
# Index of Jupyter (IPython) Notebooks
|Run|Programming Examples|
|---|--|
{nb('Advent of Code 2018', 'Advent-2018.ipynb', 'Puzzle site with a coding puzzle each day for Advent 2018 ')}
{nb('Advent of Code 2017', 'Advent%202017.ipynb', 'Puzzle site with a coding puzzle each day for Advent 2017')}
{nb('Advent of Code 2016', 'Advent%20of%20Code.ipynb', 'Puzzle site with a coding puzzle each day for Advent 2016*')}
{nb("Beal's Conjecture Revisited", 'Beal.ipynb', "A search for counterexamples to Beal's Conjecture")}
{nb('Bike Speed Versus Grade', 'Bike%20Speed%20versus%20Grade.ipynb', 'How fast can I bike as the route gets steeper?')}
{nb("Can't Stop", 'Cant-Stop.ipynb', 'Optimal play in a dice board game')}
{nb('Chaos with Triangles', 'Sierpinski.ipynb', 'A surprising appearance of the Sierpinski triangle in a random walk between vertexes')}
{nb("Conway's Game of Life", 'Life.ipynb', 'The cellular automata zero-player game')}
{nb('Dice Baseball', 'Dice%20Baseball.ipynb', 'Simulating baseball games')}
{nb('Generating Mazes', 'Maze.ipynb', 'Make a maze by generating a random tree superimposed on a grid')}
{nb('Photo Focal Lengths', 'PhotoFocalLengths.ipynb', 'Generate charts of what focal lengths were used on a photo trip.')}
{nb('Pickleball Tournament', 'Pickleball.ipynb', 'Scheduling a doubles tournament fairly and efficiently')}
{nb('Project Euler Utilities', 'Project%20Euler%20Utils.ipynb', 'My utility functions for the Project Euler problems, including `Primes` and `Factors`')}
{nb('Properly Ordered Card Hands', 'Orderable%20Cards.ipynb', 'Can you get your hand of cards into a nice order with just one move?')}
{nb('Tracking Trump: Electoral Votes', 'Electoral%20Votes.ipynb', 'How many electoral votes would Trump get if he wins the state where he has positive net approval?')}
{nb('Weighing Twelve Balls', 'TwelveBalls.ipynb', 'A puzzle where you are given some billiard balls and a balance scale, and asked to find the one ball that is heavier or lighter, in a limited number of weighings')}
{nb('WWW: Who Will Win (NBA Title)?', 'WWW.ipynb', 'Computing the probability of winning the NBA title, for my home town Warriors, or any other team')}
|Run | Logic and Number Puzzles|
|--|---|
{nb('Boggle / Inverse Boggle', 'Boggle.ipynb', 'Find all the words on a Boggle board; then find a board with a lot of words')}
{nb('Chemical Element Spelling', 'ElementSpelling.ipynb', 'Spelling words using the chemical element symbols, like CoIn')}
{nb('Cryptarithmetic', 'Cryptarithmetic.ipynb', 'Substitute digits for letters and make NUM + BER = PLAY')}
{nb('Four 4s, Five 5s, and Countdown to 2016', 'Countdown.ipynb', 'Solving the equation 10 _ 9 _ 8 _ 7 _ 6 _ 5 _ 4 _ 3 _ 2 _ 1 = 2016. From an Alex Bellos puzzle')}
{nb('The Devil and the Coin Flip Game', 'Coin%20Flip.ipynb', 'How to beat the Devil at his own game')}
{nb('Flipping Cards: A Guessing Game', 'flipping.ipynb', 'Can you go through a deck of cards, guessing higher or lower correctly for each card?')}
{nb('Gesture Typing', 'Gesture%20Typing.ipynb', 'What word has the longest path on a gesture-typing smartphone keyboard?')}
{nb('Ghost', 'Ghost.ipynb', 'The word game Ghost (add letters, try to avoid making a word)')}
{nb('How Many Soldiers Do You Need to Beat the Night King?', 'NightKing.ipynb', 'Investigasting a battle between the army of the dead and the army of the living')}
{nb("Let's Code About Bike Locks", 'Fred%20Buns.ipynb', 'A tale of a bicycle combination lock that uses letters instead of digits. Inspired by Bike Snob NYC')}
{nb('Pairing Socks', 'Socks.ipynb', 'What is the probability that you will be able to pair up socks as you randomly pull them out of the dryer?')}
{nb('Portmantout Words', 'Portmantout.ipynb', 'Find a long word that squishes together a bunch of words')}
{nb('The Puzzle of the Misanthropic Neighbors', 'Mean%20Misanthrope%20Density.ipynb', 'How crowded will this neighborhood be, if nobody wants to live next door to anyone else?')}
{nb('Refactoring a Crossword Game Program', 'Scrabble.ipynb', 'Refactoring the Scrabble / Word with Friends game from Udacity 212')}
{nb('Riddler: Battle Royale', 'Riddler%20Battle%20Royale.ipynb', 'A puzzle involving allocating your troops and going up against an opponent')}
{nb('Riddler Lottery', 'RiddlerLottery.ipynb', 'Can you find what lottery number tickets these five friends picked?')}
{nb('Riddler: Tour de 538', 'TourDe538.ipynb', 'Solve a puzzle involving the best pace for a bicycle race.')}
{nb('Sicherman Dice', 'Sicherman%20Dice.ipynb', 'Find a pair of dice that is like a regular pair of dice, only different')}
{nb("Sol Golomb's Rectangle Puzzle", 'Golomb-Puzzle.ipynb', 'A Puzzle involving placing rectangles of different sizes inside a square')}
{nb('Spelling Bee', 'SpellingBee.ipynb', 'Find the highest-scoring board for the NY Times Spelling Bee puzzle')}
{nb('Translating English Sentences into Propositional Logic Statements', 'PropositionalLogic.ipynb', 'Automatically convert informal English sentences into formal Propositional Logic')}
{nb('How to Do Things with Words: NLP in Python', 'How%20to%20Do%20Things%20with%20Words.ipynb', 'Spelling Correction, Secret Codes, Word Segmentation, and more')}
{nb('When Cheryl Met Eve: A Birthday Story', 'Cheryl-and-Eve.ipynb', "Inventing new puzzles in the Style of Cheryl's Birthday")}
{nb("When is Cheryl's Birthday?", 'Cheryl.ipynb', "Solving the *Cheryl's Birthday* logic puzzle")}
{nb("World's Longest Palindrome", 'pal3.ipynb', 'Searching for a long Panama-style palindrome, this time letter-by-letter')}
{nb('xkcd 1313: Regex Golf (Part 2: Infinite Problems)', 'xkcd1313-part2.ipynb', 'Regex Golf: better, faster, funner. With Stefan Pochmann')}
{nb('xkcd 1313: Regex Golf', 'xkcd1313.ipynb', 'Find the smallest regular expression; inspired by Randall Monroe')}
{nb('xkcd 1970: Name Dominoes', 'xkcd-Name-Dominoes.ipynb', 'Lay out dominoes legally; the dominoes have people names, not numbers')}
|Run|Math Concepts|
|--|--|
{nb('A Concrete Introduction to Probability', 'Probability.ipynb', 'Code and examples of the basic principles of Probability Theory')}
{nb('Probability, Paradox, and the Reasonable Person Principle', 'ProbabilityParadox.ipynb', 'Some classic paradoxes in Probability Theory, and how to think about disagreements')}
{nb('Estimating Probabilities with Simulations', 'ProbabilitySimulation.ipynb', 'When the sample space is too complex, simulations can estimate probabilities')}
{nb('Economics Simulation', 'Economics.ipynb', 'A simulation of a simple economic game')}
{nb("Euler's Sum of Powers Conjecture", "Euler's%20Conjecture.ipynb", 'Solving a 200-year-old puzzle by finding integers that satisfy a<sup>5</sup> + b<sup>5</sup> + c<sup>5</sup> + d<sup>5</sup> = e<sup>5</sup>')}
{nb('How to Count Things', 'How%20To%20Count%20Things.ipynb', 'Combinatorial math: how to count how many things there are, when there are a lot of them')}
{nb('Symbolic Algebra, Simplification, and Differentiation', 'Differentiation.ipynb', 'A computer algebra system that manipulates expressions, including symbolic differentiation')}
|Run|Computer Science Algorithms and Concepts|
|--|--|
{nb('Bad Grade, Good Experience', 'Snobol.ipynb', 'As a student, did you ever get a bad grade on a programming assignment? (Snobol, Concordance)')}
{nb('BASIC Interpreter', 'BASIC.ipynb', 'How to write an interpreter for the BASIC programming language')}
{nb('The Convex Hull Problem', 'Convex%20Hull.ipynb', 'A classic Computer Science Algorithm')}
{nb('The Stable Matching Problem', 'StableMatching.ipynb', 'What is the best way to pair up two grpups with each other, obeying preferences?')}
{nb('The Traveling Salesperson Problem', 'TSP.ipynb', 'Another of the classics')}
# Index of Python Files
| File | Description | Documentation |
|:--------|:-------------------|----|
{py('beal.py', "Search for counterexamples to Beal's Conjecture", 'http://norvig.com/beal.html')}
{py('docex.py', 'A framework for running unit tests, similar to `doctest`')}
{py('ibol.py', 'An Exercise in Species Barcoding', 'http://norvig.com/ibol.html')}
{py('lettercount.py', 'Convert Google Ngram Counts to Letter Counts', 'http://norvig.com/mayzner.html')}
{py('lis.py', 'Lisp Interpreter written in Python', 'http://norvig.com/lispy.html')}
{py('lispy.py', 'Even Better Lisp Interpreter written in Python', 'http://norvig.com/lispy2.html')}
{py('lispytest.py', 'Tests for Lisp Interpreters')}
{py('pal.py', 'Find long palindromes', 'http://norvig.com/palindrome.html')}
{py('pal2.py', 'Find longer palindromes', 'http://norvig.com/palindrome.html')}
{py('pal3.py', 'Find even longer palindromes', 'http://norvig.com/palindrome.html')}
{py('pytudes.py', 'Pre-process text to generate this README.md file.')}
{py('py2html.py', 'Pretty-printer to format Python files as html')}
{py('SET.py', 'Analyze the card game SET', 'http://norvig.com/SET.html')}
{py('spell.py', 'Spelling corrector', 'http://norvig.com/spell-correct.html')}
{py('sudoku.py', 'Program to solve sudoku puzzles', 'http://norvig.com/sudoku.html')}
{py('testaccum.py', 'Tests for my failed Python `accumulation display` proposal', 'http://norvig.com/pyacc.html')}
{py('yaptu.py', 'Yet Another Python Templating Utility')}
# Etudes for Programmers
I got the idea for the *"etudes"* part of the name from
this [1978 book](https://books.google.com/books/about/Etudes_for_programmers.html?id=u89WAAAAMAAJ)
by [Charles Wetherell](http://demin.ws/blog/english/2012/08/25/interview-with-charles-wetherell)
that was very influential to me when I was first learning to program. I still have my copy.
![](https://images-na.ssl-images-amazon.com/images/I/51ZnZH29dvL._SX394_BO1,204,203,200_.jpg)
'''
output = 'README.md'
with open(output, 'w') as out:
print(f'Wrote {output}; {out.write(body)} characters')

View File

@ -7,7 +7,7 @@
- Deepnote
- [Epiphany](https://epiphany.pub/) "a new blogging experience"
- [Iodide](https://github.com/iodide-project/iodide) Run notebook in the browser
- {Pyodide](https://github.com/iodide-project/pyodide) Run Python in the browser
- [Pyodide](https://github.com/iodide-project/pyodide) Run Python in the browser
- [Klipse](https://github.com/viebel/klipse) Run interactive code in tech blogs
- Matlab / R
- Mathematica