From 7445a8399c5b5ec1071a0a47b5cf7e968c6b7823 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Wed, 26 Sep 2018 18:02:52 -0700 Subject: [PATCH] Add files via upload --- ipynb/Pickleball.ipynb | 67 +++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/ipynb/Pickleball.ipynb b/ipynb/Pickleball.ipynb index d392f63..101a866 100644 --- a/ipynb/Pickleball.ipynb +++ b/ipynb/Pickleball.ipynb @@ -62,9 +62,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# `all_pairs(P)`\n", + "# All Pairs of Players\n", "\n", - "We will generate all pairs of players like this:" + "We will generate all partner pairs of players like this:" ] }, { @@ -144,7 +144,7 @@ "\n", "# `make_games(pairs)`\n", "\n", - "Now let's take those pairs and place them together into games. We'll choose one pair of players, `A`, and then another pair `B` such that between them there are 4 different players. Then we'll try to make `other_games` out of the remaining pairs. If we can't, we'll make a different choice for `B`. " + "Now let's take those pairs and place them together into games. We'll choose one pair of players, `A`, to play against another pair `B`, making sure that between the two pairs there are four different players. Then we'll try to make `other_games` out of the remaining pairs. If we can't, we'll make a different choice for `B`. " ] }, { @@ -266,9 +266,9 @@ "source": [ "That looks good. Note that `make_games` does not ensure that each player plays every other player twice—we'll worry about that later.\n", "\n", - "# `schedule(games, courts)`\n", + "# Scheduling Games to Courts\n", "\n", - "Now we need to schedule games onto courts, such that no player plays twice in any round, and we take as few rounds as possible. We'll define `schedule` to produce a `list` of rounds, where each round is a tuple of up to `courts` games. We'll use a greedy approach to assigning games to rounds; this may result in more rounds than is optimal." + "Now we need to schedule games onto courts in rounds, such that no player plays twice in any round, and we take as few rounds as possible. We'll define `schedule` to produce a `list` of rounds, where each round is a tuple of up to `courts` games. We'll use a greedy approach to assigning games to rounds; this does *not* guarantee the shortest possible schedule." ] }, { @@ -405,6 +405,7 @@ " print(fmt(row, *[c or '-' for c in counts], sum(counts) / 2))\n", " \n", "def games_str(round):\n", + " \"A string representing a round of games.\"\n", " return ' | '.join('{:X},{:X} vs {:X},{:X}'\n", " .format(a, b, c, d) for ((a, b), (c, d)) in round)\n", " \n", @@ -791,7 +792,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "That's a very good schedule. It takes the minimum 15 rounds, and while not everyone plays everyone else 2 times, most counts are in the 1 to 3 range (except for pesky player 1, who faces 0 and B four times, and F zero times, and players 8 and B, who also do not play each other)." + "That's a good schedule. It takes the minimum 15 rounds, and although not all counts are 2, most are in the 1 to 3 range." ] }, { @@ -800,19 +801,22 @@ "source": [ "# Addendum: Counting Schedules\n", "\n", - "A reader asked \"*couldn't you have tried all possible schedules?*\" That's a great question! As [Ken Thompson says](https://users.ece.utexas.edu/~adnan/pike.html), \"when in doubt, use brute force.\" How many possible schedules are there? My first inclination was \"too many,\" even for *P* = 9, but is that really true? I'll count the number of schedules, approximately (that is, my formula will work exactly only for *P* where there is an even number of pairs and every round fills all the courts).\n", + "A reader asked \"*couldn't you have tried all possible schedules?*\" That's a great question! As [Ken Thompson says](https://users.ece.utexas.edu/~adnan/pike.html), \"when in doubt, use brute force.\" How many possible schedules are there? \n", "\n", - "- For *P* players, there are *P* × (*P* - 1) / 2 pairs of players.\n", - "- We can place these pairs into the schedule in any order, so take the factorial of the number of pairs.\n", - "- But that over-counts, because order doesn't matter in the following ways:\n", - "- The order of pairs within a game doesn't matter, so divide by 2 (for each game).\n", - "- The order of games within a round doesn't matter, so divide by the factorial of the number of courts (for each round).\n", - "- The order of rounds in the schedule doesn't matter, so divide by the factorial of the number of rounds." + "- Assume a schedule with *R* rounds on *C* courts, with every court filled on every round.\n", + "- That means there are *G* = *CR* games and 2*G* slots in the schedule for pairs to fill.\n", + "- We can fill those slots with pairs in (2*G*)! ways.\n", + "- But that over-counts, because order doesn't matter in the following three ways:\n", + " - The order of pairs within a game doesn't matter, so divide by 2*G*.\n", + " - The order of games within a round doesn't matter, so divide *C*!*R*.\n", + " - The order of rounds in the schedule doesn't matter, so divide by *R*!.\n", + "\n", + "That gives us:" ] }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 65, "metadata": { "collapsed": false }, @@ -820,37 +824,40 @@ { "data": { "text/plain": [ - "{4: 15,\n", - " 5: 945,\n", - " 6: 2027025,\n", - " 7: 13749310575,\n", - " 8: 28845653137679503125,\n", - " 9: 7637693625347175036443671875}" + "{4: 15.0,\n", + " 5: 945.0,\n", + " 8: 2.8845653137679503e+19,\n", + " 9: 7.637693625347176e+27,\n", + " 12: 4.375874524269406e+66,\n", + " 13: 2.53276118507763e+83,\n", + " 16: 8.78872489906208e+147,\n", + " 17: 1.1985831550364023e+174}" ] }, - "execution_count": 50, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from math import factorial as fact\n", + "from math import factorial\n", "\n", - "def combos(P):\n", - " pairs = P * (P - 1) // 2 \n", - " games = pairs // 2\n", - " courts = P // 4\n", - " rounds = games // courts\n", - " return fact(pairs) // 2 ** games // fact(courts) ** rounds // fact(rounds)\n", + "def schedules(P):\n", + " \"Number of possible schedules for P players with all courts full.\"\n", + " G = P * (P - 1) // 4 # Number of games\n", + " C = P // 4 # Number of courts\n", + " R = G // C # Number of rounds\n", + " return factorial(2 * G) / 2 ** G / factorial(C) ** R / factorial(R)\n", "\n", - "{P: combos(P) for P in range(4, 10)}" + "{P: schedules(P) \n", + " for P in (4, 5, 8, 9, 12, 13, 16, 17)}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We see that it would have been feasible to try every schedule up to *P*=7, but not for any *P* beyond that." + "We see that it would have been infeasible to try every schedule, even for *P*=8, let alone 9 or 16." ] } ],