-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday19.clj
144 lines (124 loc) · 5.25 KB
/
day19.clj
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
(ns clojure-solutions.day19
(:require [clojure.string :as str])
(:use [clojure-aoc-util.util] :reload))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Types!
(defrecord OreBot [^long ore])
(defrecord ClayBot [^long ore])
(defrecord ObsidianBot [^long ore, ^long clay])
(defrecord GeodeBot [^long ore, ^long obsidian])
(defrecord Blueprint [^OreBot ore-bot
^ClayBot clay-bot
^ObsidianBot obsidian-bot
^GeodeBot geode-bot])
(defrecord State [^long ore-bots ^long ore
^long clay-bots ^long clay
^long obsidian-bots ^long obsidian
^long geode-bots ^long geode])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Parsing
(defn- parse []
(->> (slurp "../inputs/day19.txt")
str/split-lines
(map (comp (partial map read-string) (partial re-seq #"\d+")))
(map (fn [[_ oro cro obro obrc gro grob]]
(->Blueprint (->OreBot oro)
(->ClayBot cro)
(->ObsidianBot obro obrc)
(->GeodeBot gro grob))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Mining step
(defn- kw->kw
"Create a new keyword from 'kw' with 'suf' appended to it."
[^clojure.lang.Keyword kw ^String suf]
(keyword (str/join [(name kw) suf])))
(defn- mine
"Update the 'state' with all the new rocks we mined."
[^State state]
(reduce (fn [s k] (update s k (partial + (get s (kw->kw k "-bots")))))
state
[:ore :clay :obsidian :geode]))
(defn- step
"A single step in the process.
In addition to the \"can we afford this?\" conditions, there is one
more set. Since we can only buy one thing per round anyways, there is
no sense having more X bots than we need to build the next highest
bot. For example, we don't need more obsidian bots than is needed to
build a single geode bot; that would just be a waste.
Further, always test ore bots, but prefer the obvious order for the
other bots. Why does this work? I don't know; found it out through
trial and error. It's probably a fault with my input, but it keeps me
from having to think of creative ways of pruning :)"
[^Blueprint {:keys [ore-bot clay-bot obsidian-bot geode-bot] :as blueprint}
^State {:keys [ore-bots ore clay-bots clay obsidian-bots obsidian] :as s}]
(letfn [(update-state [^clojure.lang.Keyword kw]
"Update the state for the given keyword."
(let [kws (get blueprint (kw->kw kw "-bot"))]
(reduce (fn [st k]
(update st k #(- % (get kws k))))
(update (mine s) (kw->kw kw "-bots") inc)
(keys kws))))]
(filter some?
[(when (and (>= ore (:ore ore-bot))
(< ore-bots
(apply max (map :ore [ore-bot clay-bot obsidian-bot geode-bot]))))
(update-state :ore))
(cond
(and (>= ore (:ore geode-bot))
(>= obsidian (:obsidian geode-bot)))
(update-state :geode)
(and (>= ore (:ore obsidian-bot))
(>= clay (:clay obsidian-bot))
(< obsidian-bots (:obsidian geode-bot)))
(update-state :obsidian)
(and (>= ore (:ore clay-bot))
(< clay-bots (:clay obsidian-bot)))
(update-state :clay))])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Pruning
(defn- better-than
"Is 's1' \"better\" than 's2'?
The implementation is *really* sad, but also by far the fastest one
I've tried."
[^State s1 ^State s2]
(and (>= (:ore s1) (:ore s2))
(>= (:ore-bots s1) (:ore-bots s2))
(>= (:clay s1) (:clay s2))
(>= (:clay-bots s1) (:clay-bots s2))
(>= (:obsidian s1) (:obsidian s2))
(>= (:obsidian-bots s1) (:obsidian-bots s2))
(>= (:geode s1) (:geode s2))
(>= (:geode-bots s1) (:geode-bots s2))))
(defn- prune-better [ss]
(reduce (fn [acc el]
(if (some #(better-than % el) acc) ; Anything better than 'el'?
acc
;; If not, only keep the items that are at least as good.
(conj (filter #(not (better-than el %)) acc)
el)))
[]
ss))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Solver
(defn- solve ^long [^long t-start ^Blueprint blueprint]
(loop [t t-start
states [(->State 1 0 0 0 0 0 0 0)]
seen #{}]
(if (= t 0)
(:geode (apply max-key :geode states))
(let [ss (prune-better
(into #{}
(comp cat (filter #(not (contains? seen %))))
(conj (map (partial step blueprint) states)
(map mine states))))]
(recur (dec t) ss (apply conj seen ss))))))
(defn day19 ^long [^clojure.lang.Keyword p]
(let [inp (parse)]
(case p
:one (->> inp
(map (partial solve 24))
(map * (drop 1 (range)))
sum)
:two (->> (take 3 inp)
(map (partial solve 32))
(reduce *)))))