Closed plammens closed 4 years ago
Thanks for your great contribution, Paolo @plammens.
Sometimes, when I create these exercises iteratively over a couple of days, I tend to lose the "big picture."
Your remarks about not referring to the problem as a TSP is correct. Even though I would tend to leave the term in as a comparison as most non-CS people probably can relate to it and have no idea of what a Hamiltonian path is.
Your code stub is great. One could probably adapt it to not materialize one big dictionary though. But only keep track of the best so far in a generator expression or so. What do you think?
I will merge it in in the next run of the course in two weeks.
May I ask something: You seem "overqualified" for this course already :) What made you work through the materials? If you had prior knowledge, what new ideas did you learn? Just curious to find out in case I make a version of the course for people coming from another language.
Cheers, Alex
Your remarks about not referring to the problem as a TSP is correct. Even though I would tend to leave the term in as a comparison as most non-CS people probably can relate to it and have no idea of what a Hamiltonian path is.
You're right, maybe talking about a "minimal Hamiltonian path problem" in this context does sound a bit pedantic. I see two options:
What would you prefer?
Your code stub is great. One could probably adapt it to not materialize one big dictionary though. But only keep track of the best so far in a generator expression or so. What do you think?
Totally right, hadn't thought about memory. We could get the minimal route in one call of min
on a single generator expression:
best_route, _ = min(((route, ...) for route in
((..., *perm, ...) for perm in ...)),
key=lambda x: ...)
but maybe it's a bit too messy? My eyes hurt from so many parentheses :-) And also it seems less clear as to what the student should fill in in each blank.
Since generator expressions are part of your syllabus, maybe we could split it up into several generators:
# Generator of routes from start to end
routes = ((..., *permutation, ...) for permutation in ...)
# Generator of pairs of (route, cost of route)
route_cost_pairs = ((route, ...) for route in routes)
# Select the best route/cost pair (based on cost)
best_route, _ = min(route_cost_pairs, key=lambda x: ...)
May I ask something: You seem "overqualified" for this course already :) What made you work through the materials? If you had prior knowledge, what new ideas did you learn? Just curious to find out in case I make a version of the course for people coming from another language.
I was helping someone who was working through this course. I only looked at the exercises in chapters 9 and 10, but judging only from that it looks great! And I did learn something new: I had no idea of the existence of a pretty-print function in the standard library! There's always something to learn. 🙂
You're right, maybe talking about a "minimal Hamiltonian path problem" in this context does sound a bit pedantic. I see two options:
* Keep it as it was before (but "fixing" the explanation about the number of routes in the original problem, mentioning how solutions to the TSP problem relate to solutions to the original problem) * Maintaining the "minimal Hamiltonian path" as the central "way to view" the problem (perhaps giving it a friendlier name, or omitting the phrase altogether), but mentioning its interpretation as a TSP in the "Route Optimization" section
What would you prefer?
I would suggest a mixture: reframing the problem as a "minimal Hamiltonian path" problem and mentioning the TSP as a "relative"
# Generator of routes from start to end routes = ((..., *permutation, ...) for permutation in ...) # Generator of pairs of (route, cost of route) route_cost_pairs = ((route, ...) for route in routes) # Select the best route/cost pair (based on cost) best_route, _ = min(route_cost_pairs, key=lambda x: ...)
This would be the best solution. Then, students review the idea of generator expressions.
I was helping someone who was working through this course. I only looked at the exercises in chapters 9 and 10, but judging only from that it looks great! And I did learn something new: I had no idea of the existence of a pretty-print function in the standard library! There's always something to learn. slightly_smiling_face
Ask your friend to list section of the book that are hard to understand. Maybe, we can re-write them in a clearer way.
Thanks for all your efforts.
I've made the changes.
Regarding mentioning the TSP, I only added a "generic" mention that TSP is closely related to this problem. That's because now that I thought of it, the TSP reformulation I mentioned above isn't correct either: the distances to any intermediate node are different from the start node vs from the end node, and merging the two into one "dummy" node in the TSP won't reflect this. Unless we make a TSP on a directed graph, but then I think adding an explanation for that is more detrimental than beneficial.
Hi @webartifex, any reasons for closure? I did spend a fair amount of time on this.
I believe there are a couple of problems with the explanation and proposed solution to the problem presented in the exercises for chapter 10; particularly in the "Route Optimization" section.
Quoted from the "Route Optimization" section:
You're considering only half of the Hamiltonian cycles as possible "routes" in the TSP because of symmetry (every Hamiltonian cycle and its "reverse" have the same cost). Then, according to this definition, for every possible route in that TSP, there are two possible routes in the original problem: the one that starts at
start
and traverses through the given route in normal order and ends atend
, and the one that starts atstart
, traverses the given route in reverse order and ends atend
—note that these two paths are distinct since they have, in general, different costs.Note that this is exactly what's happening in the proposed solution for
Map.brute_force
. https://github.com/webartifex/intro-to-python/blob/b363d1cd3a8bd14eac79f4c34a1a40fb07b354a9/10_classes_02_exercises.ipynb#L1411-L1412Thus, there are $2\cdot\frac{(n-1)!}{2} = (n-1)!$ possible paths in the original problem, where $n$ is the number of
sights
plus one (the "dummy" node inserted for the reduction to a TSP). Ergo, there aremath.factorial(len(sights)+ 1 - 1)
paths whose cost is to be computed. This is, precisely, the number of permutations ofsights
.So there's no need to reduce the problem to a TSP; there's a much simpler explanation for the number of Hamiltonian paths between
start
andend
. Every such path is a permutation of $n$ elements, where $n$ is the total number of nodes (includingstart
andend
), but with 2 of these elements being fixed (start
andend
); in total, there are $(n - 2)!$ such paths. (Note that there's no "redundant" paths being counted here, since for every equivalence class of paths—where a path is equivalent to another if one is the reverse of the other—only one path will be in the aforementioned set: the path wherestart
is at the beginning andend
is at the end.) Indeed, we would getmath.factorial((len(sights) + 2) - 2)
, ergomath.factorial(len(sights))
.Quoted from the same section:
What's the point of removing so-called "redundant" routes if then we end up checking every route and its reverse in
Map.brute_force
?https://github.com/webartifex/intro-to-python/blob/b363d1cd3a8bd14eac79f4c34a1a40fb07b354a9/10_classes_02_exercises.ipynb#L1411-L1412
Quick example:
This relates to point 1: the reverse of a permutation of inner nodes is not, in fact, redundant.
Proposed changes
The stub for
Map.brute_force
becomeswith the solution being