diff --git a/10_classes_02_exercises.ipynb b/10_classes_02_exercises.ipynb
index ee6f7ccf0f5cd3ccf9e7d24b0143c77d67a23763..269e79a7ded9cd99b1b3234c3cb3249de089ed6c 100644
--- a/10_classes_02_exercises.ipynb
+++ b/10_classes_02_exercises.ipynb
@@ -27,14 +27,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Berlin Tourist Guide: A Traveling Salesman Problem"
+ "## Berlin Tourist Guide: A Minimal Hamiltonian Path Problem"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "This notebook is a hands-on and tutorial-like application to show how to load data from web services like [Google Maps](https://developers.google.com/maps) and use them to solve a logistics problem, namely a **[Traveling Salesman Problem ](https://en.wikipedia.org/wiki/Traveling_salesman_problem)**.\n",
+ "This notebook is a hands-on and tutorial-like application to show how to load data from web services like [Google Maps](https://developers.google.com/maps) and use them to solve a logistics problem.\n",
"\n",
"Imagine that a tourist lands at Berlin's [Tegel Airport ](https://en.wikipedia.org/wiki/Berlin_Tegel_Airport) in the morning and has his \"connecting\" flight from [Schönefeld Airport ](https://en.wikipedia.org/wiki/Berlin_Sch%C3%B6nefeld_Airport) in the evening. By the time, the flights were scheduled, the airline thought that there would be only one airport in Berlin.\n",
"\n",
@@ -1223,13 +1223,16 @@
"source": [
"Let us find the cost minimal order of traveling from the `arrival` airport to the `departure` airport while visiting all the `sights`.\n",
"\n",
- "This problem can be expressed as finding the shortest so-called [Hamiltonian path ](https://en.wikipedia.org/wiki/Hamiltonian_path) from the `start` to `end` on the `Map` (i.e., a path that visits each intermediate node exactly once). With the \"hack\" of assuming the distance of traveling from the `end` to the `start` to be `0` and thereby effectively merging the two airports into a single node, the problem can be viewed as a so-called [traveling salesman problem ](https://en.wikipedia.org/wiki/Traveling_salesman_problem) (TSP).\n",
+ "This problem can be expressed as finding the shortest so-called [Hamiltonian path ](https://en.wikipedia.org/wiki/Hamiltonian_path) from the `start` to `end` on the `Map` (i.e., a path that visits each intermediate node exactly once).\n",
+ "This type of problem is a close relative of the so-called [Traveling Salesman Problem (TSP) ](https://en.wikipedia.org/wiki/Traveling_salesman_problem), which will maybe be more familiar.\n",
"\n",
- "The TSP is a hard problem to solve but also well studied in the literature. Assuming symmetric distances, a TSP with $n$ nodes has $\\frac{(n-1)!}{2}$ possible routes. $(n-1)$ because any node can be the `start` / `end` and divided by $2$ as the problem is symmetric.\n",
+ "On an undirected (i.e. assuming symmetrical distances), complete (i.e. assuming we can go from\n",
+ "any node to another) graph with $n + 2$ nodes, there are $n!$ Hamiltonian paths between any two\n",
+ "(distinct) start and end nodes.\n",
"\n",
- "Starting with about $n = 20$, the TSP is almost impossible to solve exactly in a reasonable amount of time. Luckily, we do not have that many `sights` to visit, and so we use a [brute force ](https://en.wikipedia.org/wiki/Brute-force_search) approach and simply loop over all possible routes to find the shortest.\n",
+ "Starting with about $n = 20$, finding the minimal Hamiltonian path is almost impossible to solve exactly in a reasonable amount of time. Luckily, we do not have that many `sights` to visit, and so we use a [brute force ](https://en.wikipedia.org/wiki/Brute-force_search) approach and simply loop over all possible routes to find the shortest.\n",
"\n",
- "In the case of our tourist, we \"only\" need to try out `181_440` possible routes because the two airports are effectively one node and $n$ becomes $10$."
+ "In the case of our tourist, we \"only\" need to try out `362_880` possible routes."
]
},
{
@@ -1247,7 +1250,7 @@
"metadata": {},
"outputs": [],
"source": [
- "math.factorial(len(places) + 1 - 1) // 2"
+ "math.factorial(len(places))"
]
},
{
@@ -1275,7 +1278,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "However, if we use [permutations() ](https://docs.python.org/3/library/itertools.html#itertools.permutations) as is, we try out *redundant* routes. For example, transferred to our case, the tuples `(1, 2, 3)` and `(3, 2, 1)` represent the *same* route as the distances are symmetric and the tourist could be going in either direction. To obtain the *unique* routes, we use an `if`-clause in a \"hacky\" way by only accepting routes where the first node has a smaller value than the last. Thus, we keep, for example, `(1, 2, 3)` and discard `(3, 2, 1)`."
+ "As the code cell below shows, the number of permutations of `sights` corresponds with the correct number of routes to be looped over."
]
},
{
@@ -1284,61 +1287,7 @@
"metadata": {},
"outputs": [],
"source": [
- "for permutation in itertools.permutations(numbers):\n",
- " if permutation[0] < permutation[-1]:\n",
- " print(permutation)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In order to compare `Place`s as numbers, we would have to implement, among others, the `__eq__()` special method. Otherwise, we get a `TypeError`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "Place(arrival, client=api) < Place(departure, client=api)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As a quick and dirty solution, we use the `location` property on a `Place` to do the comparison."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "Place(arrival, client=api).location < Place(departure, client=api).location"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As the code cell below shows, combining [permutations() ](https://docs.python.org/3/library/itertools.html#itertools.permutations) with an `if`-clause results in the correct number of routes to be looped over."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "sum(\n",
- " 1\n",
- " for route in itertools.permutations(places)\n",
- " if route[0].location < route[-1].location\n",
- ")"
+ "sum(1 for route in itertools.permutations(places))"
]
},
{
@@ -1351,7 +1300,7 @@
"\n",
"**Q32**: Finish the `evaluate()` method as described!\n",
"\n",
- "Second, we create a `brute_force()` method that needs no arguments. It loops over all possible routes to find the shortest. As the `start` and `end` of a route are fixed, we only need to look at `permutation`s of inner nodes. Each `permutation` can then be traversed in a forward and a backward order. `brute_force` enables method chaining as well.\n",
+ "Second, we create a `brute_force()` method that needs no arguments. It loops over all possible routes to find the shortest. As the `start` and `end` of a route are fixed, we only need to look at `permutation`s of inner nodes. `brute_force` enables method chaining as well.\n",
"\n",
"**Q33**: Finish the `brute_force()` method as described!"
]
@@ -1360,7 +1309,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### The `Map` Class (continued): Brute Forcing the TSP"
+ "### The `Map` Class (continued): Brute Forcing the minimal Hamiltonian path problem"
]
},
{
@@ -1399,24 +1348,14 @@
"\n",
" The route is plotted on the folium.Map.\n",
" \"\"\"\n",
- " # Assume a very high cost to begin with.\n",
- " min_cost = ...\n",
- " best_route = None\n",
- "\n",
- " # Loop over all permutations of the intermediate nodes to visit.\n",
- " for permutation in ...:\n",
- " # Skip redundant permutations.\n",
- " if ...:\n",
- " ...\n",
- " # Travel through the routes in both directions.\n",
- " for route in (permutation, permutation[::-1]):\n",
- " # Add start and end to the route.\n",
- " route = (..., *route, ...)\n",
- " # Check if a route is cheaper than all routes seen before.\n",
- " cost = ...\n",
- " if ...:\n",
- " min_cost = ...\n",
- " best_route = ...\n",
+ " # Generator of routes from start to end\n",
+ " routes = ((..., *permutation, ...) for permutation in ...)\n",
+ "\n",
+ " # Generator of pairs of (route, cost of route)\n",
+ " route_cost_pairs = ((route, ...) for route in routes)\n",
+ "\n",
+ " # Select the best route/cost pair (based on cost)\n",
+ " best_route, _ = min(route_cost_pairs, key=lambda x: ...)\n",
"\n",
" # Plot the route on the map\n",
" folium.PolyLine(\n",
@@ -1499,4 +1438,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file