Advent of Code 2015: Day 9
Advent of Code: Day 9
https://blog.jverkamp.com/2015/12/09/advent-of-code-day-9/
Part 1: Given a list of distances between cities of the form London to Dublin = 464
, calculate the shortest route that visits each city exactly once.
routes = collections.defaultdict(
lambda : collections.defaultdict(
lambda : float("inf")
)
)
for line in sys.stdin:
src, dst, dist = re.match(r'(\w+) to (\w+) = (\d+)', line).groups()
dist = int(dist)
routes[src][dst] = dist
routes[dst][src] = dist
best_length, best_ordering = min(
(sum(
routes[src][dst]
for src, dst in zip(ordering, ordering[1:])
), ordering)
for ordering in itertools.permutations(routes.keys())
)
print(best_length)
There are a few neat tricks here that I’ve used. First routes
is defined as nested defaultdict
, with an eventual default value of float("inf")
. This solves two problems:
- We don’t have to explicitly check if a station exists before adding a route to it:
python routes[src][dst] = dist
rather thanpython if src in routes: routes[src][dst] = dist else: routes[src] = {dst: dist}
- Any missing routes will have an infinite distance, which will work correctly with
+
andmin
.
We add each distance to both routes[src][dst]
and routes[dst][src]
so that we don’t have to worry about ordering when we calculate full routes. The other way to do this would be to sort src
and dst
so that src < dst
is always true. I think this way is a little cleaner.
Next, we use a bunch of tools to calculate the shortest route.
First, itertools.permutations
will give us every possible ordering:
>>> pprint.pprint(list(itertools.permutations(routes.keys())))
[('London', 'Belfast', 'Dublin'),
('London', 'Dublin', 'Belfast'),
('Belfast', 'London', 'Dublin'),
('Belfast', 'Dublin', 'London'),
('Dublin', 'London', 'Belfast'),
('Dublin', 'Belfast', 'London')]
Next, zip
over ordering
and ordering[1:]
will give us the pairs of stations (since zip
after exhausting its shortest argument):
>>> ordering = ('Dublin', 'Belfast', 'London')
>>> pprint.pprint(list(zip(ordering, ordering[1:])))
[('Dublin', 'Belfast'), ('Belfast', 'London')]
Next, we can get the distance for each pairing and sum
them all up. This is where float("inf")
really comes in handy (although in this smaller example, we don’t need it):
>>> orderings = (routes[src][dst] for src, dst in zip(ordering, ordering[1:]))
>>> pprint.pprint(list(orderings))
[141, 518]
>>> pprint.pprint(sum(orderings))
659
That, we wrap up in a tuple of (distance, ordering)
so that they are sortable. Then, apply min
to that to find the route with the minimum distance and unpack the tuple again.
And that’s it. Minimum distance.
It’s certainly a brute force solution in that it will try every possible route to find the shortest one. There are probably a few dynamic programming tricks that could cut that down a bit. On the other hand, the input is relatively short (28 connections between 8 stations for a total of 40320 orderings), so just do them all.
Part 2: Find the longest route.
Just change min
to max
.
This wouldn’t work if there wasn’t a connection listed between every possible station (there is in my given input, since 8 stations will have 8*7/2 = 28
connections). That’s solveable though. Just use float("-inf")
for the default distance, so that any routes with invalid stations will have an infinitely small distance.
This was a pretty cool problem again!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!