aboutsummaryrefslogtreecommitdiffstats
path: root/AoC2022/19/solver.lisp
blob: b939bea258f84f7a23e2880241121d6194658729 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
(ql:quickload '(fiveam uiop cl-ppcre fset lparallel arrows))
;; (setf lparallel:*kernel* (lparallel:make-kernel 8))

;; (declaim (optimize (speed 0) (space 0) (debug 3)))

(defun material (string)
  (cond
    ((string= string "ore") :ore)
    ((string= string "clay") :clay)
    ((string= string "obsidian") :obsidian)
    ((string= string "geode") :geode)))

(defun parse-robot-recipe (line)
  (cl-ppcre:register-groups-bind ((#'material robot) (#'parse-integer fq) (#'material fm) (#'parse-integer sq) (#'material sm))
      ("(\\w+) robot costs (\\d+) (\\w+)(?: and (\\d+) (\\w+))?" line)
    (let ((first-material (fset:with (fset:empty-bag) fm fq)))
      (fset:map (robot
                 (if sm (fset:with first-material sm sq)
                     first-material))))))


(defun can-build-robot-pm (requirements available)
  (fset:subbag? requirements available))

(defun consume (requirements available)
  (fset:bag-difference available requirements))

(defun material-collect (bots available-materials)
  (fset:bag-sum bots available-materials))

(defun bot-requirements (type recipes)
  (fset:lookup recipes type))

(defun next-step (build-next robots resources blueprints time-left)
  (if (zerop time-left)
      ;; (list robots resources)
      (fset:multiplicity resources :geode)
      ;; resources
      (let ((req (bot-requirements build-next blueprints)))
        (if (can-build-robot-pm req resources)
            (let ((new-robots (fset:with robots build-next))
                  (resources-left (consume req resources)))
              (probe new-robots
                     (material-collect robots resources-left)
                     blueprints (1- time-left)))
            (next-step build-next
                       robots
                       (material-collect robots resources)
                       blueprints (1- time-left))))))

(defun next-step2 (build-next robots resources blueprints)
  (let ((req (bot-requirements build-next blueprints)))
    (when (can-build-robot-pm req resources)
      (let ((new-robots (fset:with robots build-next))
            (resources-left (consume req resources)))
        (values new-robots (material-collect robots resources-left))))))

(defun probe2 (current-state blueprints)
  (destructuring-bind (robots resources) current-state
    (reduce
     (lambda (acc bot-type)
       (multiple-value-bind (new-robots new-states)
           (next-step2 bot-type
                       robots
                       resources
                       blueprints)
         (if new-robots (cons (list new-robots new-states) acc) acc)))
     (propose-builds robots (max-cost blueprints))
     :initial-value (list (list robots (material-collect robots resources))))))

(defun advance (robot-resource-list blueprints)
  (reduce
   (lambda (new-states current-state)
     (union new-states
            (probe2 current-state blueprints)
            :test #'equal))
   robot-resource-list
   :initial-value nil))

(resource-increase
 (list (fset:bag :obsidian :ore) (fset:bag :clay)) 2)

(defun materials< (m1 m2)
  (eq :less
      (fset:reduce (lambda (result material)
                     (if (eq result :equal)
                         (let ((c1 (fset:multiplicity m1 material))
                               (c2 (fset:multiplicity m2 material)))
                           (cond
                             ((< c1 c2) :less)
                             ((= c1 c2) :equal)
                             ((> c1 c2) :greater)))
                         result))
                   '(:geode :obsidian :clay :ore)
                   :initial-value :equal)))

(materials< (fset:bag :clay) (fset:bag :obsidian :ore))

(defun resource-increase (current-state time-left)
  (destructuring-bind (robots resources) current-state
    (loop for s = resources then (fset:bag-sum s robots)
          repeat time-left
          finally (return s))))

(defun find-best (robot-resource-list blueprints time-left)
  (if (zerop time-left)
      robot-resource-list
      (find-best (advance robot-resource-list blueprints) blueprints (1- time-left))))

(defun max-cost (blueprint)
  (fset:reduce
   (lambda (acc key value)
     (declare (ignore key))
     (fset:union value acc))
   blueprint
   :initial-value (fset:empty-bag)))

(defun can-build (bots)
  (append
   (list :clay :ore)
   (when (plusp (fset:multiplicity bots :clay)) (list :obsidian))
   (when (plusp (fset:multiplicity bots :obsidian)) (list :geode))))

(defun propose-builds (bots costs)
  (loop for try in (can-build bots)
        when (or (eq :geode try)
                 (< (fset:multiplicity bots try)
                    (fset:multiplicity costs try)))
          collect try))

(defun probe (robots resources blueprints time-left)
  (reduce #'max
          (mapcar
           (lambda (next)
             (next-step next
                        robots
                        resources
                        blueprints time-left))
           (propose-builds robots (max-cost blueprints)))))

(defun get-blueprints (filename)
  (mapcar
   (lambda (l)
     (let ((blueprint (uiop:split-string l :separator ":.")))
       (cons (car blueprint)
             (reduce #'fset:map-union
                     (mapcar #'parse-robot-recipe (butlast (cdr blueprint)))))))
   (uiop:read-file-lines filename)))

(defun solver (blueprints time-left)
  (apply #'+
         (lparallel:pmap 'list
                         (lambda (bp)
                           (destructuring-bind (name . recipes) bp
                             (* (parse-integer name :start 10)
                                (probe (fset:bag :ore) (fset:empty-bag) recipes time-left))))
                         blueprints)))

(defun solver2 (blueprints time-left)
  (lparallel:pmap 'list
                  (lambda (bp)
                    (destructuring-bind (name . recipes) bp
                      (declare (ignore name))
                      (probe (fset:bag :ore) (fset:empty-bag) recipes time-left)))
                  blueprints))

(let ((factory (cdar (get-blueprints "/home/titan/dev/scratch/AoC2022/19/eg-in"))))
  ;; (next-step2 :clay (fset:bag :ore) (fset:bag :ore :ore) factory)
  ;; (probe2 (list (fset:bag :ore :clay) (fset:bag :ore :ore)) factory)
  ;; (arrows:->
  ;;  (list (list (fset:bag :ore :clay) (fset:bag :ore :ore)))
  ;;  (advance factory)
  ;;  (advance factory)
  ;;  (advance factory)
  ;;  (advance factory)
  ;;  (advance factory)
  ;;  (advance factory)
  ;;  (advance factory)
  ;;  )
  (sort
   ;; (mapcar (lambda (state)
   ;;           (fset:multiplicity (cadr state) :ore)))
   (find-best (list (list (fset:bag :ore) (fset:bag))) factory 5)
   #'materials< :key (lambda (state) (resource-increase state 5))))


;; (require :sb-sprof)
;; (progn
;;   (sb-sprof:start-profiling)
;;   (time
;;    (probe (fset:bag :ore) (fset:empty-bag) (cdar (get-blueprints "eg-in")) 25))
;;   (sb-sprof:stop-profiling)
;;   (sb-sprof:report))

;; (step
;;  (probe (fset:bag :ore) (fset:empty-bag) (cdar (get-blueprints "/home/titan/dev/scratch/AoC2022/19/eg-in")) 22))



;; (fiveam:test solution
;;   ;; part1
;;   ;; (fiveam:is (= 33 (solver (get-blueprints "eg-in") 24)))
;;   (fiveam:is (= 1266 (solver (get-blueprints "/home/titan/dev/scratch/AoC2022/19/input") 24)))
;;   )

;; (let ((blueprints
;;         ;; (cdadr)
;;         (get-blueprints "input")
;;         )
;;       ;; (materials '(:ore 8 :clay 20))
;;       (materials (start-resources))
;;       (bots (start-bots))
;;       ;; (bots '(:geode 1 :ore 8 :clay 20))
;;       (time-left 24))
;;   (time
;;    (apply #'+
;;           (lparallel:pmap 'list
;;                           (lambda (bp)
;;                             (destructuring-bind (name . recipes) bp
;;                               (* (parse-integer name :start 10)
;;                                  (probe bots materials recipes time-left))))
;;                           blueprints)))

;;   ;; (time
;;   ;;  (print (probe bots materials blueprints 24)))
;;   )