Skip to content

Instantly share code, notes, and snippets.

@JpOnline
Last active November 4, 2020 13:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JpOnline/ff968f4232cf9a54de0a98193a62af4c to your computer and use it in GitHub Desktop.
Save JpOnline/ff968f4232cf9a54de0a98193a62af4c to your computer and use it in GitHub Desktop.
Clojure solutions for https://adventofcode.com/2018
(ns laboratory.advent-code-2018
(:require [cljs.test :refer-macros [deftest is testing run-tests]]
[clojure.set]))
(declare reach-twice)
(deftest advent-code-day1-part2
(do
(is (= 0 (reach-twice [+1 -1])))
(is (= 10 (reach-twice [+3 +3 +4 -2 -4])))
(is (= 5 (reach-twice [-6 +3 +8 +5 -6])))
(is (= 14 (reach-twice [+7 +7 -2 -7 -4])))))
(defn reach-twice [original-coll]
(loop [last-frequency 0
frequencies #{0}
coll original-coll]
(let [frequency (+ (first coll) last-frequency)]
(cond
(contains? frequencies frequency) frequency
(empty? (rest coll)) (recur frequency (conj frequencies frequency) original-coll)
:else (recur frequency (conj frequencies frequency) (rest coll))))))
(declare overlapping-inch2)
(declare not-overlapping)
(declare process-claim)
(deftest advent-code-day2
(do
(is (= 4 (overlapping-inch2 "
#1 @ 1,3: 4x4
#2 @ 3,1: 4x4
#3 @ 5,5: 2x2"))) ;; Part 1
(is (= #{3} (not-overlapping "
#1 @ 1,3: 4x4
#2 @ 3,1: 4x4
#3 @ 5,5: 2x2"
3))) ;; Part 2
(is (= {"5x5" [2 3] "6x5" [3] "5x6" [3] "6x6" [3]}
(process-claim {"5x5" [2]} [3 5 5 2 2])))))
(declare process-claim)
(defn overlapping-inch2 [raw-data]
(->> raw-data
(re-seq #"\d")
(map int)
(partition 5)
(reduce process-claim {})
(vals)
(map count)
(filter #(>= % 2))
(count)))
(defn not-overlapping [raw-data last-id]
(->> raw-data
(re-seq #"\d")
(map int)
(partition 5)
(reduce process-claim {})
(vals)
(filter #(>= (count %) 2))
(reduce clojure.set/difference (set (range 1 (inc last-id))))))
(defn process-claim [fabric [id col lin width height]]
(merge-with concat fabric (apply merge
(for [c (range col (+ col width))
l (range lin (+ lin height))]
{(str c "x" l) [id]}))))
(declare guard-times-minute)
(declare minute-times-guard)
(deftest advent-code-day4
(let [day4-input-example "
[1518-11-01 00:55] wakes up
[1518-11-02 00:50] wakes up
[1518-11-03 00:29] wakes up
[1518-11-01 00:25] wakes up
[1518-11-04 00:46] wakes up
[1518-11-05 00:55] wakes up
[1518-11-02 00:40] falls asleep
[1518-11-03 00:24] falls asleep
[1518-11-01 00:05] falls asleep
[1518-11-04 00:36] falls asleep
[1518-11-05 00:45] falls asleep
[1518-11-01 00:30] falls asleep
[1518-11-03 00:05] Guard #10 begins shift
[1518-11-04 00:02] Guard #99 begins shift
[1518-11-01 23:58] Guard #99 begins shift
[1518-11-01 00:00] Guard #10 begins shift
[1518-11-05 00:03] Guard #99 begins shift"]
(is (= 240 (guard-times-minute day4-input-example))) ; Part 1
(is (= 4455 (minute-times-guard day4-input-example))))) ; Part 2
(declare process-timelogs)
(defn guard-times-minute [raw-data]
(let [minutes-map (process-timelogs raw-data)
guard (->> minutes-map
(map #(vector (first %) (reduce + (map count (vals (second %))))))
(sort-by second)
(last)
(first))
minute-more-often (->> (get minutes-map guard)
(map #(vector (first %) (count (second %))))
(sort-by second)
(last)
(first))]
(* (int guard) minute-more-often)))
(defn minute-times-guard [raw-data]
(let [minutes-map (process-timelogs raw-data)
guard (->> minutes-map
(map #(vector (first %) (apply max (map (fn [e] (first e) (count (second e))) (second %)))))
(sort-by second)
(last)
(first))
minute (->> (get minutes-map guard)
(map #(vector (first %) (count (second %))))
(sort-by second)
(last)
(first))]
(* (int guard) minute)))
(defn get-minutes-asleep [minutes-map guard start end day]
(merge-with
#(merge-with concat %1 %2)
minutes-map
{guard (zipmap (range (int start) (int end)) (repeat [day]))}))
(defn create-minutes-map [ordered-timelogs]
(loop [remain-logs ordered-timelogs
curr-guard nil
minutes-map {}
previous-minute nil]
(if (empty? remain-logs)
minutes-map
(let [curr-guard (or
(second (re-find #"Guard #(\d+) begins shift" (first remain-logs)))
curr-guard)
wake-up? (re-find #"wakes up" (first remain-logs))
minute (second (re-find #"\d+:(\d+)" (first remain-logs)))
day (re-find #"\d+-\d+-\d+" (first remain-logs))
minutes-map (if wake-up?
(get-minutes-asleep
minutes-map
curr-guard
previous-minute
minute
day)
minutes-map)]
(recur (rest remain-logs) curr-guard minutes-map minute)))))
(defn process-timelogs [timelogs]
(as-> timelogs l
(clojure.string/split l #"\n")
(filter (complement empty?) l)
(sort l)
(create-minutes-map l)))
(declare next-round)
(declare get-solution)
(declare raw-field->field-map)
(declare print-field-map)
(deftest advent-code-day15-solution
(let [raw-field
"#######
#.G...#
#...EG#
#.#.#G#
#..G#E#
#.....#
#######"]
(is (= [47 590] (get-solution raw-field)))))
(declare battle-finished?)
(declare sum-hp)
(defn get-solution [raw-field]
(loop [round 0
field-map (raw-field->field-map raw-field)
]
(do (println (print-field-map field-map :symbol))
(if (battle-finished? field-map)
[round (sum-hp field-map)]
(recur (inc round) (next-round field-map))))))
(declare get-units-positions)
(defn battle-finished? [field-map]
(->> field-map
(get-units-positions)
(map #(get-in field-map (flatten [% :symbol])))
(apply =)))
(defn sum-hp [field-map]
(->> field-map
(get-units-positions)
(map #(get-in field-map (flatten [% :hp])))
(reduce +)))
(declare after-n-rounds)
(declare clear-spaces)
(declare reverse-comp)
(deftest advent-code-day15-attack
(let [raw-field
"#######
#.G...#
#...EG#
#.#.#G#
#..G#E#
#.....#
#######"]
(is (= ((reverse-comp
raw-field->field-map
next-round
#(print-field-map % :hp :symbol))
raw-field)
(clear-spaces
"#######
#..200..#
#...197197#
#.#200#197#
#...#197#
#.....#
#######")))
(is (= ((reverse-comp
raw-field->field-map
(partial after-n-rounds 23)
#(print-field-map % :symbol))
raw-field)
(clear-spaces
"#######
#...G.#
#..G.G#
#.#.#G#
#...#E#
#.....#
#######")))))
(declare move-unit)
(declare attack)
(declare get-enemy)
(declare get-adjacent)
(declare still-alive?)
(defn play-unit-turn [field-map unit]
(if (still-alive? field-map unit)
;; I should not need to check if the unit changed it state,
;; would be better to add the information of how many rounds
;; a unit played and then filter the units that still needs
;; to play in the remainnig-units in the next-round function.
(let [[field-map unit] (move-unit field-map unit)]
(attack field-map unit))
field-map))
(defn still-alive? [field-map unit-pos]
((complement nil?)
(re-find
#"[GE]"
(get-in
field-map
(flatten [unit-pos :symbol])))))
(defn attack [field-map attacking-unit]
(let [adjacent-pos (get-adjacent field-map attacking-unit)
adjacent-enemies (filter #(= (get-enemy attacking-unit field-map) (:symbol %)) adjacent-pos)
enemy-to-attack (first (sort-by :hp adjacent-enemies))
elf-attacking-power 13
goblin-attacking-power 3
attack-power (if (= "G" (:symbol enemy-to-attack)) elf-attacking-power goblin-attacking-power)]
(cond
(nil? enemy-to-attack) field-map
(<= (:hp enemy-to-attack) attack-power) (assoc-in field-map (:pos enemy-to-attack) {:symbol "."})
:else (update-in field-map (flatten [(:pos enemy-to-attack) :hp]) #(- % attack-power))))) ;; Kill unit.
(defn get-adjacent [field-map [lin col]]
[(assoc (get-in field-map [(dec lin) col]) :pos [(dec lin) col])
(assoc (get-in field-map [lin (dec col)]) :pos [lin (dec col)])
(assoc (get-in field-map [lin (inc col)]) :pos [lin (inc col)])
(assoc (get-in field-map [(inc lin) col]) :pos [(inc lin) col])])
(deftest advent-code-day15-next-round
(let [raw-field
"#########
#G..G..G#
#.......#
#.......#
#G..E..G#
#.......#
#.......#
#G..G..G#
#########"]
(is (= ((reverse-comp
raw-field->field-map
next-round
#(print-field-map % :symbol))
raw-field)
(clear-spaces
"#########
#.G...G.#
#...G...#
#...E..G#
#.G.....#
#.......#
#G..G..G#
#.......#
#########")))
(is (= ((reverse-comp
raw-field->field-map
(partial after-n-rounds 2)
#(print-field-map % :symbol))
raw-field)
(clear-spaces
"#########
#..G.G..#
#...G...#
#.G.E.G.#
#.......#
#G..G..G#
#.......#
#.......#
#########")))
(is (= ((reverse-comp
raw-field->field-map
(partial after-n-rounds 3)
#(print-field-map % :symbol))
raw-field)
(clear-spaces
"#########
#.......#
#..GGG..#
#..GEG..#
#G..G...#
#......G#
#.......#
#.......#
#########")))))
(defn after-n-rounds [n field-map]
;; ((apply comp (repeat n (fn [f] (println (print-field-map f :symbol)) (println (print-field-map f :hp :symbol)) (next-round f)))) field-map))
((apply comp (repeat n next-round)) field-map))
(declare next-position)
(defn next-round [field-map]
(let [units-positions (get-units-positions field-map)]
(loop [remaining-units units-positions
field-map field-map]
(let [selected-unit (first remaining-units)
field-map-after-unit-turn (play-unit-turn field-map selected-unit)
remaining-units (rest remaining-units)]
(if (empty? remaining-units)
field-map-after-unit-turn
(recur remaining-units field-map-after-unit-turn))))))
(defn move-unit [field-map from]
(let [unit (get-in field-map from)
position-to-move (next-position field-map from)
result-field-map (as-> field-map f
(assoc-in f from {:symbol "."})
(assoc-in f position-to-move unit))]
[result-field-map position-to-move]))
(defn get-units-positions [field-map]
(filter (complement nil?) (apply concat (map-indexed (fn [i l] (map-indexed (fn [j c] (if (re-find #"[GE]" (:symbol c)) [i j])) l)) field-map))))
(deftest advent-code-day15-position-to-move
(let [raw-field "#######
#.E...#
#.....#
#...G.#
#######"
raw-field2 "#######
#.....#
#...E.#
#...G.#
#######"]
(is (= [1 3] (next-position (raw-field->field-map raw-field) [1 2])))
(is (= [2 4] (next-position (raw-field->field-map raw-field) [3 4])))
(is (= [2 4] (next-position (raw-field->field-map raw-field2) [2 4])))
(is (= [3 4] (next-position (raw-field->field-map raw-field2) [3 4])))))
(declare field-map->path-map)
(defn next-position [field-map unit-pos]
(let [[field-map goal-position] (field-map->path-map unit-pos field-map)]
(loop [final-pos goal-position]
(let [new-final-pos (or (get-in field-map (flatten [final-pos :parent-pos]) final-pos))]
(if (= unit-pos new-final-pos)
final-pos
(recur new-final-pos))))))
(deftest advent-code-day15-shortest-path-to-enemy
(is (= ((reverse-comp
raw-field->field-map
(partial field-map->path-map [1 2])
first
#(print-field-map % :distance :symbol))
"#######
#.E...#
#.....#
#...G.#
#######")
(clear-spaces
"#######
#10123#
#21234#
#323G.#
#######"))))
(declare raw-field->lines-vector)
(declare lines-vector->field-map)
(declare adjacent-positions)
(defn field-map->path-map [[lin col] field-map]
(let [enemy (get-enemy [lin col] field-map)
friend (get-in field-map [lin col :symbol])]
(loop [positions-to-verify (adjacent-positions lin col 0)
field-map (assoc-in field-map [lin col :distance] 0)]
(let [[verifying-lin verifying-col distance parent-pos] (first positions-to-verify)
verifying-symbol (get-in field-map [verifying-lin verifying-col :symbol])
verifying-distance (get-in field-map [verifying-lin verifying-col :distance])]
;; (println positions-to-verify)
;; (println (print-field-map field-map :distance :symbol))
(cond
(= verifying-symbol enemy) [field-map parent-pos]
(empty? positions-to-verify) [field-map [lin col]]
(or
((complement nil?) verifying-distance)
(= verifying-symbol friend)
(= verifying-symbol "#"))
(recur (into [] (rest positions-to-verify)) field-map)
(= verifying-symbol ".")
(recur (into [] (rest (apply conj
positions-to-verify
(adjacent-positions verifying-lin verifying-col distance))))
(update-in field-map [verifying-lin verifying-col] assoc :distance distance :parent-pos parent-pos))
:else (throw (ex-info "Could not find any path" {:verifying-lin verifying-lin :col verifying-col :friend friend :field (print-field-map field-map :distance :symbol)})))))))
(defn get-enemy [[lin col] field-map]
(let [symbol (get-in field-map [lin col :symbol])]
(case symbol
"G" "E"
"E" "G"
(throw (ex-info "Symbol is not a unit" {:symbol symbol :pos [lin col]})))))
(defn adjacent-positions [lin col d]
[[(dec lin) col (inc d) [lin col]]
[lin (dec col) (inc d) [lin col]]
[lin (inc col) (inc d) [lin col]]
[(inc lin) col (inc d) [lin col]]])
(def raw-field->field-map
(comp lines-vector->field-map raw-field->lines-vector))
(defn lines-vector->field-map [lines-vector]
(into [] (map #(into [] (map
(fn [s] (if (re-find #"[GE]" s)
{:symbol s :hp 200}
{:symbol s}))
%))
lines-vector)))
(defn raw-field->lines-vector [raw-field]
(as-> raw-field f
(clojure.string/split f #"\n")
(map clojure.string/trim f)))
(defn print-field-map [field-map & keys]
(as-> field-map f
(map (fn [line] (map #(or ((first keys) %) ((second keys) %)) line)) f)
(map #(clojure.string/join "" %) f)
(clojure.string/join "\n" f)))
(defn clear-spaces [s]
(clojure.string/replace s #" " ""))
(defn print-batle-field [batle-field-vec]
(println (clojure.string/join "\n" batle-field-vec)))
(defn reverse-comp [& fs]
(apply comp (reverse fs)))
(println "The great answer" (get-solution
"################################
#################..#############
#################.##############
#################.####..########
############G..G...###..########
##########...G...........#######
##########.#.......#.G##########
########...#.....#...G..########
#######G.###............G#######
###########..G..#.......########
####..#####............#########
###.G.###.......G.....G.########
###..#####....#####.......######
####..#####..#######........E..#
#.##..####..#########.........E#
#....###.GG.#########........###
##....#.G...#########.......####
#....G...G..#########......#####
#..........G#########.....######
#.....G......#######......######
#........##...#####.......######
#G###...##............#....#####
#..#######................E#####
#.########...............#######
#..#######..............########
#####..#....E...##.......#######
#####.G#.......#.E..#EE.########
#####...E....#....#..###########
#######.......E....E.###########
#######.###....###.....#########
#######.####.######.....########
################################")) ;; I use it to load the big input.
;; Day 5
(defn react-polymer []
(reduce (fn [final-polymer unit]
(if (= (str (first final-polymer)) (change-case unit))
(rest final-polymer)
(conj final-polymer unit))
) '() (butlast (slurp "input.txt"))))
(defn react-polymer-no-unit [to-remove]
(reduce (fn [final-polymer unit]
(cond
(or (= (str unit) to-remove) (= (change-case unit) to-remove))
final-polymer
(= (str (first final-polymer)) (change-case unit))
(rest final-polymer)
:else
(conj final-polymer unit))
) '() (butlast (slurp "input.txt"))))
(apply min (map #(count (react-polymer-no-unit (str (char %)))) (range 97 122)))
(run-tests)
@JpOnline
Copy link
Author

I started to use declare to be able to change the order of the functions, it's possible to increase a lot readability by doing so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment