Skip to content

Commit 397a2ee

Browse files
committed
Add a hand rolled priority queue
1 parent 799bb9b commit 397a2ee

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed
File renamed without changes.

17.pq.hs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import Data.Map qualified as M
2+
import Data.Set qualified as S
3+
import Control.Arrow ((&&&))
4+
5+
-- A variant of 17.hs that uses a hand-rolled priority queue instead of a heap.
6+
-- Fortunately or unfortunately, I can't decide, this isn't faster than the
7+
-- version that uses the heap.
8+
9+
main :: IO ()
10+
main = interact $ (++ "\n") . show . (p1 &&& p2) . parse
11+
12+
type Node = (Int, Int)
13+
data Grid a = Grid { items :: M.Map Node a, lastNode :: Node }
14+
15+
parse :: String -> Grid Int
16+
parse s = Grid { items = M.fromList xs, lastNode = fst (last xs) }
17+
where xs = [((x, y), read [c]) | (y, l) <- enum (lines s), (x, c) <- enum l]
18+
19+
enum :: [a] -> [(Int, a)]
20+
enum = zip [0..]
21+
22+
data Direction = L | R | U | D deriving (Eq, Ord)
23+
data Cell = Cell {
24+
node :: Node, direction :: Direction,
25+
-- The number of blocks that we have already moved in this direction.
26+
moves :: Int }
27+
deriving (Eq, Ord)
28+
29+
data Neighbour = Neighbour { cell :: Cell, distance :: Int }
30+
31+
neighbours :: Grid Int -> [Int] -> Cell -> [Neighbour]
32+
neighbours Grid { items } range = filter inRange . adjacent
33+
where
34+
adjacent Cell { node = (x, y), direction, moves } = case direction of
35+
L -> concat [cells (\m -> Cell (x + m, y) L (moves + m)),
36+
cells (\m -> Cell (x, y - m) U m),
37+
cells (\m -> Cell (x, y + m) D m)]
38+
R -> concat [cells (\m -> Cell (x - m, y) R (moves + m)),
39+
cells (\m -> Cell (x, y - m) U m),
40+
cells (\m -> Cell (x, y + m) D m)]
41+
U -> concat [cells (\m -> Cell (x, y - m) U (moves + m)),
42+
cells (\m -> Cell (x - m, y) R m),
43+
cells (\m -> Cell (x + m, y) L m)]
44+
D -> concat [cells (\m -> Cell (x, y + m) D (moves + m)),
45+
cells (\m -> Cell (x - m, y) R m),
46+
cells (\m -> Cell (x + m, y) L m)]
47+
cells c = snd (foldl (\(d, xs) m -> toNeighbour (c m) d xs) (0, []) extent)
48+
extent = [1..maximum range]
49+
toNeighbour cell d xs = case M.lookup (node cell) items of
50+
Just d2 -> (d + d2, Neighbour cell (d + d2) : xs)
51+
_ -> (d, xs)
52+
inRange Neighbour { cell } = moves cell `elem` range
53+
54+
shortestPath :: [Int] -> Grid Int -> Int
55+
shortestPath moveRange grid@Grid { items, lastNode } =
56+
go (M.singleton startCell 0) S.empty (mkPrioQ startCell)
57+
where
58+
-- By setting moves to 0, the starting cell's considers both the left and
59+
-- down neighbours as equivalent (which is what we want).
60+
startCell = Cell { node = (0, 0), direction = L, moves = 0 }
61+
isEnd Cell { node } = node == lastNode
62+
63+
go ds seen q = case extractMin q seen of
64+
Nothing -> 0
65+
Just ((du, u), q')
66+
| isEnd u -> du
67+
| S.member u seen -> go ds seen q'
68+
| otherwise -> let adj = neighbours grid moveRange u
69+
(ds', q'') = foldl (relax u du) (ds, q') adj
70+
in go ds' (S.insert u seen) q''
71+
72+
relax u du (ds, q) Neighbour { cell = v, distance = d } =
73+
let d' = du + d in case M.lookup v ds of
74+
Just dv | dv < d' -> (ds, q)
75+
_ -> (M.insert v d' ds, insert d' v q)
76+
77+
data PrioQ = PrioQ { dmap :: M.Map Int [Cell], dmin :: Int }
78+
79+
mkPrioQ :: Cell -> PrioQ
80+
mkPrioQ startCell = PrioQ { dmap = M.singleton 0 [startCell], dmin = 0 }
81+
82+
extractMin :: PrioQ -> S.Set Cell -> Maybe ((Int, Cell), PrioQ)
83+
extractMin pq@PrioQ { dmap, dmin } seen | dmin > M.size dmap = Nothing
84+
| otherwise = case M.lookup dmin dmap of
85+
Nothing -> extractMin PrioQ { dmap, dmin = dmin + 1 } seen
86+
Just cells -> case filter (`S.notMember` seen) cells of
87+
[] -> extractMin PrioQ { dmap, dmin = dmin + 1 } seen
88+
[u] -> Just ((dmin, u), PrioQ { dmap, dmin = dmin + 1 })
89+
u:us -> Just ((dmin, u), PrioQ { dmap = M.insert dmin us dmap, dmin = dmin })
90+
91+
insert :: Int -> Cell -> PrioQ -> PrioQ
92+
insert du u PrioQ { dmap, dmin } = PrioQ { dmap = M.alter af du dmap, dmin }
93+
where af Nothing = Just [u]
94+
af (Just us) = Just (u:us)
95+
96+
p1, p2 :: Grid Int -> Int
97+
p1 = shortestPath [1..3]
98+
p2 = shortestPath [4..10]
File renamed without changes.

0 commit comments

Comments
 (0)