@@ -2860,9 +2860,9 @@ path. We want to know the lowest possible total risk level for a path that gets
2860
2860
from the entrance (top-left) to the exit (bottom-right), passing through any of
2861
2861
the cells in the grid.
2862
2862
2863
- We can think about the grid as a graph where nodes are the cells of the grid,
2864
- and each node is connected to its neighboring nodes ( just as each cell of the
2865
- grid is connected to its neighboring cells) .
2863
+ We can think about the grid as a directed graph with as much nodes as there are
2864
+ cells in the grid, connected between each others just like cells are connected
2865
+ to their neighboring cells.
2866
2866
2867
2867
What about the edges? Moving from a given cell A to a neighboring cell B (thus
2868
2868
entering B) costs us as much as the risk level of B: we can represent this an
@@ -2872,8 +2872,7 @@ level of B, and thus we have another different edge going from B to A with a
2872
2872
weight equal to the risk level of B. In other words, the edges entering a node
2873
2873
have the same weight as the risk level of the cell corresponding to that node.
2874
2874
2875
- It should be pretty clear how to think about the grid as a graph now, but in
2876
- case it isn't, consider the following example with a small grid and its
2875
+ Do you see consider the following example with a small grid and its
2877
2876
corresponding graph representation (` S ` = entrance, ` E ` = exit):
2878
2877
2879
2878
``` none
@@ -2918,18 +2917,23 @@ def neighbors4(r, c, h, w):
2918
2917
```
2919
2918
2920
2919
Similarly to what we did two years ago for [ 2019 day 6 part 2] [ 2019-d06-p2 ] , we
2921
- will implement Dijkstra's algorithm using a [ min-heap] [ wiki-min-heap ] as a queue
2922
- to hold the nodes to visit and always pop the one with the shortest distance
2923
- from the source. The [ ` heapq ` ] [ py-heapq ] module is exactly what we need. A
2924
- ` defaultdict ` that returns ` float('inf') ` (positive floating point infinity,
2925
- which compares greater than any integer) as the default value is also useful to
2926
- treat not-yet-seen nodes as being infinitely distant.
2920
+ will implement Dijkstra's algorithm using a [ min-heap] [ wiki-min-heap ] as a
2921
+ [ priority queue] [ wiki-priority-queue ] to hold the nodes to visit and always pop
2922
+ the one with the shortest distance from the source. The [ ` heapq ` ] [ py-heapq ]
2923
+ module is exactly what we need. A ` defaultdict ` that returns ` float('inf') `
2924
+ (also provided by ` math.inf ` ) as the default value is also useful to treat
2925
+ not-yet-seen nodes as being infinitely distant (positive floating point infinity
2926
+ compares greater than any integer).
2927
2927
2928
2928
The algorithm is pretty well-known and also well-explained in the Wikipedia page
2929
2929
I just linked above, so I'm not going into much detail about it, I'll just add
2930
2930
some comments to the code.
2931
2931
2932
2932
``` python
2933
+ import heapq
2934
+ from collections import defaultdict
2935
+ from math import inf as INFINITY
2936
+
2933
2937
def dijkstra (grid ):
2934
2938
h, w = len (grid), len (grid[0 ])
2935
2939
source = (0 , 0 )
@@ -2938,7 +2942,7 @@ def dijkstra(grid):
2938
2942
# Start with only the source in our queue of nodes to visit and in the
2939
2943
# mindist dictionary, with distance 0.
2940
2944
queue = [(0 , source)]
2941
- mindist = defaultdict(lambda : float ( ' inf ' ) , {source: 0 })
2945
+ mindist = defaultdict(lambda : INFINITY , {source: 0 })
2942
2946
visited = set ()
2943
2947
2944
2948
while queue:
@@ -2957,8 +2961,11 @@ def dijkstra(grid):
2957
2961
visited.add(node)
2958
2962
r, c = node
2959
2963
2960
- # For each neighbor of this node:
2964
+ # For each unvisited neighbor of this node...
2961
2965
for neighbor in neighbors4(r, c, h, w):
2966
+ if neighbor in visited:
2967
+ continue
2968
+
2962
2969
# Calculate the total distance from the source to this neighbor
2963
2970
# passing through this node.
2964
2971
nr, nc = neighbor
@@ -2973,7 +2980,32 @@ def dijkstra(grid):
2973
2980
2974
2981
# If we ever empty the queue without entering the node == destination check
2975
2982
# in the above loop, there is no path from source to destination!
2976
- return float (' inf' )
2983
+ return INFINITY
2984
+ ```
2985
+
2986
+ The ` for ` loop which iterates over the neighbors skipping already visited ones
2987
+ can be simplified with a [ ` filter() ` ] [ py-builtin-filter ] plus a lambda:
2988
+
2989
+ ``` python
2990
+ # ...
2991
+ for neighbor in filter (lambda n : n not in visited, neighbors4(r, c, h, w)):
2992
+ nr, nc = neighbor
2993
+ newdist = dist + grid[nr][nc]
2994
+ # ...
2995
+ ```
2996
+
2997
+ Or using [ ` itertools.filterfalse() ` ] [ py-itertools-filterfalse ] , exploiting the
2998
+ already existing ` .__contains__() ` built-in method of the ` visited ` set:
2999
+
3000
+ ``` python
3001
+ from itertools import filterfalse
3002
+ # ...
3003
+
3004
+ # ...
3005
+ for neighbor in filterfalse(visited.__contains__ , neighbors4(r, c, h, w)):
3006
+ nr, nc = neighbor
3007
+ newdist = dist + grid[nr][nc]
3008
+ # ...
2977
3009
```
2978
3010
2979
3011
All that's left to do is call the function we just wrote on our grid:
@@ -3146,6 +3178,7 @@ get on the global leaderboard, this time for both parts (79th and 62nd), yay!
3146
3178
[ py-heapq ] : https://docs.python.org/3/library/heapq.html
3147
3179
[ py-io-readline ] : https://docs.python.org/3/library/io.html#io.IOBase.readline
3148
3180
[ py-itertools-count ] : https://docs.python.org/3/library/itertools.html#itertools.count
3181
+ [ py-itertools-filterfalse ] : https://docs.python.org/3/library/itertools.html#itertools.filterfalse
3149
3182
[ py-itertools-product ] : https://docs.python.org/3/library/itertools.html#itertools.product
3150
3183
[ py-itertools-repeat ] : https://docs.python.org/3/library/itertools.html#itertools.repeat
3151
3184
[ py-itertools-starmap ] : https://docs.python.org/3/library/itertools.html#itertools.starmap
@@ -3179,6 +3212,7 @@ get on the global leaderboard, this time for both parts (79th and 62nd), yay!
3179
3212
[ wiki-linked-list ] : https://en.wikipedia.org/wiki/Linked_list
3180
3213
[ wiki-median ] : https://en.wikipedia.org/wiki/Median
3181
3214
[ wiki-min-heap ] : https://en.wikipedia.org/wiki/Binary_heap
3215
+ [ wiki-priority-queue ] : https://en.wikipedia.org/wiki/Priority_queue
3182
3216
[ wiki-pushdown-automata ] : https://en.wikipedia.org/wiki/Pushdown_automaton
3183
3217
[ wiki-queue ] : https://en.wikipedia.org/wiki/Queue_(abstract_data_type)
3184
3218
[ wiki-reflection ] : https://en.wikipedia.org/wiki/Reflection_(mathematics)
0 commit comments