diff --git a/day11-input.txt b/day11-input.txt new file mode 100644 index 0000000..0dd3bf4 --- /dev/null +++ b/day11-input.txt @@ -0,0 +1,55 @@ +Monkey 0: + Starting items: 80 + Operation: new = old * 5 + Test: divisible by 2 + If true: throw to monkey 4 + If false: throw to monkey 3 + +Monkey 1: + Starting items: 75, 83, 74 + Operation: new = old + 7 + Test: divisible by 7 + If true: throw to monkey 5 + If false: throw to monkey 6 + +Monkey 2: + Starting items: 86, 67, 61, 96, 52, 63, 73 + Operation: new = old + 5 + Test: divisible by 3 + If true: throw to monkey 7 + If false: throw to monkey 0 + +Monkey 3: + Starting items: 85, 83, 55, 85, 57, 70, 85, 52 + Operation: new = old + 8 + Test: divisible by 17 + If true: throw to monkey 1 + If false: throw to monkey 5 + +Monkey 4: + Starting items: 67, 75, 91, 72, 89 + Operation: new = old + 4 + Test: divisible by 11 + If true: throw to monkey 3 + If false: throw to monkey 1 + +Monkey 5: + Starting items: 66, 64, 68, 92, 68, 77 + Operation: new = old * 2 + Test: divisible by 19 + If true: throw to monkey 6 + If false: throw to monkey 2 + +Monkey 6: + Starting items: 97, 94, 79, 88 + Operation: new = old * old + Test: divisible by 5 + If true: throw to monkey 2 + If false: throw to monkey 7 + +Monkey 7: + Starting items: 77, 85 + Operation: new = old + 6 + Test: divisible by 13 + If true: throw to monkey 4 + If false: throw to monkey 0 diff --git a/day11-test.txt b/day11-test.txt new file mode 100644 index 0000000..30e09e5 --- /dev/null +++ b/day11-test.txt @@ -0,0 +1,27 @@ +Monkey 0: + Starting items: 79, 98 + Operation: new = old * 19 + Test: divisible by 23 + If true: throw to monkey 2 + If false: throw to monkey 3 + +Monkey 1: + Starting items: 54, 65, 75, 74 + Operation: new = old + 6 + Test: divisible by 19 + If true: throw to monkey 2 + If false: throw to monkey 0 + +Monkey 2: + Starting items: 79, 60, 97 + Operation: new = old * old + Test: divisible by 13 + If true: throw to monkey 1 + If false: throw to monkey 3 + +Monkey 3: + Starting items: 74 + Operation: new = old + 3 + Test: divisible by 17 + If true: throw to monkey 0 + If false: throw to monkey 1 diff --git a/day11.lisp b/day11.lisp new file mode 100644 index 0000000..86e75db --- /dev/null +++ b/day11.lisp @@ -0,0 +1,302 @@ +;; monkeys https://adventofcode.com/2022/day/11 + +(defparameter *test-monkey-line* "Monkey 0: + Starting items: 79, 98, 101, 66 + Operation: new = old * 19 + Test: divisible by 23 + If true: throw to monkey 2 + If false: throw to monkey 3") +;; +;; so. i'd want to parse this string into list and it be a valid macro to return a monkey class +;; and have "turn" and "round" methods over the array of monkeys + +(require 'cl-ppcre) +(cl-ppcre:split " " (string-upcase *test-monkey-line*)) +(cl-ppcre:split "\\n" (string-upcase *test-monkey-line*)) + + (defparameter num-plus-newline "9 +") +(format t num-plus-newline) +(parse-integer num-plus-newline) ; junk in string "9\\n" + +(mapcar (lambda (str) (cl-ppcre:split " " (string-trim str) )) (cl-ppcre:split "\\n" (cl-ppcre:regex-replace-all ":" *test-monkey-line* " "))) +;; and now to have all numbers be translated into numbers, and works into symbols +;; to get single list? +;; or list per line i suppose + +;; from "macro and compilation.lisp" +;; but, we coult treat arguments as lists where we define marco +(defmacro mix-and-match-2 ((x1 y1) (x2 y2)) + `(list '(,x1 ,y1) + '(,x1 ,y2) + '(,x2 ,y1) + '(,x2 ,y2))) + +(mix-and-match-2 (fred wilma) (tony bony)) + +;; so could have defmacro that takes in 4 lists, each is a line parsed +(string-trim " :" " :hello: worls: ") + +(mapcar (lambda (str) + (mapcar #'parse-integer-or-symbol (cl-ppcre:split " " (string-trim " " str)))) (cl-ppcre:split "\\n" *test-monkey-line*)) +;; i could first for each line to (string-trim ":," lala) if convertable to number convert to number +;; and then all stringp upcase and intern +(parse-integer "7:hello" :junk-allowed t) ; cool + +(defun parse-integer-or-symbol (str) + (let ((maybe-int (parse-integer str :junk-allowed t))) + (if maybe-int + maybe-int + (intern (string-upcase str))))) + +(parse-integer-or-symbol "7:") +(parse-integer-or-symbol "qer") + +(defun line-to-sexp (line) + (mapcar (lambda (str) + (mapcar #'parse-integer-or-symbol (cl-ppcre:split " " (string-trim " " str)))) + (cl-ppcre:split "\\n" line))) + +(defparameter *test-monkey-sexp* (line-to-sexp *test-monkey-line*)) + +((MONKEY 0) + (STARTING |ITEMS:| 79 98 101 66) + (|OPERATION:| NEW = OLD * 19) + (|TEST:| DIVISIBLE BY 23) + (IF |TRUE:| THROW TO MONKEY 2) + (IF |FALSE:| THROW TO MONKEY 3)) + +(defmacro monkey-struct-to-instance (((monkey ordering-number) + (starting items &rest inventory-list) + (operation new eq old operation operation-number) + (test divisible by divisible-number) + (test tru th to monk true-monkey-number) + (test fals th to monk false-monkey-number))) + `(list 'monkey ,ordering-number 'inventory ',inventory-list 'operation '(lambda (item) (,operation item ,operation-number)) 'test ,divisible-number)) + +(monkey-struct-to-instance *test-monkey-sexp*) +(monkey-struct-to-instance ((MONKEY 0) + (STARTING |ITEMS:| 79 98 101 66) + (|OPERATION:| NEW = OLD * 19) + (|TEST:| DIVISIBLE BY 23) + (IF |TRUE:| THROW TO MONKEY 2) + (IF |FALSE:| THROW TO MONKEY 3))) + +(defmacro list-in-list (((first second) (third fourth))) + `(list ,second ,fourth)) +(list-in-list ((1 2) ("hello" "world"))) + +(nsubst *test-monkey-sexp*) + +;; maybe do another macro on top of macro then? +(defmacro put-struct-into-macro (my-macro var) + `(,my-macro ,(eval var))) + +(put-struct-into-macro monkey-struct-to-instance *test-monkey-sexp*) +;; LOL, this is certainly not the way to go + +;; after getting help from CL Matrix: +;; i need to use DESTRUCTURING-BIND + +(defun monkey-struct-to-instance (monkey-struct) + (destructuring-bind + ((monkey ordering-number) + (starting items &rest inventory-list) + (operation new eq old operation operation-number) + (test divisible by divisible-number) + (test tru th to monk true-monkey-number) + (test fals th to monk false-monkey-number)) monkey-struct + `(list 'monkey ,ordering-number 'inventory ',inventory-list 'operation '(lambda (item) (,operation item ,operation-number)) 'test ,divisible-number))) + +(monkey-struct-to-instance *test-monkey-sexp*) +;; and there's METABANG-BIND facility + +;; now - have monkey class. what would connect the different data about monkey +;; and after that - initialize the array. and write functions like "step" on the monkey and "round" on the array? + +;; first - let's read in a paragraph at a time? + +(defparameter *11-test-input* (cl-ppcre:split "\\n\\n" (uiop:read-file-string "day11-test.txt"))) +;; yay. thank you Cookbook : https://lispcookbook.github.io/cl-cookbook/files.html Reading Files +(mapcar #'monkey-struct-to-instance (mapcar #'line-to-sexp *11-test-input*)) + +(defclass monkey () + ((order-number :reader order-number :initarg :number) + (inventory :accessor inventory :initarg :inventory) + (operation :reader operation :initarg :operation) + (test :reader test :initarg :test) + (inspection-counter :reader inspection-counter :initform 0))) + +(defmethod print-object ((obj monkey) stream) + (print-unreadable-object (obj stream :type t) + (with-accessors ((order-number order-number ) + (inventory inventory) + (inspection-counter inspection-counter)) + obj + (format stream "~a, with objects: ~a; count: ~a" order-number inventory inspection-counter)))) + +(defun new-from-old-function (operation operand1 operand2) + (eval `(lambda (old) + (,operation ,operand1 ,operand2)))) ; unhyginic macro +(funcall (new-from-old-function '+ 2 'old) 7) + +;; here doing eval and defun, because macro just been inserting operand1 instead of symbod to which it references? +;; this is not good, but ok =C + + +(defun monkey-struct-to-instance (monkey-struct) + (destructuring-bind + ((monkey ordering-number) + (starting items &rest inventory-list) + (operation new eq operand1 operation-sign operand2) + (test divisible by divisible-number) + (test tru th to monk true-monkey-number) + (test fals th to monk false-monkey-number)) monkey-struct + (let ((operation-fun (new-from-old-function operation-sign operand1 operand2)) + (test-fun (lambda (item-worry) + (if (= 0 (mod item-worry divisible-number)) + true-monkey-number + false-monkey-number)))) + (make-instance 'monkey + :test test-fun :operation operation-fun + :inventory inventory-list :number ordering-number)))) + +(rem 7 3) +(mod 7 3) + +(monkey-struct-to-instance *test-monkey-sexp*) + +(defparameter *test-monkey-instance* + (make-instance 'monkey + :test (lambda (num) (if (= num 4) 1 2)) :operation (lambda (num) (+ num 1)) + :inventory '(1 2 3) :number 4)) + +(funcall (test *test-monkey-instance*) 6) +;; and i don't really need the ordering-number, but ok + +(defparameter *11-test-structs* + (coerce (mapcar #'monkey-struct-to-instance (mapcar #'line-to-sexp *11-test-input*)) 'vector)) + +;; now i want Turn and Round +;; turn - monkey iterates over all inventory. +;; inspects - +;; - apply Operation +;; - the worry / 3 ; default by player +;; - test -> return to which monkey to send +;; +;; i probably want to not tie up "turn" with array of monkeys +;; and only reference the array when codiyng up +;; round - each monkey gets a turn. +;; monkeys receive thrown items immediately when they are thrown +(lambda (worry) (* worry 13)) + +(funcall (slot-value (aref *11-test-structs* 2) 'operation) 5) +(let ((old 2)) (eval (funcall '* old old))) +(funcall (test (aref *11-test-structs* 0)) 23) + +;; wow. some operations are new = old * old. wow. now that's not cool +;; but can be done i think +;; yup. only with macros i guess? and unhyginic? +;; and it works, but again with eval. to create lambda from the quoted description of lambda + +;; and we're back to doing the turn? +;; or do i implement methods for monkey inspect and test? ugh +(defun monkey-one-item-action (monkey) + (let ((item-worry (first (inventory monkey) ))) + (when item-worry + (setf (inventory monkey) (cdr (inventory monkey))) + (setq item-worry (funcall (operation monkey) item-worry)) + (setq item-worry (floor (/ item-worry 3))) + (incf (slot-value monkey 'inspection-counter)) + ;; returning (target-monkey-num thrown-item) + (list (funcall (test monkey) item-worry) item-worry)))) + +(defparameter *test-monkey-instance* + (make-instance 'monkey + :test (lambda (num) (if (= num 4) 9 8)) :operation (lambda (num) (+ num 1)) + :inventory '(100 60 30) :number 4)) +(monkey-one-item-action *test-monkey-instance*) +(inspection-counter *test-monkey-instance*) + +;; ok, one step seems to work. +;; now the full Turn will return hashmap from monkey-num to list of values which to be appended to the end, so i too should append to end \ prepend + reverse +(ql:quickload 'alexandria) + +(defun monkey-turn (monkey) + (let ((thrown-hash (make-hash-table :test 'equal))) + (loop + for i from 1 to (length (inventory monkey)) + do (let ((throw-result (monkey-one-item-action monkey))) + ;; (format t "~s ~%" throw-result) + (setf (gethash (first throw-result) thrown-hash) (push (second throw-result) (gethash (first throw-result) thrown-hash))))) + (maphash (lambda (key value-list) (setf (gethash key thrown-hash) (reverse (gethash key thrown-hash)))) thrown-hash) + thrown-hash)) +;; returns hashmap of which values go where + +(defparameter *test-monkey-turn* (monkey-turn *test-monkey-instance*)) +(alexandria:hash-table-alist *test-monkey-turn*) ; looks ok, but it still should be reversed? + +(alexandria:maphash-values #'reverse *test-monkey-turn*) +(reverse (gethash 8 *test-monkey-turn*)) +(maphash (lambda (key value-list) (setf (gethash key *test-monkey-turn*) (reverse (gethash key *test-monkey-turn*)))) *test-monkey-turn*) +;; so MAPHASH and MAPHASH-VALUES do not change hashmap, ugh + +(defparameter *test-hash* (make-hash-table :test 'equal)) +(defparameter *test-hash* '(1)) +(push 11 (gethash "hello" *test-hash*)) +(push 11 *test-hash*) +(setf (gethash "hello" *test-hash*) (push 11 (gethash "hello" *test-hash*))) +(setf (gethash "why" *test-hash*) 11) +;; wtf. why setf doesn't change content of hash key? +;; well, that's because I use `eq for comparison. hello + +;; now have function for MONKEY-TURN, returning hashmap of transferred items +(append '(1 2 3) '(5 6)) + +(defun monkeys-round (monkey-array) + ;; for each monkey from array: + ;; do it's turn, then for each record in resulting hashmap append list of transferred items to correct monkey + (loop + for monkey across monkey-array + do (let ((turn-result (monkey-turn monkey))) + (maphash + (lambda (target-monkey-num items-list) + (setf (inventory (aref monkey-array target-monkey-num)) (append (inventory (aref monkey-array target-monkey-num)) items-list))) + turn-result)))) + +;; and now yolo try to run 20 rounds on test monkey array? + +;; wait. I also need to cound "how many items did each monkey inspected" +;; let's keep it for tomorrow, ugh + +;; on the next task, i'll start separating my daily scratch and final code +;; it's too much noise between the code +;; ok, added that + +(defparameter *11-test-structs* + (coerce (mapcar #'monkey-struct-to-instance (mapcar #'line-to-sexp *11-test-input*)) 'vector)) +(monkeys-round *11-test-structs*) + +(loop + for i from 1 to 20 + do (monkeys-round *11-test-structs*)) + +(sort (mapcar #'inspection-counter (coerce *11-test-structs* 'list)) #'>) + +;; PART 1 on input data + +(defparameter *11-input-paragraphs* (cl-ppcre:split "\\n\\n" (uiop:read-file-string "day11-input.txt"))) + +(defparameter *11-input-structs* + (coerce (mapcar #'monkey-struct-to-instance (mapcar #'line-to-sexp *11-input-paragraphs*)) 'vector)) + +(loop + for i from 1 to 20 + do (monkeys-round *11-input-structs*)) + +(sort (mapcar #'inspection-counter (coerce *11-input-structs* 'list)) #'>) +;; 329 305 +(* 329 305) +(apply #'* (subseq (sort (mapcar #'inspection-counter (coerce *11-input-structs* 'list)) #'>) 0 2)) + +;;; PART 2.