diff --git a/ipynb/Pickleball.ipynb b/ipynb/Pickleball.ipynb
index 101a866..bf9d444 100644
--- a/ipynb/Pickleball.ipynb
+++ b/ipynb/Pickleball.ipynb
@@ -27,12 +27,12 @@
" Round 6: | 0,6 vs 2,5 | 1,7 vs 3,4 |\n",
" Round 7: | 3,5 vs 1,6 | 2,4 vs 0,7 |\n",
" \n",
- "This is a pretty good schedule—it is optimal according to criteria 1, 3, and 4, but it is not optimal in terms of who plays against who; for example players 1 and 5 play 3 times, not 2. We will see if we can do better. Our overall strategy is as follows:\n",
+ "This is a pretty good schedule—it is optimal according to criteria 1, 3, and 4, but it is not optimal in terms of number of times playing each opponent; for example players 1 and 5 play 3 times, not 2. We will see if we can do better. Our overall strategy is as follows:\n",
"\n",
- "- To satisfy criterion 1, we will start with a list of all pairs of players. The function `all_pairs` does this.\n",
+ "- To satisfy criterion 1, we will start with a list of all pairs of players. The function `all_pairs` creates this.\n",
"- We will then call `make_games` to take these pairs and put them together into a list of games, strictly enforcing criterion 4 that a player can't be scheduled into both sides of the net in any one game.\n",
"- Next we call `schedule` to take the list of games and put them into a schedule with up to *C* games played at the same time. We will again strictly enforce criterion 4, not allowing a player to appear on two courts at the same time.\n",
- "- If this approach does not result in everybody playing everyone else twice we will randomly pick two games, and swap one sode of the net in one game with one side of the net in the other game, and check if that is an improvement.\n",
+ "- If this approach does not result in everybody playing everyone else twice we will randomly swap a pair in one game with a pair in another game, and see if this improves things. Keep swapping *N* times.\n",
"\n",
"# Implementation\n",
"\n",
@@ -47,9 +47,10 @@
},
"outputs": [],
"source": [
- "import random; random.seed('reproducible')\n",
"from itertools import combinations\n",
"from collections import Counter\n",
+ "import random\n",
+ "random.seed('reproducible')\n",
"\n",
"Player = int # A player is an int: `1`\n",
"Pair = tuple # A pair is a tuple of two players who are partners: `(1, 2)`\n",
@@ -64,7 +65,7 @@
"source": [
"# All Pairs of Players\n",
"\n",
- "We will generate all partner pairs of players like this:"
+ "We will generate all pairs of players (partners) like this:"
]
},
{
@@ -142,7 +143,7 @@
"source": [
"This looks good!\n",
"\n",
- "# `make_games(pairs)`\n",
+ "# Placing Pairs into Games\n",
"\n",
"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`. "
]
@@ -222,7 +223,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The astute reader may have noticed that `all_pairs(6)` has 15 pairs, and from that we can only make 7 games, not 7.5. We must drop one of the pairs, meaning that two players will never partner with each other, and will end up playing one less game than everyone else. Since there are *P* × (*P*-1) / 2 pairs of *P* players, that means there are *P* × (*P*-1) / 4 games, and that is a whole number when either *P* or *P*-1 is divisble by 4."
+ "The astute reader may have noticed that `all_pairs(6)` has 15 pairs, and from that we can only make 7 games, not 7.5. We must drop one of the pairs, meaning that two players will never partner with each other, and will end up playing one less game than everyone else. Since there are *P* × (*P*-1) / 2 pairs of *P* players, that means there are *P* × (*P*-1) / 4 games, which is a whole number when either *P* or *P*-1 is divisble by 4."
]
},
{
@@ -264,11 +265,11 @@
"cell_type": "markdown",
"metadata": {},
"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",
+ "That looks good. \n",
"\n",
- "# Scheduling Games to Courts\n",
+ "# Scheduling Games onto Courts\n",
"\n",
- "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."
+ "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(games, courts)` 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."
]
},
{
@@ -280,8 +281,9 @@
"outputs": [],
"source": [
"def schedule(games, courts=2):\n",
+ " \"Schedule games onto courts; return a list of rounds.\"\n",
" games = list(games) # Don't modify the input\n",
- " sched = Schedule()\n",
+ " sched = []\n",
" while games:\n",
" round = []\n",
" # A round gets up to `courts` games, all with disjoint players.\n",
@@ -295,6 +297,7 @@
"def disjoint(A, B): return not (A & B)\n",
"\n",
"def players(x):\n",
+ " \"The set of players in a pair, game, round, or schedule.\"\n",
" return ({x} if isinstance(x, Player) else set().union(*map(players, x)))"
]
},
@@ -332,7 +335,7 @@
"source": [
"That looks pretty good—we fit all the games into the minimum number of rounds. But the opponents are not evenly distributed. For example, player 0 and player 1 play against each other in every round except the first (where they are partners).\n",
"\n",
- "How can we improve that? We can try *shuffling* the pairs before we make games. "
+ "How can we improve that? We could try *shuffling* the pairs before we make games. "
]
},
{
@@ -515,7 +518,7 @@
"\n",
"My strategy now is to start with a non-optimal schedule, and repeatedly try to improve it by randomly altering the games and seeing if this results in a better schedule. This is called a **hillclimbing** approach; the analogy is that we start out in a valley, take a step in a random direction, and if that is upward, keep going, otherwise step back and try again. Eventually you reach a peak. \n",
"\n",
- "In this case I will be picking two games at random, and swapping one pair of partners in one game with one pair of partners in the other. If the swap makes things worse, discard it; if it makes things better, keep it. Either way, try `N` swaps. I measure \"better\" both in terms of the variation from the optimal distribution of opponents (as measured by `opp_difference(games, pairs)`) and in terms of the number of rounds (as measured by `len(sched)`). "
+ "In this case I will be picking two games at random, and swapping one pair of partners in one game with one pair of partners in the other. If the swap makes things worse, discard it; if it makes things better, keep it. Either way, try `N` swaps. I measure \"better\" both in terms of minimal variation from the optimal distribution of opponents (as measured by `opp_difference(games, pairs)`) and in terms of the number of rounds (as measured by `len(sched)`). I keep track of both a list of `games` and a complete schedule, `sched`. I make random changes to the `games`, and then re-schedule the games after each change."
]
},
{
@@ -729,7 +732,7 @@
"source": [
"# 16 Player Tournament\n",
"\n",
- "Let's jump to 16 players on 4 courts (this will take a while):"
+ "Let's jump to 16 players on 4 courts:"
]
},
{
@@ -792,7 +795,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "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."
+ "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."
]
},
{
@@ -808,15 +811,15 @@
"- 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",
+ " - The order of games within a round doesn't matter, so divide by *C*!*R*.\n",
+ " - The order of rounds within the schedule doesn't matter, so divide by *R*!.\n",
"\n",
"That gives us:"
]
},
{
"cell_type": "code",
- "execution_count": 65,
+ "execution_count": 67,
"metadata": {
"collapsed": false
},
@@ -828,13 +831,11 @@
" 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": 65,
+ "execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
@@ -850,7 +851,7 @@
" return factorial(2 * G) / 2 ** G / factorial(C) ** R / factorial(R)\n",
"\n",
"{P: schedules(P) \n",
- " for P in (4, 5, 8, 9, 12, 13, 16, 17)}"
+ " for P in (4, 5, 8, 9, 16, 17)}"
]
},
{