|
| 1 | +(ns y2024.d13 |
| 2 | + (:require |
| 3 | + [clojure.core.match :as m] |
| 4 | + [numeric.expresso.core :as e] |
| 5 | + [instaparse.core :as insta] |
| 6 | + [advent-of-code-clj.input :as input])) |
| 7 | + |
| 8 | +; 2024, dag 13 |
| 9 | + |
| 10 | +; ## Del 1 |
| 11 | + |
| 12 | +; I dag ser det ut til at man kan bruke matematikk for å |
| 13 | +; finne svaret på hvor mange ganger man må trykke på en av |
| 14 | +; to knapper for å få tak i belønningen. |
| 15 | + |
| 16 | +; Klassisk formel med to ukjente, men å faktisk løse den er jo en |
| 17 | +; annen sak! Heldigvis har vi et bibliotek for dette. |
| 18 | + |
| 19 | +; Starter med en liten overkomplisert parsing av dagens input: |
| 20 | + |
| 21 | +(def test-data "Button A: X+94, Y+34 |
| 22 | +Button B: X+22, Y+67 |
| 23 | +Prize: X=8400, Y=5400 |
| 24 | +
|
| 25 | +Button A: X+26, Y+66 |
| 26 | +Button B: X+67, Y+21 |
| 27 | +Prize: X=12748, Y=12176 |
| 28 | +
|
| 29 | +Button A: X+17, Y+86 |
| 30 | +Button B: X+84, Y+37 |
| 31 | +Prize: X=7870, Y=6450 |
| 32 | +
|
| 33 | +Button A: X+69, Y+23 |
| 34 | +Button B: X+27, Y+71 |
| 35 | +Prize: X=18641, Y=10279") |
| 36 | + |
| 37 | +; Instaparse lar oss definere en parser for problemet: |
| 38 | + |
| 39 | +(def game-parser |
| 40 | + (insta/parser "game = button <ws> button <ws> prize |
| 41 | + button = <'Button '> type <': X+'> num <', Y+'> num |
| 42 | + prize = <'Prize: X='> num <', Y='> num |
| 43 | + type = 'A'|'B' |
| 44 | + ws = #'[\\s\\n]*' |
| 45 | + num = #'\\d+'")) |
| 46 | + |
| 47 | +(insta/parse game-parser (first (.split test-data "\n\n"))) |
| 48 | + |
| 49 | +; Og core.match lar oss pattern-matche på parset output for å |
| 50 | +; sette sammen dataene slik vi vil: |
| 51 | + |
| 52 | +(defn game-map [game] |
| 53 | + (m/match game |
| 54 | + [:game button-a button-b prize] {:a (game-map button-a) |
| 55 | + :b (game-map button-b) |
| 56 | + :prize (game-map prize)} |
| 57 | + [:button [:type _] [:num x] [:num y]] {:x (parse-long x) |
| 58 | + :y (parse-long y)} |
| 59 | + [:prize [:num x] [:num y]] {:x (parse-long x) :y (parse-long y)})) |
| 60 | + |
| 61 | +(defn get-games [input] |
| 62 | + (map #(game-map (insta/parse game-parser %)) |
| 63 | + (.. input trim (split "\n\n")))) |
| 64 | + |
| 65 | +(get-games test-data) |
| 66 | + |
| 67 | +; For å løse spillet bruker vi expresso for å sette opp to |
| 68 | +; formler (løse for x-aksen og y-aksen): |
| 69 | + |
| 70 | +(defn solve-game [{px :x py :y} {ax :x ay :y} {bx :x by :y}] |
| 71 | + (first (e/solve ['a 'b] |
| 72 | + (e/ex (= ~px (+ (* ~ax a) (* ~bx b)))) |
| 73 | + (e/ex (= ~py (+ (* ~ay a) (* ~by b))))))) |
| 74 | + |
| 75 | +(let [{:keys [a b prize]} (first (get-games test-data))] |
| 76 | + (solve-game prize a b)) |
| 77 | + |
| 78 | +; Med løsningsfunksjonen i hånd kan vi lage reduceren som løser |
| 79 | +; del 1. I del 1 er det ikke lov å trykke mer enn 100 ganger |
| 80 | +; på hver knapp, og det er ikke lov med "halve trykk", så vi |
| 81 | +; filtrerer vekk løsninger som gir ratio (feks `1/3`): |
| 82 | + |
| 83 | +(defn part-1 [acc {:keys [prize a b]}] |
| 84 | + (let [{a 'a b 'b} (solve-game prize a b)] |
| 85 | + (if (or (< 100 a) (< 100 b) |
| 86 | + (ratio? a) (ratio? b)) |
| 87 | + acc |
| 88 | + (+ acc (* a 3) b)))) |
| 89 | + |
| 90 | +(defn solve [solver input] |
| 91 | + (reduce solver 0 |
| 92 | + (get-games input))) |
| 93 | + |
| 94 | +; Nå kan vi løse del 1 for test-dataene: |
| 95 | + |
| 96 | +(solve part-1 test-data) |
| 97 | + |
| 98 | +; Og for reelle data: |
| 99 | + |
| 100 | +(solve part-1 (input/get-input 2024 13)) |
| 101 | + |
| 102 | +; # Del 2 |
| 103 | + |
| 104 | +; I del 2 er belønningen i en posisjon som er `10000000000000` |
| 105 | +; lenger unna i hver akse! |
| 106 | + |
| 107 | +; Nå er det også lov å trykke flere ganger på knappene, som er |
| 108 | +; reflektert i den nye reduceren for del 2: |
| 109 | + |
| 110 | +(defn part-2 [acc {:keys [prize a b]}] |
| 111 | + (let [bigger-prize (-> prize |
| 112 | + (update :x + 10000000000000) |
| 113 | + (update :y + 10000000000000)) |
| 114 | + {a 'a b 'b} (solve-game bigger-prize a b)] |
| 115 | + (if (or (ratio? a) (ratio? b)) |
| 116 | + acc |
| 117 | + (+ acc (* a 3) b)))) |
| 118 | + |
| 119 | +; Ellers kan vi løse oppgaven på samme måte som i del 1 for test-input: |
| 120 | + |
| 121 | +(solve part-2 test-data) |
| 122 | + |
| 123 | +; Og faktiske input: |
| 124 | + |
| 125 | +(solve part-2 (input/get-input 2024 13)) |
0 commit comments