(ns edition2022.day11
(:require [clojure.math :as math]
[clojure.string :as str])
(:import (clojure.lang PersistentQueue)))
(def example "Monkey 0:\n Starting items: 79, 98\n Operation: new = old * 19\n Test: divisible by 23\n If true: throw to monkey 2\n If false: throw to monkey 3\n\nMonkey 1:\n Starting items: 54, 65, 75, 74\n Operation: new = old + 6\n Test: divisible by 19\n If true: throw to monkey 2\n If false: throw to monkey 0\n\nMonkey 2:\n Starting items: 79, 60, 97\n Operation: new = old * old\n Test: divisible by 13\n If true: throw to monkey 1\n If false: throw to monkey 3\n\nMonkey 3:\n Starting items: 74\n Operation: new = old + 3\n Test: divisible by 17\n If true: throw to monkey 0\n If false: throw to monkey 1\n")
(defn parse-arg [arg]
(if (= arg "old")
"%"
arg))
(defn eval-operation [operation-str]
(let [exp-str (str/replace operation-str #" Operation: new = " "")
symbols (str/split exp-str #" ")
s (str/join " " [(second symbols) (parse-arg (first symbols)) (parse-arg (nth symbols 2))])]
(eval (read-string (str "#(" s ")")))))
(defn parse-items [items-str]
(->> (str/replace items-str #" Starting items: " "")
(#(str/split % #", "))
(map read-string)
(map bigint)
(reduce conj PersistentQueue/EMPTY)
))
(defn parse-last-number [line]
(->> (str/split line #" ")
(last)
(#(str/replace % #":" ""))
(read-string)))
(defn parse-monkey [lines]
(let [idx (parse-last-number (first lines))
items (parse-items (second lines))
operation (eval-operation (nth lines 2))
div (parse-last-number (nth lines 3))
true-monkey (parse-last-number (nth lines 4))
false-monkey (parse-last-number (nth lines 5))]
{:id idx
:items items
:operation operation
:div div
:true-monkey true-monkey
:false-monkey false-monkey}))
(defn parse-monkeys [input]
(let [lines (str/split-lines input)]
(->> (partition-by #(= "" %) lines)
(remove #(= 1 (count %)))
(map parse-monkey)
(into []))))
(defn peek-item [monkey]
(peek (monkey :items)))
(defn remove-item [monkey]
(let [items (monkey :items)]
(assoc monkey :items (pop items))))
(defn add-item [monkey item]
(let [items (monkey :items)]
(assoc monkey :items (conj items item))))
(defn compute-worry-level [monkey item]
;(math/floor-div ((monkey :operation) item) 3)
((monkey :operation) item)
)
(defn choose-where-to-pass [monkey item]
(if (= 0 (mod item (monkey :div)))
(monkey :true-monkey)
(monkey :false-monkey)))
(defn register-inspection [monkey]
(assoc monkey :inspections (inc (get monkey :inspections 0))))
(defn pass-item [current-monkey monkeys]
(let [item (peek-item current-monkey)]
(if item
(let [new-iem (compute-worry-level current-monkey item)
idx-to-pass (choose-where-to-pass current-monkey new-iem)
monkey-to-pass (nth monkeys idx-to-pass)]
(recur (register-inspection (remove-item current-monkey)) (assoc monkeys idx-to-pass (add-item monkey-to-pass new-iem))))
(assoc monkeys (current-monkey :id) current-monkey))))
(defn play-round [monkeys]
(loop [m monkeys
idx 0]
(if (< idx (count m))
(recur (pass-item (nth m idx) m) (inc idx))
m)))
(defn play-rounds [monkeys rounds]
(loop [m monkeys
r 0]
(if (< r rounds)
(recur (play-round m) (inc r))
m)))
(def example-monkeys (parse-monkeys example))
(def first-pass (pass-item (first example-monkeys) example-monkeys))
(defn solution1 [input]
(->> (play-rounds (parse-monkeys input) 10000)
(map :inspections)
(sort)
(reverse)
(#(* (first %) (second %)))))
(comment
(math/floor-div 5 4)
((eval-operation "new = old * 3") 4)
(seq (parse-items " Starting items: 79, 98"))
(seq (conj (parse-items " Starting items: 79, 98") 67))
(conj (parse-items " Starting items: 79, 98") 3)
(parse-last-number " Test: divisible by 5")
(parse-last-number "Monkey 3:")
(parse-monkeys example)
(map #(seq (:items %)) first-pass)
(map #(seq (:items %)) (pass-item (second first-pass) first-pass))
(map #(seq (:items %)) (play-round (parse-monkeys example)))
(map #(seq (:items %)) (play-rounds example-monkeys 20))
(play-rounds example-monkeys 20)
(solution1 example)
(solution1 (slurp "resources/day11.in"))
)