graph-algorithm.scm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. (library (graph-algorithm)
  2. (export dijkstra-shortest-path
  3. A*
  4. routes->path)
  5. (import (except (rnrs base)
  6. let-values
  7. map)
  8. (only (guile)
  9. lambda* λ
  10. when
  11. ;; simple-format
  12. ;; with-output-to-string
  13. )
  14. (ice-9 pretty-print)
  15. (srfi srfi-1) ; lists
  16. (srfi srfi-69) ; hash tables
  17. ;; Functional Sets
  18. (pfds sets)
  19. ;; Priority Search Queues
  20. (pfds psqs)
  21. ;; Bounded Balance Trees
  22. (pfds bbtrees))
  23. (define make-empty-set
  24. (λ (less?)
  25. (make-set less?)))
  26. (define set-insert-multiple
  27. (λ (myset items)
  28. (cond
  29. [(null? items) myset]
  30. [else
  31. (set-insert-multiple (set-insert myset (car items))
  32. (cdr items))])))
  33. (define set-empty?
  34. (λ (set)
  35. (= (set-size set) 0)))
  36. (define dijkstra-shortest-path
  37. (lambda* (start-node
  38. nodes
  39. get-neighbors
  40. get-neighbor-distance
  41. node<
  42. #:key
  43. (distance< <))
  44. (define init-unvisited-nodes (set-insert-multiple (make-empty-set node<) nodes))
  45. (define init-visited-nodes (make-empty-set node<))
  46. (define init-distances (alist->hash-table (map (λ (node) (cons node +inf.0)) nodes)))
  47. (define init-routes-table (make-hash-table eq?))
  48. ;; Set distance from start node to itself to 0.
  49. (hash-table-set! init-distances start-node 0)
  50. (hash-table-set! init-routes-table start-node start-node)
  51. ;; Visit an unvisited node with shortest known distance from start
  52. ;; node. Initially the start node, since all other nodes still have infinite
  53. ;; distance.
  54. (let iter ([current-node start-node]
  55. [distances° init-distances]
  56. [unvisited° init-unvisited-nodes]
  57. [visited° init-visited-nodes]
  58. [routes° init-routes-table])
  59. (cond
  60. ;; Stop, if there are no more unvisited nodes.
  61. [(set-empty? unvisited°)
  62. (values distances° routes°)]
  63. [else
  64. ;; Calculate distance to every unvisited neighbor from the start
  65. ;; node. The distance is the distance to current node plus distance to
  66. ;; the unvisted neighbor).
  67. (let* ([neighbors (get-neighbors current-node)]
  68. ;; Only look at unvisited neighbors.
  69. [unvisited-neighbors
  70. (filter (λ (neighbor) (set-member? unvisited° neighbor))
  71. neighbors)])
  72. (cond
  73. ;; If this particular node does not have any unvisited neighbors, go
  74. ;; back to visiting the next node.
  75. [(null? unvisited-neighbors)
  76. ;; Repeat until all nodes visited.
  77. (iter (set-fold (λ (node acc)
  78. (cond [(null? acc) node]
  79. [(distance< (hash-table-ref distances° node)
  80. (hash-table-ref distances° acc))
  81. node]
  82. [else acc]))
  83. '()
  84. unvisited°)
  85. distances°
  86. ;; Mark current node as visited.
  87. (set-remove unvisited° current-node)
  88. (set-insert visited° current-node)
  89. routes°)]
  90. [else
  91. ;; Look at the distances to all neighbors and update distances and
  92. ;; routes accordingly.
  93. (for-each (λ (neighbor)
  94. (let ([start-to-neighbor-distance
  95. (+ (hash-table-ref distances° current-node)
  96. (get-neighbor-distance current-node neighbor))])
  97. ;; If distance from the start node to a neighbor node
  98. ;; is less than previously known distance for that
  99. ;; node, update that distance in the distances
  100. ;; table. If a distance is updated, also update what
  101. ;; that node's previous node on the path to the node
  102. ;; is (the current node).
  103. (when (distance< start-to-neighbor-distance
  104. (hash-table-ref distances° neighbor))
  105. (hash-table-set! distances° neighbor start-to-neighbor-distance)
  106. (hash-table-set! routes° neighbor current-node))))
  107. unvisited-neighbors)
  108. ;; Continue with unvisted nodes.
  109. (iter (set-fold (λ (node acc)
  110. (cond [(null? acc) node]
  111. [(distance< (hash-table-ref distances° node)
  112. (hash-table-ref distances° acc))
  113. node]
  114. [else acc]))
  115. '()
  116. unvisited°)
  117. distances°
  118. ;; Mark current node as visited.
  119. (set-remove unvisited° current-node)
  120. (set-insert visited° current-node)
  121. routes°)]))]))))
  122. (define A*
  123. (lambda* (start
  124. target
  125. nodes
  126. get-neighbors
  127. get-neighbor-distance
  128. cost-heuristic
  129. node<
  130. distance<)
  131. (let ([fringe (psq-set (make-psq node< <)
  132. ;; Initially put the start node in the open-set,
  133. ;; since we need at least some node to go on from.
  134. start
  135. ;; The start node has priority 0, which is the
  136. ;; highest priority. It does not really matter,
  137. ;; since there is only one element in the open-set.
  138. 0)]
  139. ;; routes stores the node preceding any target node on the cheapest
  140. ;; path to that target node. Initially only the preceding node for
  141. ;; the start node is set. The start node itself.
  142. [routes (alist->hash-table `((,start . ,start)) eq?)]
  143. ;; score stores the cost of the cheapest path from the start node to
  144. ;; other nodes as currently known. Initially it is only set for the
  145. ;; start node, as one does not know other costs yet.
  146. [cheapest-path-costs (alist->hash-table `((,start . 0)) eq?)]
  147. ;; Also keep track of a best estimate (calculated using the
  148. ;; heuristic) of the cost from the start node to the target node via
  149. ;; a node. Initially we have not explored any other nodes than the
  150. ;; start node, so the cost for any path via them to the target node
  151. ;; is pessimistically estimated to be infinite. Update formula:
  152. ;; via-node-score-estimate(node) := current-best-score(node) + heuristic(node).
  153. [via-node-score-estimate (make-hash-table eq?)])
  154. ;; Set cost estimate for start node.
  155. (for-each (λ (node) (hash-table-set! via-node-score-estimate node +inf.0)) nodes)
  156. (hash-table-set! via-node-score-estimate start (cost-heuristic start))
  157. (let iter ([fringe° fringe])
  158. ;; Initially the current-node° is the one, that is estimated to have
  159. ;; the lowest cost, when a path to the target contains it. Initially
  160. ;; this should be the start node.
  161. (let ([current-node° (psq-min fringe°)])
  162. (cond
  163. ;; If the current node is the target node, return the routes.
  164. [(eq? current-node° target) routes]
  165. [else
  166. (let ([neighbors (get-neighbors current-node°)])
  167. ;; Per neighbor node update the following: routes, cheapest path
  168. ;; cost, via node path cost estimate
  169. (for-each (λ (neighbor)
  170. (let* ([distance (get-neighbor-distance current-node° neighbor)]
  171. ;; At first the tentative score is the distance
  172. ;; from the start to the neighbor going via the
  173. ;; current node.
  174. [tentative-score
  175. (+ (hash-table-ref cheapest-path-costs current-node°)
  176. distance)])
  177. ;; If we have found a cheaper path to the neighbor
  178. ;; ...
  179. (when (< tentative-score (hash-table-ref cheapest-path-costs neighbor))
  180. ;; ... update the preceding node on the path to
  181. ;; the neighbor in the routes map ...
  182. (hash-table-set! routes neighbor current-node°)
  183. ;; ... and update the cheapest path costs for the
  184. ;; neighbor ...
  185. (hash-table-set! cheapest-path-costs neighbor tentative-score)
  186. ;; ... and update the estimates of the cost of a
  187. ;; path from the start node through the neighbor
  188. ;; to the target node.
  189. (hash-table-set! via-node-score-estimate
  190. neighbor
  191. (+ tentative-score
  192. (cost-heuristic neighbor))))))
  193. neighbors)
  194. ;; Iterate with updated fringe.
  195. (iter (fold (λ (node)
  196. (psq-set fringe°
  197. node
  198. (hash-table-ref cheapest-path-costs node)))
  199. (psq-delete fringe° current-node°)
  200. neighbors)))]))))))
  201. ;; #;(define A*-pure
  202. ;; (lambda* (start
  203. ;; target
  204. ;; nodes
  205. ;; get-neighbors
  206. ;; get-neighbor-distance
  207. ;; cost-heuristic
  208. ;; node<
  209. ;; distance<)
  210. ;; (let ([fringe (psq-set (make-psq node< <)
  211. ;; ;; Initially put the start node in the open-set,
  212. ;; ;; since we need at least some node to go on from.
  213. ;; start
  214. ;; ;; The start node has priority 0, which is the
  215. ;; ;; highest priority. It does not really matter,
  216. ;; ;; since there is only one element in the open-set.
  217. ;; 0)]
  218. ;; ;; routes stores the node preceding any target node on the cheapest
  219. ;; ;; path to that target node. Initially only the preceding node for
  220. ;; ;; the start node is set. The start node itself.
  221. ;; [routes
  222. ;; (bbtree-set (make-bbtree node<) start start)]
  223. ;; ;; score stores the cost of the cheapest path from the start node to
  224. ;; ;; other nodes as currently known. Initially it is only set for the
  225. ;; ;; start node, as one does not know other costs yet.
  226. ;; [cheapest-path-costs
  227. ;; (bbtree-set (make-bbtree node<) start 0)]
  228. ;; ;; Also keep track of a best estimate (calculated using the
  229. ;; ;; heuristic) of the cost from the start node to the target node via
  230. ;; ;; a node. Initially we have not explored any other nodes than the
  231. ;; ;; start node, so the cost for any path via them to the target node
  232. ;; ;; is pessimistically estimated to be infinite. Update formula:
  233. ;; ;; via-node-score-estimate(node) := current-best-score(node) + heuristic(node).
  234. ;; [via-node-score-estimate
  235. ;; ;; Set cost estimate for start node.
  236. ;; (psq-set
  237. ;; ;; Set initial estimates for all nodes.
  238. ;; (fold (λ (node queue) (psq-set queue node +inf.0))
  239. ;; (make-psq node< <)
  240. ;; nodes)
  241. ;; start
  242. ;; (cost-heuristic start))])
  243. ;; (let iter ([current-node°
  244. ;; ;; Initially the current-node° is the one, that is estimated
  245. ;; ;; to have the lowest cost, when a path to the target
  246. ;; ;; contains it. Initially this should be the start node.
  247. ;; #;(psq-min via-node-score-estimate)
  248. ;; ;; Perhaps one can simply put start here.
  249. ;; start]
  250. ;; [fringe° fringe]
  251. ;; [routes° routes]
  252. ;; [cheapest-path-costs° cheapest-path-costs]
  253. ;; [via-node-score-estimate° via-node-score-estimate])
  254. ;; (cond
  255. ;; ;; If the current node is the target node, return the routes.
  256. ;; [(eq? current-node° target) routes°]
  257. ;; [else
  258. ;; ;; TODO: do not forget to remove the current node from the fringe, since we visited it
  259. ;; (let ([all-neighbors (get-neighbors current-node°)])
  260. ;; ;; per neighbor node update the following:
  261. ;; ;; routes, cheapest path cost, via node path cost estimate
  262. ;; (iter (psq-min via-node-score-estimate)
  263. ;; ;; Add all neighbors to the fringe, if they are not yet in
  264. ;; ;; the fringe. Otherwise update their priority values.
  265. ;; (fold (λ (node queue) (psq-set queue node ... #|cheapest known path cost of node|#))
  266. ;; fringe°
  267. ;; all-neighbors)
  268. ;; #;(pretty-print (bbtree->alist (bbtree-set (bbtree-set
  269. ;; (bbtree-set (make-bbtree (lambda (k1 k2) (string<?
  270. ;; (symbol->string k1) (symbol->string k2)))) 'a 4) 'b 3)
  271. ;; 'c 5)))
  272. ;; ;; --> ((a . 4) (b . 3) (c . 5))
  273. ;; )
  274. ;; (let* ([distance (get-neighbor-distance current-node° neighbor)]
  275. ;; ;; At first the tentative score is the distance from the
  276. ;; ;; start to the neighbor going via the current node.
  277. ;; [tentative-score
  278. ;; (+ (hash-table-ref cheapest-path-costs° current-node°)
  279. ;; distance)])
  280. ;; (cond
  281. ;; ;; If we have found a cheaper path to the neighbor ...
  282. ;; [(< tentative-score (hash-table-ref cheapest-path-costs° neighbor))
  283. ;; ;; if tentative_gScore < gScore[neighbor]
  284. ;; ;; // This path to neighbor is better than any previous one. Record it!
  285. ;; ;; cameFrom[neighbor] := current
  286. ;; ;; gScore[neighbor] := tentative_gScore
  287. ;; ;; fScore[neighbor] := tentative_gScore + h(neighbor)
  288. ;; ;; if neighbor not in openSet
  289. ;; ;; openSet.add(neighbor)
  290. ;; ;; ... update the preceding node on the path to the neighbor
  291. ;; ;; in the routes° map ...
  292. ;; (hash-table-set! routes° neighbor current-node°)
  293. ;; ;; ... and update the cheapest path costs for the neighbor
  294. ;; ;; ... (->1)
  295. ;; (hash-table-set! cheapest-path-costs° tentative-score)
  296. ;; ;; ... and update the estimates of the cost of a path from
  297. ;; ;; the start node through the neighbor to the target node.
  298. ;; #|TODO|#]
  299. ;; [else ...])))
  300. ;; ...])))))
  301. ;; ;; // Open set is empty but goal was never reached
  302. ;; ;; return failure
  303. (define routes->path
  304. (λ (routes target-node)
  305. "Constructs the shortest path from the start node to the
  306. target node using the routes table."
  307. (let iter ([current-node° target-node] [path° (list target-node)])
  308. ;; (simple-format #t "current node:~a\n" current-node°)
  309. ;; (simple-format #t "current path:~a\n" (with-output-to-string (λ () (pretty-print path°))))
  310. (let ([prior-node (hash-table-ref routes current-node°)])
  311. (cond
  312. [(eq? current-node° prior-node) path°]
  313. [else
  314. (iter prior-node (cons prior-node path°))]))))))