|
| 1 | +(require '[clojure.string :as str] |
| 2 | + '[clojure.set :as set]) |
| 3 | + |
| 4 | +(def the-map (str/split-lines (slurp "2024/12.in"))) |
| 5 | +(def unvisited |
| 6 | + (set |
| 7 | + (for [i (range (count the-map)), j (range (count the-map))] |
| 8 | + [i j]))) |
| 9 | + |
| 10 | +(def deltas [[0 1] [0 -1] [1 0] [-1 0]]) |
| 11 | + |
| 12 | +(defn flood [unvisited position] |
| 13 | + (loop [to-visit [position], unvisited unvisited, visited #{}] |
| 14 | + (if (empty? to-visit) |
| 15 | + visited |
| 16 | + (let [visiting (peek to-visit) |
| 17 | + to-visit (pop to-visit)] |
| 18 | + (recur (into to-visit |
| 19 | + (keep (fn [delta] |
| 20 | + (let [neighbor (mapv + visiting delta)] |
| 21 | + (when (and (contains? unvisited neighbor) |
| 22 | + (= (get-in the-map visiting) |
| 23 | + (get-in the-map neighbor))) |
| 24 | + neighbor))) |
| 25 | + deltas)) |
| 26 | + (disj unvisited visiting) |
| 27 | + (conj visited visiting)))))) |
| 28 | + |
| 29 | +(def regions |
| 30 | + (loop [unvisited unvisited, regions []] |
| 31 | + (if (empty? unvisited) |
| 32 | + regions |
| 33 | + (let [plant (first unvisited), plant-region (flood unvisited plant)] |
| 34 | + (recur (set/difference unvisited plant-region) |
| 35 | + (conj regions plant-region)))))) |
| 36 | + |
| 37 | +(defn perimeter [region] |
| 38 | + (reduce (fn [s position] |
| 39 | + (into s (keep (fn [delta] |
| 40 | + (let [neighbor (mapv + position delta)] |
| 41 | + (when-not (contains? region neighbor) |
| 42 | + (mapv + position (mapv (partial * 1/4) delta))))) |
| 43 | + deltas))) |
| 44 | + #{} |
| 45 | + region)) |
| 46 | + |
| 47 | +;; part 1 |
| 48 | +(time |
| 49 | + (reduce + (map (fn [region] (* (count region) (count (perimeter region)))) regions))) |
| 50 | + |
| 51 | +(defn side->orientation [[i _j]] (if (= (type i) clojure.lang.Ratio) :v :h)) |
| 52 | + |
| 53 | +(defn side-from-pos [start perimeter] |
| 54 | + (if (= (side->orientation start) :h) |
| 55 | + (into #{} cat [(take-while #(contains? perimeter %) (iterate #(update % 0 inc) start)) |
| 56 | + (take-while #(contains? perimeter %) (iterate #(update % 0 dec) start))]) |
| 57 | + (into #{} cat [(take-while #(contains? perimeter %) (iterate #(update % 1 inc) start)) |
| 58 | + (take-while #(contains? perimeter %) (iterate #(update % 1 dec) start))]))) |
| 59 | + |
| 60 | +(defn sides [perimeter] |
| 61 | + (loop [unvisited perimeter, sides #{}] |
| 62 | + (if (empty? unvisited) |
| 63 | + sides |
| 64 | + (let [start (first unvisited), side (side-from-pos start perimeter)] |
| 65 | + (recur (set/difference unvisited side) |
| 66 | + (conj sides side)))))) |
| 67 | + |
| 68 | +;; part 2 |
| 69 | +(time |
| 70 | + (reduce + (map (fn [region] (* (count region) |
| 71 | + (count (sides (perimeter region))))) regions))) |
0 commit comments