part-01.scm 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. (import
  2. (except (rnrs base)
  3. let-values
  4. map
  5. error
  6. vector-map)
  7. (only (guile)
  8. lambda* λ
  9. simple-format
  10. current-output-port)
  11. (fileio)
  12. (ice-9 pretty-print)
  13. (ice-9 peg)
  14. (ice-9 match)
  15. (prefix (peg-tree-utils) peg-tree:)
  16. ;; (ice-9 format)
  17. (srfi srfi-1)
  18. (pipeline)
  19. (debug)
  20. ;; (list-helpers)
  21. ;; (array-helpers)
  22. (segment)
  23. (parallelism)
  24. ;; (math)
  25. ;; (logic)
  26. ;; receive
  27. (srfi srfi-8)
  28. (srfi srfi-9 gnu)
  29. ;; hash tables
  30. (srfi srfi-69)
  31. ;; let-values
  32. (srfi srfi-11)
  33. ;; purely functional data structures
  34. (pfds sets)
  35. ;; (timing)
  36. (graph-algorithm))
  37. (define input-filename "example-input")
  38. ;; (define input-filename "input")
  39. (define-peg-pattern NUMBER body (and (? (or "-" "+")) (+ (range #\0 #\9))))
  40. (define-peg-pattern SPACE none " ")
  41. (define-peg-pattern SEMICOLON none ";")
  42. (define-peg-pattern VALVE-LABEL none "Valve")
  43. (define-peg-pattern TUNNELS-LABEL none (or "tunnel leads to valve" "tunnels lead to valves"))
  44. (define-peg-pattern ANYTHING-EXCEPT-NUMBER none
  45. (* (and (not-followed-by NUMBER) peg-any)))
  46. (define-peg-pattern WORD-DELIMITER none (or "," "." ";" " "))
  47. (define-peg-pattern NEXT-WORD body
  48. (* (and (not-followed-by WORD-DELIMITER) peg-any)))
  49. (define-peg-pattern FLOWRATE all NUMBER)
  50. (define-peg-pattern NAME body NEXT-WORD)
  51. (define-peg-pattern VALVE-NAME all NAME)
  52. (define-peg-pattern NEIGHBOR-NAME all NAME)
  53. (define-peg-pattern NEIGHBOR-NAMES all
  54. (+ (and NEIGHBOR-NAME (? NEIGHBOR-NAME-SEP))))
  55. (define-peg-pattern NEIGHBOR-NAME-SEP none (and "," " "))
  56. (define-peg-pattern VALVE-INFO body
  57. (and (and VALVE-LABEL SPACE VALVE-NAME)
  58. (and ANYTHING-EXCEPT-NUMBER FLOWRATE)
  59. (and SEMICOLON SPACE TUNNELS-LABEL SPACE)
  60. NEIGHBOR-NAMES))
  61. (define-immutable-record-type <valve>
  62. (make-valve name flowrate neighbors)
  63. valve?
  64. (name valve-name set-valve-name)
  65. (flowrate valve-flowrate set-valve-flowrate)
  66. (neighbors valve-neighbors set-valve-neighbors))
  67. (define parse-valves
  68. (λ (line)
  69. (-> line
  70. (match-pattern VALVE-INFO)
  71. peg:tree
  72. ((λ (valve-parsed-tree)
  73. (make-valve
  74. (car (peg-tree:tree-refs valve-parsed-tree '(VALVE-NAME)))
  75. (string->number (car (peg-tree:tree-refs valve-parsed-tree '(FLOWRATE))))
  76. (map (λ (neighbor-name)
  77. ;; cons the distance 1 - it takes 1 minute to get to a
  78. ;; neighboring valve
  79. (cons (car (peg-tree:tree-refs neighbor-name '(NEIGHBOR-NAME)))
  80. 1))
  81. (peg-tree:tree-refs valve-parsed-tree '(NEIGHBOR-NAMES)))))))))
  82. (define valves
  83. (-> (get-lines-from-file input-filename)
  84. (map parse-valves)))
  85. ;; Create lookup table to quickly get valves by name.
  86. (define valves-table
  87. (alist->hash-table
  88. (map (λ (valve)
  89. ;; (simple-format #t "adding ~a\n" (valve-name valve))
  90. (cons (valve-name valve) valve))
  91. valves)
  92. string=?))
  93. (define start-valve (hash-table-ref valves-table "AA"))
  94. ;;; OK, got it parsed. Now, lets consider a brute-force approach. At
  95. ;;; each iteration or "minute" it is clear, how much time is left and
  96. ;;; with that implicitly how much pressure a valve will release over
  97. ;;; the remaining time. We could write a recursive program, that from
  98. ;;; every visited valve moves to every yet unvisited neighbor, summing
  99. ;;; each path of valves and when returning to a recursive "split",
  100. ;;; taking the maximum.
  101. ;;; This could work, we can try it, but it might be a trap for the
  102. ;;; naive. The branching might be too much over the 30 iterations
  103. ;;; ("minutes").
  104. ;; Functional sets stuff.
  105. ;; (define make-empty-set
  106. ;; (λ ()
  107. ;; (make-set
  108. ;; (λ (valve1 valve2)
  109. ;; (let ([v1-name (valve-name valve1)]
  110. ;; [v2-name (valve-name valve2)])
  111. ;; (string< v1-name v2-name))))))
  112. ;; (define set-insert-multiple
  113. ;; (λ (myset items)
  114. ;; (cond
  115. ;; [(null? items) myset]
  116. ;; [else
  117. ;; (set-insert-multiple (set-insert myset (car items))
  118. ;; (cdr items))])))
  119. ;; (define set-empty?
  120. ;; (λ (set)
  121. ;; (= (set-size set) 0)))
  122. ;; Puzzle logic.
  123. (define valve-open-total-release
  124. (λ (valve remaining-minutes)
  125. "Returns the amount of pressure, that will be released by
  126. the valve, if opened. It takes 1 minute to open the valve,
  127. so the remaining minutes - 1 are used for calculation."
  128. (* (- remaining-minutes 1)
  129. (valve-flowrate valve))))
  130. (define cost-move-to-valve 1)
  131. (define cost-open-valve 1)
  132. (define path<
  133. (λ (p1 p2)
  134. (< (car p1) (car p2))))
  135. (define path-max
  136. (λ (. paths)
  137. (reduce (λ (p1 acc) (if (path< p1 acc) acc p1))
  138. 0
  139. paths)))
  140. ;; (define naive-find-max-pressure-released
  141. ;; (λ (valves minutes)
  142. ;; (let ([valves-count (length valves)])
  143. ;; (let iter ([minutes° minutes]
  144. ;; [opened-valves° (make-empty-set)]
  145. ;; [current-valve° (hash-table-ref valves-table start-valve-name)]
  146. ;; [pressure-release° 0]
  147. ;; [path° (list start-valve-name)])
  148. ;; ;; (simple-format #t "current path: ~a\n" path°)
  149. ;; (cond
  150. ;; ;; no more valves to open
  151. ;; [(= (set-size opened-valves°) valves-count)
  152. ;; (simple-format #t "ended: no more valves to open\n")
  153. ;; (cons pressure-release° path°)]
  154. ;; ;; no neighbors to go on from here (should not happen actually)
  155. ;; [(null? (valve-neighbors current-valve°))
  156. ;; (simple-format #t "ended: no more neighbors to go to\n")
  157. ;; (cons pressure-release° path°)]
  158. ;; ;; no time left
  159. ;; [(= minutes° 0)
  160. ;; ;; (simple-format #t "ended: time is up\n")
  161. ;; (cons pressure-release° path°)]
  162. ;; [else
  163. ;; (let ([neighbor-results
  164. ;; ;; go to neighbor valves
  165. ;; (map (λ (neighbor-name)
  166. ;; (iter (- minutes° 1)
  167. ;; opened-valves°
  168. ;; (hash-table-ref valves-table neighbor-name)
  169. ;; pressure-release°
  170. ;; (cons neighbor-name path°)))
  171. ;; (valve-neighbors current-valve°))])
  172. ;; (cond
  173. ;; ;; current valve already opened?
  174. ;; [(set-member? opened-valves° current-valve°)
  175. ;; (apply path-max neighbor-results)]
  176. ;; [else
  177. ;; (apply path-max
  178. ;; ;; open current valve instead of moving on to the next
  179. ;; (cons (iter (- minutes° 1)
  180. ;; (set-insert opened-valves° current-valve°)
  181. ;; current-valve°
  182. ;; (+ pressure-release°
  183. ;; (valve-open-total-release current-valve° minutes°))
  184. ;; (cons 'open-valve path°))
  185. ;; neighbor-results))]))])))))
  186. ;; (simple-format
  187. ;; #t "result: ~a\n"
  188. ;; (naive-find-max-pressure-released valves 30))
  189. ;;; As expected, it takes too long.
  190. ;;; Run it for a lower amount of minutes to see some result:
  191. ;; (simple-format
  192. ;; #t "result: ~a\n"
  193. ;; (naive-find-max-pressure-released valves 15))
  194. ;;; Ideas for solving the problem:
  195. ;;; For each node calculate the shortes paths to all other
  196. ;;; nodes. Moving from node to neighbor node takes 1 minute
  197. ;;; for all nodes. We can then calculate how much pressure
  198. ;;; release can be gained by moving to any other node. To
  199. ;;; calculate it, we substract the distance from the
  200. ;;; remaining minutes. Based on how much pressure can be
  201. ;;; released we decide where to move next.
  202. ;;; This could be a Dijkstra for each node. Or perhaps there
  203. ;;; is another algorithm to give all distances for all
  204. ;;; nodes?
  205. ;;; First try to implement Dijkstra and see if performance
  206. ;;; is acceptable.
  207. (let-values
  208. ([(distances routes)
  209. (dijkstra-shortest-path start-valve
  210. valves
  211. (λ (valve)
  212. (map (λ (neighbor-name-distance-pair)
  213. (hash-table-ref valves-table
  214. (car neighbor-name-distance-pair)))
  215. (valve-neighbors valve)))
  216. ;; In this case the distance is always 1.
  217. (λ (current-node neighbor) 1)
  218. (λ (valve1 valve2)
  219. (let ([v1-name (valve-name valve1)]
  220. [v2-name (valve-name valve2)])
  221. (string< v1-name v2-name)))
  222. #:distance< <)])
  223. (simple-format #t "distances:\n")
  224. (pretty-print (hash-table->alist distances))
  225. (simple-format #t "routes:\n")
  226. (pretty-print (hash-table->alist routes))
  227. (simple-format #t "shortest path from AA to HH:\n")
  228. (pretty-print (routes->path routes (hash-table-ref valves-table "HH"))))
  229. ;; But moving then doing Dijkstra again is greedy and might lead to wrong result.
  230. ;; Check all permutations? But there are 50 nodes in the actual puzzle input ...