Skip to content

Commit 2a3144a

Browse files
committed
2024, dag 20
1 parent c4eefcf commit 2a3144a

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

input/2024/input20.nippy

7.6 KB
Binary file not shown.

notebooks/y2024/d20.clj

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
(ns y2024.d20
2+
(:require
3+
[advent-of-code-clj.input :as input]
4+
[advent-of-code-clj.utils :as utils]
5+
[medley.core :as medley]))
6+
7+
; # 2024, dag 20
8+
9+
; ## Del 1
10+
11+
; Vi skal finne korteste sti i en labyrint, men denne gangen er
12+
; twisten at man kan jukse én gang!
13+
14+
; Så i stedet for å kunne enkelt bruke en shortest path-algoritme
15+
; må vi tenke litt annerledes.
16+
17+
; Hva med å "tabulere" hele kartet baklengs fra mål, gi hver node
18+
; en "kost", og så finne alle mulige snarveier? Da kan man finne ut
19+
; hvor mye tid som spares på å ta snarveien...
20+
21+
(def test-input "###############
22+
#...#...#.....#
23+
#.#.#.#.#.###.#
24+
#S#...#.#.#...#
25+
#######.#.#.###
26+
#######.#.#...#
27+
#######.#.###.#
28+
###..E#...#...#
29+
###.#######.###
30+
#...###...#...#
31+
#.#####.#.###.#
32+
#.#...#.#.#...#
33+
#.#.#.#.#.#.###
34+
#...#...#...###
35+
###############")
36+
37+
; Vi starter med å samle opp alle mulige posisjoner:
38+
39+
(defn coordinates [input]
40+
(let [coord-map (utils/coord-map-fixed (utils/text->matrix input))
41+
start-node (key (medley/find-first (fn [[k v]]
42+
(= \S v))
43+
coord-map))
44+
end-node (key (medley/find-first (fn [[k v]]
45+
(= \E v))
46+
coord-map))]
47+
{:start-node start-node
48+
:end-node end-node
49+
:possible-locations (keys (medley/filter-vals #{\. \S \E} coord-map))}))
50+
51+
(update (coordinates test-input) :possible-locations #(take 10 %))
52+
53+
; Så kan vi gå fra slutt-noden og finne ut hvor mange steg man må ta:
54+
55+
(defn node->cost [possible-locations from-node]
56+
(let [valid-location (set possible-locations)]
57+
(->> (utils/breadth-search
58+
(fn [node]
59+
(->> (apply utils/adjacent-hv node)
60+
(filter valid-location)))
61+
from-node)
62+
(map-indexed (fn [idx items]
63+
(into {} (map (fn [node] [node idx]))
64+
items)))
65+
(apply merge))))
66+
67+
(let [{p :possible-locations
68+
from :end-node} (coordinates test-input)]
69+
(node->cost p from))
70+
71+
; Neste steg er å finne alle mulige snarveier. En snarvei er lik
72+
; en mulig lokasjon som kan nå en annen mulig lokasjon selv om en
73+
; umulig lokasjon er mellom.
74+
75+
(def dir-vectors
76+
(for [y [-1 0 1]
77+
x [-1 0 1]
78+
:when (not= x y)
79+
:when (or (zero? x) (zero? y))]
80+
[y x]))
81+
82+
dir-vectors
83+
84+
(defn shortcuts
85+
"For each possible location, can it reach a location two steps
86+
away with an impossible location in-between?"
87+
[possible-locations]
88+
(let [loc-set (set possible-locations)]
89+
(->> (for [loc-a possible-locations
90+
dir-v dir-vectors
91+
:let [next-v (mapv #(* % 2) dir-v)
92+
pos-1 (mapv + loc-a dir-v)
93+
pos-2 (mapv + loc-a next-v)]
94+
:when (and (not (loc-set pos-1))
95+
(loc-set pos-2))]
96+
#{loc-a pos-2})
97+
set)))
98+
99+
; Nå ser vi at det er 44 mulige snarveier:
100+
101+
(count (shortcuts (:possible-locations (coordinates test-input))))
102+
103+
; Nå kan vi gå igjennom hver mulige snarvei, og se hvor mye tid som
104+
; kan spares ved å bruke snarveien. Tiden spart finner vi ved å plusse
105+
; sammen de korteste lengdene til snarvei-nodene fra start og slutt:
106+
107+
(defn cost-with-shortcut [node->cost-1 node->cost-2 shortcut]
108+
(let [c1 (mapv node->cost-1 shortcut)
109+
c2 (mapv node->cost-2 shortcut)]
110+
(+ (apply min c1) (apply min c2) 2)))
111+
112+
; Vi forventer at vi sparer 64 steg på å hoppe mellom slutt-noden
113+
; og noden to steg til høyre:
114+
115+
(let [{:keys [possible-locations start-node end-node]} (coordinates test-input)
116+
node->cost-from-start (node->cost possible-locations start-node)
117+
node->cost-from-end (node->cost possible-locations end-node)
118+
cost (node->cost-from-start end-node)]
119+
(- cost (cost-with-shortcut
120+
node->cost-from-start
121+
node->cost-from-end
122+
#{[7 7] [7 5]})))
123+
124+
; Dette ser jo ut til å fungere! Da kan vi prøve å løse del 1:
125+
126+
(defn part-1 [required-amount-saved input]
127+
(let [{:keys [possible-locations start-node end-node]} (coordinates input)
128+
node->cost-from-start (node->cost possible-locations start-node)
129+
node->cost-from-end (node->cost possible-locations end-node)
130+
cost (node->cost-from-start end-node)
131+
shortcuts (shortcuts possible-locations)]
132+
(->> shortcuts
133+
(map #(- cost (cost-with-shortcut node->cost-from-start
134+
node->cost-from-end
135+
%)))
136+
(filter (fn [savings] (>= savings required-amount-saved)))
137+
count)))
138+
139+
; For å teste kan vi se om vi finner de 5 snarveiene som er beskrevet i oppgaveteksten
140+
; som har en besparelse over 20 steg i test-dataene:
141+
142+
(delay (part-1 20 test-input))
143+
144+
; Det ser ut til å fungere også, så da finner vi nok løsningen på del 1:
145+
146+
(delay (part-1 100 (input/get-input 2024 20)))

0 commit comments

Comments
 (0)