;; https://adventofcode.com/2022/day/19 ;; whelp. do i do somehow DP? generic over parameters of blueprints somehow ;; we could potentially calculate optimal step in forward time, but i'm not sure how. ;; and backward time - no easy way to know which robots will be available? ;; ;; intuitive idea, have some state of (resource1, recource2, robot1, robot2) ;; and have 2 types of steps, one - production is simple ;; then we can have different types of actions - build robot1 \ build robot2 - parallel these out ;; somehow prune ideally, but then move forward in time and get max of these. i guess it's ok plan ;; so. ;; - each robot collects 1 of it's own resource per minute ;; so function to update state with new resources is common ;; - cost of one new robot, consumed immediately, robot is produced in 1 minute ;; costs are different and listed in the bluerint ;; i suppose overall format of the blueprint is the same, only numbers change. ;; so could hardcode the translation ;; ;; and i guess i'd have one class of "blueprint" and several instances, ;; each instance does the updating of the state. (in-package :day-19) (defclass state () ((geodes :initform 0 :initarg :geodes) (ore :initform 0 :initarg :ore) (clay :initform 0 :initarg :clay) (obsidian :initform 0 :initarg :obsidian) (geodes-robot :initform 0 :initarg :geodes-robot) (ore-robot :initform 1 :initarg :ore-robot) (clay-robot :initform 0 :initarg :clay-robot) (obsidian-robot :initform 1 :initarg :obsidian-robot))) (defclass blueprint () ((ore-robot-cost :initarg :ore) (clay-robot-cost :initarg :clay) (obsidian-robot-cost :initarg :obsidian) (geode-robot-cost :initarg :geode))) ;; wrote like this initially (make-instance 'state) ;; i'd like to have code across building robots, checking whether robot can be build ;; to be shared. would i want to do it with classes? ;; state could have hashmaps from symbol to the amount ;; do i want to have plists? (defparameter *my-plist* '(:ore 1 :clay 15)) *my-plist* (getf *my-plist* :ore) (getf *my-plist* :clay) (setf (getf *my-plist* :clay) 3) ;; i guess i like that ;; then i could have robot costs as plists as well? ;; i could iterate over the plist with destructuring ;; and blueprint can just be plist of plists ;; we don't really need generic method dispatch ;; but for state - i'd kind of want to have static field ;; to share found max and pruning (loop for (resource cost) on '(:ore 3 :clay 14) by #'cddr ;; for (resource cost) in (getf *test-blueprint* :obsidian) collect (list (* 2 cost) 'is 'cost resource) ) ;; yes. comprehension is possible (defparameter *test-state* (make-instance 'state)) (setf (getf (resources *test-state*) :ore 0) 5) (setf (getf (robots *test-state*) :ore 0) 2) (setf (getf (robots *test-state*) :obsidian 0) 5) ;; now let's check my function for can-create (print *test-state*) (can-create-robot *test-blueprint* :ore *test-state*) (can-create-robot *test-blueprint* :clay *test-state*) (can-create-robot *test-blueprint* :geode *test-state*) (can-create-robot *test-blueprint* :obsidian *test-state*) ;; yay, i guess ;; ;; i had the error of putting quoted lists into the quoted list ;; ;; and now function to create new state with resources for a particular robot deducted? ;; and i guess with that robot already increased. ;; that function would signify passing of one turn \ minute (setq *test-state* (make-instance 'state)) (setf (getf (resources *test-state*) :ore 0) 3) (defparameter *another-state* (create-robot *test-blueprint* :clay *test-state*)) ;; well, that seems to work. ;; now i'd need to create blueprints from the lines. ;; then for each of the blueprint, calculate maximum of geodes. ;; multiply with the :id and sum. ;; ;; ok, i guess. i'd want a function that takes in blueprint. ;; gets initial state. and recurses searching for maximum ;; maybe even saving into blueprint as well. ;; how would that recursion look? ;; ore is added at the end of the minute. ;; resources for building is taken out at the beginning of the minute ;; built bot is added at the end of the minute ;; ;; so, i could ;; - calculate resources to be added ;; - for each possible (on old resources) bot build ;; recurse with bot cost deducted and new resources added ;; so next functions would be ;; - resources-to-be-collected :: just the plist of additional resources that would be ;; generated in 1 turn ;; and ;; - add-resources :: modifying operation that would update state *test-state* (calc-resources-to-be-collected *test-state*) ;; lol. (add-resources '(:spagetty 1 :tuna 2) *test-state*) ;; and it works ;; so, now only main loop i suppose. and maybe-maybe later-later pruning (get-possible-bot-builds *test-blueprint* *test-state*) (defmethod find-max-geod (blueprints (s state) minute) ;; (format t "in step for ~a; with ~a~%" minute s) (if (= 25 minute) (getf (resources s) :geode 0) (progn (let* ((will-collect-this-minute (calc-resources-to-be-collected s)) (max-if-building (loop for bot-type in (get-possible-bot-builds blueprints s) for state-with-new-bot = (create-robot blueprints bot-type s) when state-with-new-bot maximize (progn (add-resources will-collect-this-minute state-with-new-bot) (find-max-geod blueprints state-with-new-bot (1+ minute))))) (if-not-building (let ((state-copy (copy-state s))) ;; (break) (add-resources will-collect-this-minute state-copy) (find-max-geod blueprints state-copy (1+ minute))))) (max (or max-if-building 0) if-not-building))))) ;; Blueprint 1: ;; Each ore robot costs 4 ore. ;; Each clay robot costs 2 ore. ;; Each obsidian robot costs 3 ore and 14 clay. ;; Each geode robot costs 2 ore and 7 obsidian. ;; Blueprint 2: ;; Each ore robot costs 2 ore. ;; Each clay robot costs 3 ore. ;; Each obsidian robot costs 3 ore and 8 clay. ;; Each geode robot costs 3 ore and 12 obsidian. ;; do i just test this? (setq *test-blueprint* '(:ore (:ore 4) :clay (:ore 2) :obsidian (:ore 3 :clay 14) :geode (:ore 2 :obsidian 7))) (setq *test-state* (make-instance 'state)) ;; (print (find-max-geod *test-blueprint* *test-state* 1)) ;; => 0 ;; that's because i have no ability to "wait" ;; whoops ;; so. do i want, um. add one more attempted call after the loop in the iteration? ;; now. the looping is serious. ;; would it work for me to order keys geode first ;; now we seem to get geodes first, yay ;; maybe just run without printing? ;; let's check manually that when i do state copy, the plists are independent? (setq *test-state* (make-instance 'state)) *test-state* (setq *another-state* (copy-state *test-state*)) (incf (getf (resources *another-state*) :ore 0)) *another-state* (add-resources '(:seeds 151) *another-state*) (incf (getf (robots *another-state*) :obsidian 0)) ;; oh, i didn't check that state returned from the "create bot" is independent (setq *test-state* (make-instance 'state)) (add-resources '(:ore 10) *test-state*) (setq *another-state* (create-robot *test-blueprint* :clay *test-state*)) ;; ugh. resources stays shared. ;; WTF, why ;; manually create new list, i guess then then do set to the 'copied state'? ;; this is unpleasant ;; so, i guess use (copy-list ;; ok. the numbers seem ok. but this is long. ;; how do i trim this? ;; if on step 10 there's already a state with 3 obsidian machines. ;; does this state would always be ahead of ;; state on step 10 with 1 obsidian machine? ;; ;; it seems so! ;; for which reason? because if the state got 3 geode machines, it will be able to get more? ;; i suppose only when i reach case where each new step can add one more geod machine ;; only then i can guess the state is actually domeeneering? ;; but only over states with same amount of steps? ;; ok. let's commit what i have right now and try to trim? ;; how could i compare with that "cur-max" and update that cur-max? ;; no, i don't understand (format t "some result ~a~%" (find-max-geod *test-blueprint* *test-state* 1)) ;; well, yes one optimizaiton - stop building robots, when resource is to the top of ;; max daily expense ;; that would reduce a lot ;; 1) ;; would be nice to put these into (possible-robots-to-build) ;; so that it would also filtered out unnecessary new robots ;; ;; 2) ;; "keep global current max state" how would i compare and check if it's impossible to beat? ;; with "even if i could to build geod machine every day for rest N days" *test-blueprint* *test-state* (add-resources '(:ore 10) *test-state*) (incf (getf (robots *test-state*) :ore) 2) (any-use-of-creating-robot *test-blueprint* *test-state* :ore) (any-use-of-creating-robot *test-blueprint* *test-state* :clay) (any-use-of-creating-robot *test-blueprint* *test-state* :obsidian) (any-use-of-creating-robot *test-blueprint* *test-state* :geode) (max-need *test-blueprint* *test-state* :ore) (max-need *test-blueprint* *test-state* :clay) (max-need *test-blueprint* *test-state* :obsidian) (max-need *test-blueprint* *test-state* :geode) ;; and this is not good for :geode, we want as much as possible (get-possible-bot-builds *test-blueprint* *test-state*) ;; and now let's add static "max state"? ;; i'd need comparison like "can catch up" with that found max "is dominated by" ;; and also way to update that maximal state? ;; ;; so, what will that be? ;; comparison for update, should it also be if i found a state that dominates? ;; only for satiated states? ;; ;; if day is same or more ;; but the amount of geode robots and geodes is smaller? (is-satiated-p *test-blueprint* *test-state*) (incf (getf (robots *test-state*) :obsidian 0) 20) ;; seems to work, ;; but i already want to utilize some test framework ;; whelp. i do want to test this ;; 4 14 7 to be satisfied (is-satiated-p *test-blueprint* (make-instance 'state :robots '(:ore 4 :clay 14 :obsidian 7 :geode 2))) (is-satiated-p *test-blueprint* (make-instance 'state :resources '(:ore 4 :clay 14 :obsidian 7 :geode 2))) ; not, need robots (is-satiated-p *test-blueprint* (make-instance 'state :robots '(:ore 4 :clay 14 :obsidian 7 :geode 2))) ;; now for checking is-dominated. ugh. (a-dominates-b-p *test-blueprint* (make-instance 'state :robots '(:ore 4 :clay 14 :obsidian 7 :geode 2)) (make-instance 'state :robots '(:ore 4 :clay 14 :obsidian 7 :geode 2))) ;; both satiated, but second bigger (a-dominates-b-p *test-blueprint* (make-instance 'state :robots '(:ore 5 :clay 17 :obsidian 7 :geode 2)) (make-instance 'state :robots '(:ore 6 :clay 18 :obsidian 7 :geode 2))) ;; ;; both satiated, but second not always bigger (a-dominates-b-p *test-blueprint* (make-instance 'state :robots '(:ore 5 :clay 17 :obsidian 9 :geode 2)) (make-instance 'state :robots '(:ore 6 :clay 18 :obsidian 8 :geode 2))) ;; ;; first not satiated, even though second is bigger - nil (a-dominates-b-p *test-blueprint* (make-instance 'state :robots '(:ore 2 :clay 17 :obsidian 9 :geode 2)) (make-instance 'state :robots '(:ore 6 :clay 18 :obsidian 8 :geode 2))) ;; and that's not right. if we have on same amount of steps ;; reference as satiated and checking as not - wouldn't keep up ;; so big check should be whether steps are even in imagination permit ;; ugh. that would mean putting minute\step into state. ;; um, let's not do it right now? ;; for both satiated is a very weak check, but let's try it like this? (setq *test-state* (make-instance 'state)) (setf (cur-found-max *test-state*) (make-instance 'state)) ;; so whelp ;; should have committed after doing the "build makes sence list" ;; ;; my problems are because "is dominated by" is not simmetrical to "dominates" ;; and i want both ;; ;; now in the loop set first satiated as domination ;; after that compare if our set dominates that one and swap ;; and compare if it's dominated by that one and prune ;; so, only if ref earlier than checked state. ;; and then - if checked not saitated, or by all resources less than ;; but i do want tests ;; both satiated, but second bigger (a-dominates-b-p *test-blueprint* (make-instance 'state :robots '(:ore 6 :clay 18 :obsidian 7 :geode 2) :minute 2) (make-instance 'state :robots '(:ore 6 :clay 18 :obsidian 7 :geode 2) :minute 3)) ;; should be NIL ;; now want to check for different amount of steps. ;; so if same resources but first is earlier - it dominates (minute (make-instance 'state :robots '(:ore 6 :clay 18 :obsidian 7 :geode 2) :minute 2)) ;; i was putting :minute 2 into :robots plist, cool, no thanks to you types (minute (make-instance 'state :robots '(:ore 6 :clay 18 :obsidian 7 :geode 2) :minute 2)) ;; ok. this also seems well. ;; then main loop? on fisrt satiated set as `cur-found-max` ;; and after that always check - (defmethod find-max-geod-2 (blueprints (s state)) ;; (declare (optimize (debug 3))) ;; (format t "in step for ~a; with ~a~%" (minute s) s) (cond ((= 25 (minute s)) ; exit condition fully calculated (getf (resources s) :geode 0)) ((< (estimate s) (cur-found-max s)) ;; (print "pruning") 0) ; pruning this branch (t ; default check (progn (let* ((will-collect-this-minute (calc-resources-to-be-collected s)) (possible-bot-builds (get-possible-bot-builds blueprints s)) (max-if-building (when possible-bot-builds (loop for bot-type in possible-bot-builds for state-with-new-bot = (create-robot blueprints bot-type s) when state-with-new-bot maximize (progn (add-resources will-collect-this-minute state-with-new-bot) (incf (minute state-with-new-bot)) (find-max-geod-2 blueprints state-with-new-bot ))))) (if-not-building (let ((state-copy (copy-state s))) ;; (break) (add-resources will-collect-this-minute state-copy) (incf (minute state-copy)) (find-max-geod-2 blueprints state-copy ))) (recursed-max (max (or max-if-building 0) if-not-building))) ;; (break) ;; (format t "would build ~a~%" possible-bot-builds) (when (> recursed-max (cur-found-max s)) (setf (cur-found-max s) recursed-max)) recursed-max ))))) (setq *test-state* (make-instance 'state)) (setf (cur-found-max *test-state*) 0) (timing (print (find-max-geod-2 *test-blueprint* *test-state*))) (timing (let ((a 1) (b 2)) (* a b 15))) ;; thank you CL-Cookbook: https://cl-cookbook.sourceforge.net/dates_and_times.html (defmacro timing (&body forms) (let ((real1 (gensym)) (real2 (gensym)) (run1 (gensym)) (run2 (gensym)) (result (gensym))) `(let* ((,real1 (get-internal-real-time)) (,run1 (get-internal-run-time)) (,result (progn ,@forms)) (,run2 (get-internal-run-time)) (,real2 (get-internal-real-time))) (format *debug-io* ";;; Computation took:~%") (format *debug-io* ";;; ~f seconds of real time~%" (/ (- ,real2 ,real1) internal-time-units-per-second)) (format t ";;; ~f seconds of run time~%" (/ (- ,run2 ,run1) internal-time-units-per-second)) ,result))) ;; so, why doesn't it ever builds obsidian? *test-blueprint* ;; because is "has to build something if it can" whops ;; wow 20 seconds. cool ;; last things: ;; read in the line for the blueprints into plist ;; then loop over lines in file ;; for each line compute max, multiply by index ;; and sum ;; ok, i guess (rest (remove-if-not #'identity (mapcar (lambda (str) (parse-integer str :junk-allowed t)) (ppcre:split " " "Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.")))) (blueprint-line-to-plist "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.") ;; and now loop over file. (print (with-open-file (in "day19-test.txt") (loop for line = (read-line in nil nil) for n from 1 for blueprints = (when line (progn (format t "Starting processing for ~a~%" line) (blueprint-line-to-plist line))) for max-geo = 0 ;; for max-geo = (when blueprints ;; (progn ;; (setf (cur-found-max *test-state*) 0) ;; (timing (find-max-geod-2 blueprints (make-instance 'state))))) while blueprints do (format t "processed ~a : ~a. its max is ~a~%" n blueprints max-geo) summing (* n max-geo)))) (format t "and the result is : ~a~%" (read-and-calc-part-1 "day19-test.txt")) (format t "and the result is : ~a~%" (read-and-calc-part-1 "day19-input.txt")) ;; wtf is taking so long in between the processings? (apply #'* (list 2 3 5)) ;; but before that - change exit point (format t "and the result is : ~a~%" (read-and-calc-part-2 "day19-2-input.txt")) ;; 261 is too low. so ;; but in previous my maxes were 0 1 2 ;; and current are 3 3 29 ;; but still not good ;; ugh. what about test input? (format t "and the result is : ~a~%" (read-and-calc-part-2 "day19-test.txt")) ;; my calc : 18 and 55 ;; correct : 56 and 62 ;; coooool. let's move on.