Skip to content

Commit 5e01dec

Browse files
committed
Day 9
1 parent 73ee693 commit 5e01dec

File tree

7 files changed

+314
-1
lines changed

7 files changed

+314
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
| 6 | [source](src/advent_2021_clojure/day06.clj) | [blog](docs/day06.md) |
1111
| 7 | [source](src/advent_2021_clojure/day07.clj) | [blog](docs/day07.md) |
1212
| 8 | [source](src/advent_2021_clojure/day08.clj) | [blog](docs/day08.md) |
13+
| 9 | [source](src/advent_2021_clojure/day09.clj) | [blog](docs/day09.md) |

docs/day09.md

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Day Nine: Smoke Basin
2+
3+
* [Problem statement](https://adventofcode.com/2021/day/9)
4+
* [Solution code](https://github.com/abyala/advent-2021-clojure/blob/master/src/advent_2021_clojure/day09.clj)
5+
6+
---
7+
8+
## Preamble
9+
10+
This year, I seem to be on a quest to replace anonymous functions of the form `#(foo %)` with partial functions and
11+
the use of `juxt` and `comp`. I'm not sure why that is, but it seems to be a pattern. I have discussed `partial`,
12+
`juxt`, and `comp` in previous problems this season, so I won't be explaining them anymore, so feel free to look back
13+
if things are starting to look funny.
14+
15+
---
16+
17+
## Part 1
18+
19+
When exploring underwater caves, one does run the risk of running into
20+
[liquid hot magma](https://www.youtube.com/watch?v=UNJU-5vCrJc), which apparently we have done today. We need to
21+
navigate the cave carefully. In part 1, we need to identify all points within the cave that are local minima (smaller
22+
than all cardinal neighbors), increment their heights and add the results together.
23+
24+
In most such problems, I tend to convert the entire input into one giant map of coordinates to values, in the shape of
25+
`{[x y] v}`, but for a change of pace I decided to go old-school and use nested vectors. So my core data structure
26+
is simply `[[values]]`. Thus to parse the cave from the initial input, I split the input string into a sequence of
27+
strings for each line, and then we have a fun line `(mapv (partial mapv (comp parse-int str)) line)`. So let's piece
28+
this together. The outer `mapv` says we're going to create a vector from mapping each line to the internal function;
29+
we need `mapv` to ensure we get an outer vector, since we'll be accessing it by index. The inner function can be
30+
partial because we're calling `(mapv (comp parse-int str) line)` where `line` is fed in from the outer `mapv`. Each
31+
inner line is a numeric string, so we need to convert each character to its integer value. I implemented a `char->int`
32+
function in the `utils` package to perform this.
33+
34+
```clojure
35+
; advent-2021-clojure.utils namespace
36+
(defn char->int [c] (- (int c) 48))
37+
38+
; advent-2021-clojure.day09 namespace
39+
(defn parse-cave [input] (->> (str/split-lines input)
40+
(mapv (partial mapv utils/char->int))))
41+
```
42+
43+
Next, we need to find all of the lowest points in the cave, so we'll need four functions to get there. First, the
44+
`neighbors` function in the `advent-2021-clojure.point` namespace will return the coordinates of the four points
45+
adjacent to a single point. This is one of those instances of using `(mapv f coll1 coll2)` instead of the normal
46+
`(mapv f coll)`, where the mapping function gets applied to the nth instance of multiple collections. This lets us
47+
add the `x` and `y` coordinates of two points together.
48+
49+
The `lowest-point?` function checks if a single point in a cave is a local low point. This is the first of several
50+
instances where we'll be calling `(get-in cave point)`. `get-in` takes in a collection and a vector of nested values
51+
to apply to get to a sub-value in a collection, or `nil` if that value does not exist in the collection. So
52+
`(get-in {:name {:first "Franklin" :middle "Delano" :last "Roosevelt"}} [:name :middle])` returns `"Delano"`, and for
53+
vectors, `(get-in [:a :b [:c :d :e]] [2 2])` returns `:e`. Thus we get the value of a point (which is a vector) within
54+
the cave by calling `(get-in cave point)`. Then we find all neighbors to the given point, keep the ones that exist
55+
within the cave by calling `keep`, and then ensure that the point's height is lower than all of its neighbors.
56+
57+
With that function, it's easy to find all of the lowest points. `all-coords` will return all `[y x]` coordinates in
58+
the cave; note that we use `[y x]` instead of `[x y]` because the outer vector of our `[[values]]` data structure is
59+
the row, which is indexed by `y`, and the inner vector is the column, which is indexed by `x`. Finally, in
60+
`lowest-points`, we take all of the coordinates in the cave, and filter them to the ones that pass `lowest-point?`.
61+
62+
```clojure
63+
; advent-2021-clojure.point namespace
64+
(defn neighbors [point]
65+
(map (partial mapv + point) [[0 1] [0 -1] [-1 0] [1 0]]))
66+
67+
; advent-2021-clojure.day09 namespace
68+
(defn lowest-point? [cave point]
69+
(let [height (get-in cave point)]
70+
(->> (neighbors point)
71+
(keep (partial get-in cave))
72+
(every? (partial < height)))))
73+
74+
(defn all-coords [cave] (for [y (-> cave count range)
75+
x (-> cave first count range)]
76+
[y x]))
77+
78+
(defn lowest-points [cave]
79+
(filter (partial lowest-point? cave) (all-coords cave)))
80+
```
81+
82+
Finally, we're ready to write `part1`. Starting with the coordinates of all of the lowest points in the cave, we map
83+
them to their actual values, increment each one, and add them together.
84+
85+
```clojure
86+
(defn part1 [input]
87+
(let [cave (parse-cave input)]
88+
(->> (lowest-points cave)
89+
(map (partial get-in cave))
90+
(map inc)
91+
(apply +))))
92+
```
93+
94+
Have we reached the low point of our problem today? Let's find out.
95+
96+
---
97+
98+
## Part 2
99+
100+
Now we need to find all of the "basins" in the cave, a section whose phrasing I found incredibly misleading. The
101+
instructions say, in part, "A basin is all locations that eventually flow downward <u>to a single low point</u>"
102+
(emphasis added). That last part is untrue. What the problem seems to mean is "a basin is all locations that are
103+
fully enclosed by either the outer wall or a 9." It appears that a basin can happily contain two or more low points.
104+
105+
So the way I want to find the basins is to go to each lowest point, and find the basin around it by moving outward
106+
until we hit either a wall or a 9. For this, we'll first start with a `high-point?` function, which just returns
107+
whether a coordinate in the cave equals the max value of 9.
108+
109+
Then, the `basin-around` function takes in a cave and a low point, and returns the set of all coordinates within that
110+
basin. We'll use the recursive `loop-recur` construct, in which I often use the bindings of `candidates` (what I'm
111+
looping over) and `found` (the accumulated values). If there are candidate points to inspect, which we can tell by
112+
calling `(if-some [point (first candidates)])`, then we continue to the loop, and if not then we return the set of
113+
found points. When looping, we remove the current point from the `candidates` with `(rest candidates)`, and then add
114+
the all neighbors of that point that are in the cave, but which are not either already found or are themselves high
115+
points.
116+
117+
```clojure
118+
(def max-height 9)
119+
(defn high-point? [cave point] (= max-height (get-in cave point)))
120+
121+
(defn basin-around [cave lowest-point]
122+
(loop [candidates #{lowest-point}, found #{}]
123+
(if-some [point (first candidates)]
124+
(recur (reduce conj (rest candidates) (->> (point/neighbors point)
125+
(filter (partial get-in cave))
126+
(remove (some-fn found (partial high-point? cave)))))
127+
(conj found point))
128+
found)))
129+
```
130+
131+
And then we're ready to build `part2`. Starting from each of the lowest points, we map each one first to its basin
132+
(expressed as its set of points), which we then convert to the number of points within the basin. Then
133+
`(sort-by -)` puts them in reverse size order, from which we take the top three and multiply those values together.
134+
135+
```clojure
136+
(defn part2 [input]
137+
(let [cave (parse-cave input)]
138+
(->> (lowest-points cave)
139+
(map (comp count (partial basin-around cave)))
140+
(sort-by -)
141+
(take 3)
142+
(apply *))))
143+
```

resources/day09_data.txt

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
7678934989765431234965432345679567899987999876543210198965019878921987654343498765678932445678998678
2+
4567929878965420129874301286789456789976898989654321987894329767990986543232349874589321234567987567
3+
5679896567895431298763212878992345999875787898769439876789498656789875432101234965695410145678998456
4+
6798765457889942987654324567891239898764656999877698765678987645898976543432349876789521234567899679
5+
7987674345679899999765465678932398765832345899998959854689965434567897676546567989895432347878965989
6+
8999543256798777898976576789993987654521456789999843212569896545678998787959698991976543956799654695
7+
9987652127987665767897689899989876543210345679898754343456798656789239899868789432398659897989543234
8+
5498763439876543458789789999876999854321234599649877654767899769893123978979896543698798789878954345
9+
4329874598654432344678994599995498765435345987430998767878943978932012367989987654569987698767895467
10+
3212985798743210123489123989985259876746459876321249988989652989893134456799998765879896548656789578
11+
4339987987654321234691019979874345987897898765432356799199871296789245667898999976998785438745678989
12+
5498798998878534575692198767965456798998939878543467899299852345678956789987899897987654321236789799
13+
6987659109986545689789987659876567899659321989654568978989643456789767899876788789998776434545697678
14+
9876543219999876799899876546989878987543210198765679767678954587899898956985897678949996547656789799
15+
1987654998943989899987985434599989598657432349876789656569765678987939349854864589234987858767899989
16+
0198789887894599989996543515678993498796545456987898743478976789876321298763453678949898969898989878
17+
1999898776795999878989432101789932349987656567898989832567897899965490989541012567898769878999878967
18+
9899987685679889769878943232597891234598767899999876543478998999876989876432123456789656989298765456
19+
8789876554798778654567894743456789345679878923498987654569129989989976987543234678996545392129532367
20+
7698765443987664543456797654567899956989989014987698765678998877998765498954545689987632129098921278
21+
6569954212396543212345898765678999899898799195696549879899987666897654349765656789876543098997892389
22+
3459864343498752101456789878789198785787678989987832989999876545698743239878767897987654987886789492
23+
2398765674599543412467899999891097673654569878999421999998765436789632134999898976898789876795678991
24+
3499876965987654325698978988932986542323698767698939878999876521296541013456999285999898765644567889
25+
4989987899998765434789769876549876541012398654567998767899987990965432123467892134598999874323456978
26+
9878998998979879545678954987656985432143987843456789656989999889897545678578943245987898765401234567
27+
8767899877569998776989012999767896543459876532345678945778989678789656799989987656796789876512345678
28+
7658998766458799887899929898978997957867989665456789234569876567678998892399998767985478984323456989
29+
8745989854345678998999898787899989898978998776569894129678985425467889910978999899876367965434567891
30+
1239877541234589659298797656789876789989329987678953298989893212345678951867899987985459876565678932
31+
0198765430147678940129654345896545679096598798789874987899794101234989743456789876798567987676989543
32+
1239954321256789432334963234989632889129987679899989876997693212567897654867899965987678998789899994
33+
4345965432367898743949892145678921299298995423998798765434589323456789965979989874398999109898798789
34+
5459876563459999659898789236989545678987893212989659878321678934567897896998879983219999912987687678
35+
6567987676567898998787678947899856789456954909876543987430189765678956799897569994301989899876543587
36+
7678998987698987997654567898998767899397899899965452396542239876789545698786478989319876798767432456
37+
8989769998789996789879678929349878998989998789876321987963458989895432987645345678999985679654321347
38+
9995456999899985698998789210198989987678987678988432399874567894976321296532123456789876798768436458
39+
8764367898999874587899894321987899896567896569876543498985678923965432397832012567899987899876547969
40+
8743234567898963256789995939876798765456789456989654987696789019876943498945123678999898976998679898
41+
7632123458987654145678989898765987654345894345698769876549894125988767569656734589998789985498798767
42+
6543014567898543236789876789874398765656953234569898765634943234599878998767845678945678976569989656
43+
9667323467899876347898785459995219876769652125678987654323794545679989999898976789234567897978976547
44+
8778434568959875467897654398989323989878943012567898765474689678998997898979797890195998998989865434
45+
9989545678943976778998769987678934699989542123456999878565678999996545987667698991989899239299754323
46+
3597676789432987989999898996567895678997674334678987999678789989889239876555459789976789393198643514
47+
2498787999921099998989987887478986789498765445789876569989898876778998765434345678965678989019432101
48+
3569898939892123987679876782347897896329876786898763458999977745769899874321234567894389678998565212
49+
9679979328789239876598765431256989965212987897987654567899865632356798765410123478943234567987654323
50+
8998765412678998965439877632345678954323998998998765678998754321467999876323234569432123456798765634
51+
7899976543489987654323998543458989895439879769999876789789854210349898987434546678943234897899878795
52+
6789897654567898765214569654567898789598765657889987997698765321298787898865678989874346789954989896
53+
5976789765789989876501239865678975678987654345678998954579877452987676789976789599865457898543292987
54+
4345899876789876975423345976789214567898943234989879563459976569876555678987892459876568987652101298
55+
3234989989898765987564569987894323878989432129898765432398987699765434599099921012987679876543212349
56+
0125678999987654598965878998989439989876543298789876321297898987654321989198999129898789987654324556
57+
1237889789876543459876989439878998996989656989678943210986789998765439878987988998789999998875434567
58+
4356897654987659767987898998769877895498769878569656921975679989896798767896567897678998989998565678
59+
5456789543299998978998967987654566789329899767498969899864589878987899854578456789567987776597676989
60+
6589897632198767899219459876543045678914987654347898789653298767898998763562349895459876567459789991
61+
7678998943987656998901367965432123489323999543216987678943109456789999872101238954398765482359898910
62+
9889569959998549897899459987643234595439898932105698567893298967897895983432867893239654321454967891
63+
6993459898987698786788998798654345696598787943214595466789987898976794594543456789398763210123459942
64+
5432398767898987674597897659765456789987656894323986345679876799765789965654567898999876321254568953
65+
6543987656999876563456789749876567899878547895434595456789965689654569876767878967899965432345778964
66+
7679876545789995432367997439987698978965436789545698567899876789323456989898989456989996763456789765
67+
8798987434599989321459896598998999765432125678956987689901987895434568992949995345678987654567899896
68+
9987894323989878932998789987679899875432014589767898789912398998567899101239854234567898765678954987
69+
9876789439878769899877678987598798989543123698978999897893459987678973212398765123498999876789543098
70+
8765678998767456798765567896454687899955954567899896946989567898989765434569876764689998987899932129
71+
8754567987654323998643456789323456789876865678998785435679698929999876545678989985699987498999899945
72+
7643489998543219865432347893212345699997977899987654323798989019876987876899599876789876349998768896
73+
8732578998654301976545456789103457789999998999876543212987678998765498997945456987898765234987656789
74+
6543678959765214989856567893234569996987899298765432109876578999896399989432345698919954139876545899
75+
7654789349874323498767678954645678945876789349876543499765467890987989878991234569109893239985434578
76+
8765891299985434569878989765676789434765679459987654987654356891999876765789945678998789398765523456
77+
9976910989876545678989999877987896523254768998998765698775456789899765774677896789987658987654312348
78+
4987899976987678989998999998998965410123459987989979789876589899768954523456987893299767898732101267
79+
3298998765698989199987898999999654321234567896567989895987678998656893212347898932109878999843212456
80+
2129219854349993239876567899989876432389678985456999954398789987434789101256789543299989987656323567
81+
3998998765457899398985457789876987545678999876567899843219899986525678919397899956989999998767439678
82+
9897899879568998987654345678965987656789998987678998765401998897636789998989999899878999879896568789
83+
7776901998979987698788234567894598789899897698989987654323456789647899987679998798767898765987679892
84+
6545892987899876549887545678943219898999789549099999875634578998757999898567899632347999894398989943
85+
5434799876989765432998656789894345967988678932129876988785699109898998765456789543456789989219397994
86+
4327689945678954321298797895789959659876569893998965499896789212999899984345697654567899876101236789
87+
5212578934567895532789989934567898945989989789897894345987894323498769875456789765688999865412345699
88+
4323459323656789645679878929689967896794398698756789234598995459987654986568999878799987654323456789
89+
6496589212345678956898767898998756789899987598767892123999989698976543987779872989892198865434567896
90+
7587678923456789877989856787897645678998765459899921019899878997895432398889761098963239978545699975
91+
8698989634587893989876543836989534567899876567995432198789769876789521239997653197654347987656989654
92+
8789998545678932395978532125678923456789989979989543987698954545678994345679794989987456799769978932
93+
9899987656789541234965432034589104567899899899878959876567893234568989456798989678976567899898867941
94+
9998798967895410199876543456678915678998789798765899999456789125679878967987878567897678910997658910
95+
9897659878964321987998954568789896789679644699876789878349893236898767899876563456789989899879846891
96+
8799543989965439876339769878896799894598533989989898965258954345987656988986432345999998798765735569
97+
7678942397897598765219898989945689923987212378899987654147895456988743877898321234568987659874323478
98+
6569891456789679854329987893234578939876101256789998943236789679876432356789432345678986545985412567
99+
3456789967898789965498966534134689545965432345678929854345678989765421245679544856789976439876323478
100+
4567999878999999876987654321012789659878543656789219765656789999876532476789667767899876545987454567

src/advent_2021_clojure/day09.clj

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
(ns advent-2021-clojure.day09
2+
(:require
3+
[advent-2021-clojure.point :as point]
4+
[advent-2021-clojure.utils :as utils :refer [parse-int]]
5+
[clojure.string :as str]))
6+
7+
(defn parse-cave [input] (->> (str/split-lines input)
8+
(mapv (partial mapv utils/char->int))))
9+
10+
(defn lowest-point? [cave point]
11+
(let [height (get-in cave point)]
12+
(->> (point/neighbors point)
13+
(keep (partial get-in cave))
14+
(every? (partial < height)))))
15+
16+
(defn all-coords [cave] (for [y (-> cave count range)
17+
x (-> cave first count range)]
18+
[y x]))
19+
20+
(defn lowest-points [cave]
21+
(filter (partial lowest-point? cave) (all-coords cave)))
22+
23+
(defn part1 [input]
24+
(let [cave (parse-cave input)]
25+
(->> (lowest-points cave)
26+
(map (partial get-in cave))
27+
(map inc)
28+
(apply +))))
29+
30+
(def max-height 9)
31+
(defn high-point? [cave point] (= max-height (get-in cave point)))
32+
33+
(defn basin-around [cave lowest-point]
34+
(loop [candidates #{lowest-point}, found #{}]
35+
(if-some [point (first candidates)]
36+
(recur (reduce conj (rest candidates) (->> (point/neighbors point)
37+
(filter (partial get-in cave))
38+
(remove (some-fn found (partial high-point? cave)))))
39+
(conj found point))
40+
found)))
41+
42+
(defn part2 [input]
43+
(let [cave (parse-cave input)]
44+
(->> (lowest-points cave)
45+
(map (comp count (partial basin-around cave)))
46+
(sort-by -)
47+
(take 3)
48+
(apply *))))

src/advent_2021_clojure/point.clj

+3
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@
2727
(defn vertical-line?
2828
([[point1 point2]] (vertical-line? point1 point2))
2929
([[x1 _] [x2 _]] (= x1 x2)))
30+
31+
(defn neighbors [point]
32+
(map (partial mapv + point) [[0 1] [0 -1] [-1 0] [1 0]]))

src/advent_2021_clojure/utils.clj

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@
1616

1717
(defn summation [n]
1818
(-> (* n (inc n))
19-
(/ 2)))
19+
(/ 2)))
20+
21+
(defn char->int [c] (- (int c) 48))

0 commit comments

Comments
 (0)