adventofcode2022

My solutions for Advent of Code 2022
Log | Files | Refs

commit 92b7b6e3c5be5f10679c5d519ec864ba80c50e70
parent 70786e108e9a02b10937327786dd5928bffbde47
Author: Lukas Henkel <lh@entf.net>
Date:   Mon, 19 Dec 2022 22:13:18 +0100

Day 19

Diffstat:
Madventofcode2022.asd | 8++++++--
Ainput/day19.txt | 30++++++++++++++++++++++++++++++
Asrc/day19.lisp | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
At/day19.lisp | 17+++++++++++++++++
4 files changed, 211 insertions(+), 2 deletions(-)

diff --git a/adventofcode2022.asd b/adventofcode2022.asd @@ -28,7 +28,9 @@ (:file "day14") (:file "day15") (:file "day16") - (:file "day17"))) + (:file "day17") + (:file "day18") + (:file "day19"))) (defsystem "adventofcode2022/test" :description "My solutions to the advent of code 2022" @@ -55,4 +57,6 @@ (:file "day14") (:file "day15") (:file "day16") - (:file "day17"))) + (:file "day17") + (:file "day18") + (:file "day19"))) diff --git a/input/day19.txt b/input/day19.txt @@ -0,0 +1,30 @@ +Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 4 ore and 11 obsidian. +Blueprint 2: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian. +Blueprint 3: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 10 obsidian. +Blueprint 4: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 2 ore and 15 obsidian. +Blueprint 5: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 4 ore and 20 obsidian. +Blueprint 6: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 19 clay. Each geode robot costs 2 ore and 18 obsidian. +Blueprint 7: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 20 obsidian. +Blueprint 8: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 4 ore and 17 obsidian. +Blueprint 9: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 4 ore and 7 obsidian. +Blueprint 10: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 16 obsidian. +Blueprint 11: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 4 ore and 12 obsidian. +Blueprint 12: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 2 ore and 18 obsidian. +Blueprint 13: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 13 clay. Each geode robot costs 2 ore and 20 obsidian. +Blueprint 14: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 16 clay. Each geode robot costs 3 ore and 14 obsidian. +Blueprint 15: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 17 clay. Each geode robot costs 4 ore and 16 obsidian. +Blueprint 16: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 3 ore and 19 obsidian. +Blueprint 17: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 19 obsidian. +Blueprint 18: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 8 obsidian. +Blueprint 19: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 7 clay. Each geode robot costs 3 ore and 10 obsidian. +Blueprint 20: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 4 ore and 12 obsidian. +Blueprint 21: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 17 obsidian. +Blueprint 22: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian. +Blueprint 23: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 3 ore and 9 obsidian. +Blueprint 24: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 2 ore and 10 obsidian. +Blueprint 25: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 2 ore and 8 obsidian. +Blueprint 26: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 17 obsidian. +Blueprint 27: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 8 obsidian. +Blueprint 28: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 2 ore and 12 obsidian. +Blueprint 29: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 3 ore and 13 obsidian. +Blueprint 30: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 6 clay. Each geode robot costs 4 ore and 11 obsidian. diff --git a/src/day19.lisp b/src/day19.lisp @@ -0,0 +1,158 @@ +(defpackage #:adventofcode2022/day19 + (:use #:cl #:adventofcode2022) + (:import-from #:cl-ppcre + #:register-groups-bind)) +(in-package #:adventofcode2022/day19) + +(defun get-resource-index (resource) + (cond + ((eq resource :ore) 0) + ((eq resource :clay) 1) + ((eq resource :obsidian) 2) + ((eq resource :geode) 3) + (t (error "Unknown resource type ~S" resource)))) + +(defun getres (resource &optional field) + (if field + (aref field (get-resource-index resource)) + (lambda (x) (getres resource x)))) + +(defun (setf getres) (value resource field) + (setf (elt field (get-resource-index resource)) value)) + +(defun %res-op (op resources-1 resources-2) + (let* ((length (length resources-1)) + (new (make-array length))) + (loop for i from 0 below length + do (setf (aref new i) + (funcall op + (aref resources-1 i) + (aref resources-2 i)))) + new)) + +(declaim (inline res+)) +(defun res+ (resources-1 resources-2) + (%res-op #'+ resources-1 resources-2)) + +(declaim (inline res-)) +(defun res- (resources-1 resources-2) + (%res-op #'- resources-1 resources-2)) + +(defun can-build-robot-p (blueprint resources) + (loop with depends = blueprint + for i from 0 below (length depends) + always (>= (aref resources i) (aref depends i)))) + +(defun build-robot (type blueprints robots resources) + (let ((robots-delta (vector 0 0 0 0))) + (incf (getres type robots-delta)) + (values + (res+ robots robots-delta) + (res- resources blueprints)))) + +(defparameter *max-geodes-cache* nil) +(defparameter *max-geode-by-minute-cache* nil) + +(defun compute-cache-key (remaining-minutes robots resources) + "This is a lot faster than puttin lists in 'equal tables." + (logior + (getres :ore resources) + (ash (getres :clay resources) 16) + (ash (getres :obsidian resources) 32) + (ash (getres :geode resources) 48) + (ash (getres :ore robots) 64) + (ash (getres :clay robots) 80) + (ash (getres :obsidian robots) 96) + (ash (getres :geode robots) 112) + (ash remaining-minutes 128))) + +(defun max-geodes-for-blueprint (blueprint max-bots &optional (remaining-minutes 24) (robots (vector 1 0 0 0)) (resources (vector 0 0 0 0))) + (when (<= remaining-minutes 0) + (return-from max-geodes-for-blueprint (getres :geode resources))) + (let* ((key (compute-cache-key remaining-minutes robots resources)) + (cached (gethash key *max-geodes-cache*)) + (current-geodes (getres :geode resources))) + (when cached + (return-from max-geodes-for-blueprint cached)) + (when (< current-geodes (or (gethash remaining-minutes *max-geode-by-minute-cache*) 0)) + (return-from max-geodes-for-blueprint 0)) + (setf (gethash remaining-minutes *max-geode-by-minute-cache*) current-geodes) + (let ((produced (copy-seq robots)) + (results) + (best-result)) + (when (loop for type in '(:geode :obsidian :clay :ore) + for robot-bp = (getres type blueprint) + when (and (< (getres type robots) (getres type max-bots)) + (can-build-robot-p robot-bp resources)) + do (multiple-value-bind (new-robots new-resources) + (build-robot type robot-bp robots resources) + (push (max-geodes-for-blueprint blueprint + max-bots + (1- remaining-minutes) + new-robots + (res+ new-resources produced)) + results) + (when (eq type :geode) + (return nil))) + finally (return t)) + (push (max-geodes-for-blueprint blueprint + max-bots + (1- remaining-minutes) + robots + (res+ resources produced)) + results)) + (setf best-result (apply #'max results)) + (setf (gethash key *max-geodes-cache*) best-result)))) + +(defun get-max-robots (blueprint) + (vector (apply #'max (map 'list (getres :ore) blueprint)) + (apply #'max (map 'list (getres :clay) blueprint)) + (apply #'max (map 'list (getres :obsidian) blueprint)) + 9999)) + +(defun task1 (inputs) + (loop for input in inputs + for blueprint = (cadr input) + for max-robots = (get-max-robots blueprint) + sum (let ((*max-geodes-cache* (make-hash-table)) + (*max-geode-by-minute-cache* (make-hash-table))) + (let ((max-geodes (max-geodes-for-blueprint blueprint max-robots))) + (* (car input) max-geodes))))) + +(defun task2 (inputs) + (apply #'* + (loop for input in (subseq inputs 0 (min 3 (length inputs))) + for blueprint = (cadr input) + for max-robots = (get-max-robots blueprint) + collect (let ((*max-geodes-cache* (make-hash-table)) + (*max-geode-by-minute-cache* (make-hash-table))) + (let ((max-geodes (max-geodes-for-blueprint blueprint max-robots 32))) + max-geodes))))) + +(define-day 19 + (:translate-input (lambda (input) + (let* ((label-rules (str:split ":" input :limit 2)) + (id (parse-integer (cadr (str:split " " (car label-rules))))) + (rules (str:split "." (cadr label-rules) :omit-nulls t))) + (list id + (loop with robots = (vector + (vector 0 0 0 0) + (vector 0 0 0 0) + (vector 0 0 0 0) + (vector 0 0 0 0)) + for rule in rules + do (register-groups-bind (type-str rest) + ("Each (.*?) robot costs (.*)" rule) + (let* ((type (intern (str:upcase type-str) :keyword)) + (robot (getres type robots))) + (loop for cost-str in (str:split "and" rest) + do (register-groups-bind (cost material) + ("(\\d+) (.*)" cost-str) + (let ((cost-int (parse-integer cost)) + (dependency (intern + (str:upcase (str:trim material)) + :keyword))) + (setf (getres dependency robot) cost-int)))))) + finally (return robots)))))) + #'task1 + #'task2) diff --git a/t/day19.lisp b/t/day19.lisp @@ -0,0 +1,17 @@ +(in-package #:adventofcode2022/test) + +(define-constant +testdata-day19+ "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." + :test 'equal) + +(def-test day19-task1 () + (is-true + (= 33 + (run-task 19 1 + (make-string-input-stream +testdata-day19+))))) + +(def-test day19-task2 () + (is-true + (= 2604 ;; no idea if this is correct, but my input is + (run-task 19 2 + (make-string-input-stream +testdata-day19+)))))