Skip to content

Commit ca5fdbd

Browse files
committed
Day 20
1 parent c65ae94 commit ca5fdbd

File tree

7 files changed

+352
-2
lines changed

7 files changed

+352
-2
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
| 16 | [source](src/advent_2021_clojure/day16.clj) | [blog](docs/day16.md) |
2121
| 17 | [source](src/advent_2021_clojure/day17.clj) | [blog](docs/day17.md) |
2222
| 18 | [source](src/advent_2021_clojure/day18.clj) | [blog](docs/day18.md) |
23+
| 20 | [source](src/advent_2021_clojure/day20.clj) | [blog](docs/day20.md) |

docs/day20.md

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Day Twenty: Trench Map
2+
3+
* [Problem statement](https://adventofcode.com/2021/day/20)
4+
* [Solution code](https://github.com/abyala/advent-2021-clojure/blob/master/src/advent_2021_clojure/day20.clj)
5+
6+
---
7+
8+
## Part 1
9+
10+
Today we're simulating a Game Of Life as represented by an image whose pixels are either light or dark with each
11+
generation. Our goal is to run the game a certain number of times against an image atop an infinite background, and
12+
then count up the number of lit pixels in the end.
13+
14+
To start off, let's parse the input, which comes in as a single line of the 512-character "algorithm," followed by
15+
the initial image. We'll just return that as a two-element vector, leveraging `utils/split-blank-line` to split the
16+
input into its two segments. For the algorithm, we'll make a `pixel-map` that converts `.` to zero and `#` to 1; calling
17+
`mapv` will ensure the result is a an indexed vector instead of a linked list. For the image, we'll want that to be a
18+
map of `{[x y] 0-or-1}`. We'll leverage our existing `point/parse-to-char-coords` to return a sequence of each
19+
coordinate pair to its character, and then associate the coords onto the resulting map by again mapping the character
20+
using the `pixel-map`. Naturally we'll use constants of `dark-pixel` and `light-pixel` to avoid repeating `0` and `1`
21+
throughout the solution.
22+
23+
```clojure
24+
(def dark-pixel 0)
25+
(def light-pixel 1)
26+
27+
(defn parse-input [input]
28+
(let [pixel-map {\. dark-pixel, \# light-pixel}
29+
[alg image] (utils/split-blank-line input)]
30+
[(mapv pixel-map alg)
31+
(reduce (fn [m [coord c]] (assoc m coord (pixel-map c)))
32+
{}
33+
(point/parse-to-char-coords image))]))
34+
```
35+
36+
Our goal will include going to every point in the current image, looking at its surrounding 3x3 grid, and construct a
37+
new image from the converted characters. But what do we do along the border? Well initially, every surrounding point
38+
will be dark (aka `0`), but the algorithm tells us whether that will be true for every generation. To determine this,
39+
we'll make an infinite `border-seq`, which takes in the algorithm and returns what every infinite border value will be
40+
in each generation. Imagining an initial point far away from our image; all nine points in its grid will be dark,
41+
meaning that its binary value will be `000000000` or just plain `0`. Some algorithms will map a `0` to `0` again,
42+
keeping the border dark, but some might map it to `1` to make it light. Then similarly, a distant point of all light
43+
values will have a binary value of `111111111` or `511`, which could again map each character to dark or light. So
44+
`block-of` will map `0` to `0`, and `1` to `511`, and we'll `iterate` on mapping each previous value to its block
45+
value, and then use `alg` to convert it to its new value at that index.
46+
47+
For what it's worth, in the sample problem, `(get alg 0)` is zero, so the border is always dark. In my input,
48+
`(map alg [0 511])` was `[1 0]`, so the background flickered every generation from dark to light and back again.
49+
50+
```clojure
51+
(defn border-seq [alg]
52+
(let [block-of {dark-pixel 0, light-pixel 511}]
53+
(iterate #(-> % block-of alg) 0)))
54+
```
55+
56+
Now we can create a `migrate-image` function, which takes in the algorithm, the value of all border coordinates, and
57+
the current image, and returns the next image by migrating every point. To do this, we'll use a simple `reduce-kv` to
58+
migrate each coordinate in the image, calling `next-value-at` for each coordinate. The `next-value-at` looks up all
59+
points surrounding the current one, maps it to either its value on the image or else the default value of the
60+
infinite border character. Then it concatenates all 9 characters into a binary string, which it converts into a number
61+
and finds within the algorithm vector.
62+
63+
```clojure
64+
(defn next-value-at [alg border image coords]
65+
(->> (point/surrounding true coords)
66+
(map #(or (image %) border))
67+
(apply str)
68+
utils/parse-binary
69+
alg))
70+
71+
(defn migrate-image [alg border image]
72+
(reduce-kv (fn [m coord _] (assoc m coord (next-value-at alg border image coord)))
73+
{} image))
74+
```
75+
76+
We want to get to a function that returns every generation of the image, but there's only one issue to address first.
77+
While `migrate-image` and `next-value-at` takes into account the infinite border, with each generation we almost
78+
certainly will have at least one point along the perimeter that interacted with the infinite border, meaning that the
79+
points just outside the previous border of the image will need to be calculated in the next generation. To account for
80+
this, with each generation we'll need to expand the image by one row at the top and bottom, and by one column on the
81+
left and right. (In theory, we could just expand the entire image once to the maximum intended size, but that pollutes
82+
what each function does.)
83+
84+
The `expand-image` function looks at the current min and max values for both `x` and `y`, and associates the border
85+
character to all coordinates of the surrounding perimeter. We implement `perimeter-points` in the `point` namespace
86+
by taking all `[x y]` coordinates from a top-left point to the bottom-right point. Then `expand-image` uses `reduce`
87+
to `assoc` the `border` onto each perimeter point, starting from the `image` map itself.
88+
89+
```clojure
90+
; advent-2021-clojure.point namespace
91+
(defn perimeter-points [[x0 y0] [x1 y1]]
92+
(concat (for [x [x0 x1], y (range y0 (inc y1))] [x y])
93+
(for [y [y0 y1], x (range (inc x0) x1)] [x y])))
94+
95+
; advent-2021-clojure.day20 namespace
96+
(defn expand-image [image border]
97+
(let [min-max (juxt (partial apply min) (partial apply max))
98+
[min-x max-x] (min-max (map ffirst image))
99+
[min-y max-y] (min-max (map (comp second first) image))]
100+
(reduce #(assoc %1 %2 border)
101+
image
102+
(point/perimeter-points [(dec min-x) (dec min-y)]
103+
[(inc max-x) (inc max-y)]))))
104+
```
105+
106+
So where are we now? We know the sequence of values of the infinite border, and we can use that sequence to both
107+
expand the current image one step into its perimeter, and then to migrate every point of the image based on its
108+
neighbors. The only major step remaining is to create `image-seq`, to generate an infinite sequence of every generation
109+
the image goes through as it migrates. This function takes in the algorithm and initial image, and then creates the
110+
`border-seq` in the background. For each generation, is expands and migrates the image on the current head of the
111+
border sequence, and then uses `lazy-seq` (similar to `iterate`) to create the next element of the sequence. We don't
112+
use `iterate` here because we can't use the same value for the border with each generation, but rather need to call
113+
`(rest borders)` each time.
114+
115+
```clojure
116+
(defn image-seq
117+
([alg image] (image-seq alg image (border-seq alg)))
118+
([alg image borders] (let [border (first borders)
119+
image' (->> (expand-image image border)
120+
(migrate-image alg border))]
121+
(lazy-seq (cons image' (image-seq alg image' (rest borders)))))))
122+
```
123+
124+
Alright, let's create our `solve` function. We'll take in the input string and the number of enhancements we want to
125+
apply. The function parses the input, converts it into the sequence of images, and finds the `nth` value by dropping
126+
`(dec enhance-count)` values from the sequence and pulling the next value. From that image, it counts the number of
127+
values in the `[[x y] pixel]` pair by counting the number of pixels that are `light-pixel`, or `1`.
128+
129+
```clojure
130+
(defn solve [input enhance-count]
131+
(->> (parse-input input)
132+
(apply image-seq)
133+
(drop (dec enhance-count))
134+
first
135+
(filter #(= light-pixel (second %)))
136+
count))
137+
```
138+
139+
Finally, `part1` just calls `solve`, looking for the number of pixels after the second enhancement.
140+
```clojure
141+
(defn part1 [input] (solve input 2))
142+
```
143+
144+
---
145+
146+
## Part 2
147+
148+
Part 2 runs the same algorithm for fifty enhancements. The code we already have is fine; we'll get the answer in about
149+
10-15 seconds, which is fast enough for me.
150+
151+
```clojure
152+
(defn part2 [input] (solve input 50))
153+
```

resources/day20_data.txt

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
##........#.#.#.##.#.##...###.#.##.##.#.###..#...####..#.#.###.##....#....###..###.##..##.#.#..#....#.####..#####....##..#...##.##.....#.######.##.......##...#.#..##.#.#.#.######..##.###.#..#..###.##...#####.#....#.######.#.#..##..#.###...##.#...#.####..#.#.##..##..###..##...#.....####....###.##.#.##.#...##...#############...##.#.###.##.##.#.#####.......###.##..##..##.##.##.#..#.#..####..#####...#...#.###.##........#.##......##..###.##..#####.##........##..#.#..#.##....##.#.#..###.####.#..##...###..###.#...
2+
3+
......#.####.#...#..#..#.##.#...####.##..#..#..##.##........#..#####.##......#..#..##.##..#######.##
4+
.###.##..#.########..###...#.##.#.#.#.##.#....##........#..#.#...#..#.##.##.######.##..#.#.###.###.#
5+
.####.....##.##..#..#.#...#.###.#.###.#..##....####.#####.#..#.#.##.##.#..#..##..#.####.####....####
6+
#.####.....#.###..#.##...##..#......#.#....#.####....#..##.........##.##########.##...##....#######.
7+
.#####..##...#.####..####...#.#...###....#..##.##..#####.#..#......#..###.####.###.##......#....##.#
8+
####...#.##...##..#..####.###.###.....#.##.##....#...#.###........#.#.#....#..#.###...##...#..#.##.#
9+
###..##..###.#..#..#..#.###.##.#..###.#.#.....###.##......#..##.#...#.##..#.#########.......##....#.
10+
#...#.#..##.#.###.#######.#...####.##.#.##.###.#.###..##.#.####..####...##.##.........#...#..#.#.#..
11+
..#...##.#..#.##..#...#..#####.###..#.#..#..#..##.#.##.##.#####...######.#..#.###....##..####..##...
12+
.###.####.#.#.....#.#...##.#....##.....###.###.########..##..###....######.##..##.###..#.......##...
13+
....##.#.###..#.##.#.#.##...##.###..##.#..#...###.#...#...#..##.#....####...##.#.#..#..##.....#....#
14+
#.##..##..##..#.##.....##.###......##..####..#..#..#..#.###.##..##.#..#.#...#.#.###.##.....#.#.#...#
15+
...#.####.#.#.##...####.#.##..#.#.####..#.#...##..##..##....#.#.###...##...#.#.....##....##.#.#.###.
16+
#.###...#.....##.#..##...#....##...###.##.#.##..#...#..####...####.##.#.######....#.###..#.##.##...#
17+
..#..#..#..##.######.#..##.#####....##..##..##..##.#..####.##.##.#.###...#.###.####.#.....#.#....##.
18+
...##.#.###....##..#######...#.......##..#.###.#...##.######...##.##...####..#.#.###.#...##.#.#.....
19+
...##.#.....##.###....#..######..##.#...##.#....#####.........###..#.###.#.###.#......####........#.
20+
#######...#####..#.#.###....###.#..#.#..##.#.#.##..#.#.##.#.#####.#.#.#########.##.##.#...#.##.###..
21+
#.###.#.#.....#..###..#.#....###..#...#..####.#.....#..###..######.#.##.##.#.....#.#.#..###.###.....
22+
#..##.#..#......#.#.#...#####.#...##..#.###.####..#...##.##..##...###..###....#.##......#....##..#..
23+
#....##..#.##......###..##.#..#.#..##...#.##.##.#####.###.####.##.##.###...#..##.##.....#..#####....
24+
##...#.#..###.#.##...##.#..#.#.......########.###.#.#....##.#..####..#.##.#.##.#.#..##.##..##...#...
25+
#.###.##.##.#.#.##.###...##.##...##...####...###...#...##.###.###.##...#...##...#.#.####.#..##.##.##
26+
##.#.##.#....#.#...###...###.##....#..##.######.###.....#..##..#..#.#.##.....###...##...#.####.#.#..
27+
##..#...#..####.#.#..##..#...###...##.###.#..####...###..#..##....#####..##..#.##..####....##.####..
28+
####..####..#..#.#.#..##.########......#..#.##.###.###.######........##.###.##.####...###.#####..###
29+
##.#..#.##.#.....#........#.#.#..#.####.###..#.#..#.##...#.##.#.##.......##..####.#...........##...#
30+
#.###.....##..#.#..####.####.#....#..#####..#..#...#..#..#.....####.#.#####.#.###...####.#...####...
31+
#...##..#.##...#.#####.#.###.#######..#####..#..####..##.##.....###.#.####....#.#....#####...###.###
32+
.##...#..###..#..###.#.######....#..#..#.##.###........#..##...#..#...#.#.#...###....#####...#.#####
33+
#.#...####...###..#.#.##...##.###..####..##.###.#.#.#####...#...##.###..#.##..###....#.##....####.##
34+
####.####..#..#..##.#.#..##..#..##....#.#.##.##..#.#.##.....#.#.#...###..####..##...###.#####..#..##
35+
.##.##..#..#.##...####..##.....#....##.######.#..#..#..###.###..#...#.#....##....######....#..###.##
36+
#.##..#.#.....###..##.###.###.####.#.#.##.#....##...#..#.###....##.......#..###...#.###.#..#..##...#
37+
#..#.#####.####.##.#.#...#..##.##....#...#....##.#.##..#...##.##....##.#.##..#...###...##.#.##..###.
38+
.#..####..###.#.##.##.##......#.##..##..###.#####.####.#.#..#######.##..###...###....#..#..#........
39+
###..#.##..###..#.####.####.##.#..#####.##.#...##.#....#..#####...##.....#..#.#########..##..#.#..#.
40+
##.#.#.##.......##.#####....##...#....####.....###.##.......##...#.#..#.#.##.###..###...#.##..##.###
41+
#..#.##.#.##..#.###.....#....#.##..#....##.##..#.####.#####......#.#.#..#.....##..#..#.##.##.#...#.#
42+
#...#.#.##....#.#.#.###.#.#....#..#.###.##...#...#..##.#..#...##...#.#.#####..##..#####.###...##..##
43+
.#.#####..#..##.##...##.#..#.###.#.###.##.#...##..#..##.###.##....#####.#.##.....###.#.##...##..##.#
44+
..#..####..#..#...#....#.##..##.#..#..#.#..#.#..#....####.#.....##.#.#####..#..#..######....#..#.#..
45+
.#.###.#.#.##.#..#.###.#....##.....##..####...###.#.####...#.#.#..#.#.#..#.####.##..##.#..#.#..##..#
46+
.###.###..##.##.##.##..#.######.####.####.....#..###......#.#........#.##.####.#..##....#.#..#..####
47+
.##########..##..##.##.####.##.....#.#.#....##.###.#...#####..#.#..#.###.#.#...#.#....#######.....#.
48+
#####..#.#...##...#....###...##..#..#..#.#.##.#.#.##...#.....##.#..#..####.#####....#......#.......#
49+
#.##...#.##..#######..#.#####.#.##.##..#######.###.#..#.#..#.#.#.#...##.##.##..#....###...####.#...#
50+
...##.#.###...##.##..###..##.####.#...#.....#..##.##.....#....#####.#..##.####.#######....###.##.#..
51+
..##.#.....###..###.#.######.####.#####.##.#.####..###..#....#..##....#..#.#.#.....#...#..##.#..###.
52+
#.#...###.#.#...##..##.#..##...#####.##...#####..####.#..#..#...###..#...#....#####.###.###.#...#.#.
53+
..##...#..#.####..#...#..#.#..#.##...#...####....##.##.##.#.#..##.##....#.#..####..#..#.##..##..##.#
54+
.##.....##.#.#...#....##..##....##...##........#..###.#...##..##..######...##...##.#####....####..##
55+
.####...#..##.....####..#.#..#..####.##....#.#.#####..##...#....#..##.......#..#.....##.##.##..#.##.
56+
##.#.##.#.#...#.#.##.#.####..#..#####.#.#..#.#.##...###..#...#.#.#.#..##.#..##.....##..#.###.#...#.#
57+
#.#.#.#..#....##...#...##.#.###.#####.#..###.##.####.#.#.###...##.##.#..#.....#.#...####.##.#..##...
58+
#.......##.#####..#.#####.######.#....##..###.#.#.#.##..####..#.#..#....#.##......#......#.#....###.
59+
.##.#.#.....#....##.#.#.#.###.....#....##.#..#.##.#....#.###...##...##...##..##...##.#.#.#.#..#.##..
60+
###.#...#.#.##.......#..###.#.##..#..#.##..#...##..#......#....#.#.##....#....##...###.#.###..###..#
61+
.#..#..##...###.###.##...###.#.....#.##....##..#.###..#####.....#..####.###.##.#.#.########..##.#...
62+
.#....#..####..##.#.##.....#.#.#.##.####.###.##.##..###..#....###.###.#.....#....#..#.#..#####.#..##
63+
..#..#..####..##.###..#.##.....#..#..#.#..###..###...#..##...#.....#.###...#..#.##..#.#.#...##..###.
64+
..#.#.......#..##......#...##..##.##..#.#..#.##...#.#..#.##.#..#.#...###.#......#..#....#.##.##.##..
65+
##.##.#.#.##..#...#######.#..###...#.#.#.#.###.#....#..#.....#...#.#.###..#..###.##..#.##.##..#..#..
66+
###.#....#..##....#...#...###...##.##.#.#.#.##..###......#....##..#.....########...#.#.#.####.#....#
67+
####.##.####..##...#....#.#.#####.#####.#.##..###...#.##.#.###.#.##...#..##.###....#.######..##.##.#
68+
.#......#..##....##...###...##.##.......#.#.......##....#.....#.......#...#.##..##....##...####..##.
69+
.#.###.#.###.##..##..#.#######.##.#....##.#..###...####.##.#.....#..###...#....####.#####.##..##.###
70+
#....#######.####.###...#####.###..#.#.....#.#.#......###.....#...#.#..###.#.##...#..#.##..#.#..#.##
71+
.#..##.#..#.##..#..###..###.##...###.###..####...#####.###..#..####.#.#.####...###.####.#.####.##..#
72+
#..###..##.#####.....#..####.##.#.##....##....###..#####.#.###....###..#.#..##.#....##.###.####..##.
73+
.###.##.#.##.#.#..#.####.#...###...##..#.#.#.#..#..#.#.#..####.....##...#.#.#....#.##.###......#.##.
74+
#...#...##...##.##..###..########.#.####.##..#..##.#.##...###..##.#.##.#.#...#####.#####...###.##...
75+
.....#..#..##....####....#....#..###.##..#.#..####.###.....##..######.###..########.##.#.##..#.....#
76+
..###.##.##.####........#....###.#.###.#.###..###.#####..###.#...#..#.####.#####.########...##.##.#.
77+
.#.#.#.####.#.###..#..#####.##.#.#.####.#.##.#..###.##.#.#....#..##..##.#####.###...#..#.###....#.#.
78+
###.#.#.#.#..#####.#.###...#..##.......#.##.#...#####.######......###..###.#....##..#...##.#.#...#..
79+
..##.#.##.##.....###.....#.#.#.##...##..###.#.#.....###..##.#####.#.#..#.##..#..####.###...#.##.#...
80+
#.###..#.....###.##......####.#......#.#..#.##.##.#..###.#.####.....#.#...#...#..#.##.#.....##.#....
81+
.##.#.##....#..##.#.#...#.####.##.##.#.##.#..#.###.###.#.#...####.#.###....#.#.#..##..#.#..##....##.
82+
..##..#.#..#.##.#..###...#.#.#..#.....##...##.#####.######..####.#...##.##..#.##.#..#.##...###.##.#.
83+
..##.#..#.#......##..#.####..#...##.#.#..#.#.#.##.#.#......#####.##.##.####.##...#...#.#.##.#..##.##
84+
#..#.#.#.####.#.#.#.##.....#.#..#.#..####.#..#.####..##..#.##...#.#####..###....#...#...#....##.##..
85+
..#.......#.##.....##..########.##.#..#.....##...#..#..#...##..#....#...#..#......#..#####..#.##..#.
86+
..#.##..#...#..#..#.##..#...##.##...#..#..###.#.#.#.##.##.#.#.##.#..#.##..#.#..###.###.#..##....#.##
87+
#..#.#..#.##...#..##...##..##..#######.#...####..##..#.####.###.##.##.#....####.#..##.##..#.#...##..
88+
.##...#.##...####..#.####.#..###..##..####..##..#.##.#####.#####.#.....#..#...#..##.##.##..####.....
89+
##...#.######.#...##..###.#.#.#...##.#.#.#.#.#....##.##.###.#.#.#####.....#.#.##.........##..#######
90+
.#.###.##.#.#..##..#..#.#..###.#.#.#.##.##.##..##.###..#..#..#...#####.###...#..#..#.#....#....#..#.
91+
.##.#...#.###...#.####..##.##.#####.#.####..##....#..###...#..#..#.#.##.....#.##.#.##..........#.#..
92+
####.#.#.#.#.####..#.....#..##.#...##.###.##.#..##..#..###..#...##..#.##..#.####.#..####.....#.#.###
93+
.#.###...#.###..#.#.....#..#......#.#.#.##..#..#####....##..#.#..##########.##.#####...##.###...#...
94+
.#.#.....##.####.#..#####...##.##.....#...#.##.#.####.#.##..#....#....#.#.#.#.##...#....##..##.##..#
95+
.#....#.#.##..#.##.###...###....##........#.#....#.#.###.##.####.#..#.####.####......#...#.####...#.
96+
#######..##....###.##..###..#..#...#.....#.....#...#..#.#######.#.#...#..#.###..###.#.#...###.....##
97+
##..##..#..##...#.#.#.##.#....#.#.#.#.#..###.##..###.##.#..###..###.#.#.#....#.####..##...#...#####.
98+
.#..#.#..#.#......#..####.###.#.#.##.#.#.####.###...#.##.#.##..##...#.#..#..##.###...#.#.########..#
99+
#.#.########.#..#.#.##.###..##.#.###.#..#....####....##..###..###..#.#.#####..######.####.##..##.#..
100+
###...##.#...#.#.#.#..##.###..###..##..##.#...####..#.#...#..###..#........##.#.##.###..#..###...###
101+
###.#.#########.####.##...###.#...#.#..#.##.#..##..#.##.####...#.###..#...##..##...##.#.####..##..#.
102+
.#...####..#...###....#.....#####.#.######....#.#####.#.##..##.##..#.##.......###.#............#.#.#

resources/day20_sample_data.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..#
2+
3+
#..#.
4+
#....
5+
##..#
6+
..#..
7+
..###

0 commit comments

Comments
 (0)