Last active
November 4, 2020 13:12
-
-
Save JpOnline/ff968f4232cf9a54de0a98193a62af4c to your computer and use it in GitHub Desktop.
Clojure solutions for https://adventofcode.com/2018
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.