Skip to content

Commit 53ed3ec

Browse files
committed
Day 5 docs
1 parent c350de6 commit 53ed3ec

File tree

4 files changed

+143
-33
lines changed

4 files changed

+143
-33
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ _Warning:_ I write long explanations. So... yeah.
1313
| 1 | [source](src/advent_2020_clojure/day01.clj) | [blog](docs/day01.md) |
1414
| 2 | [source](src/advent_2020_clojure/day02.clj) | [blog](docs/day02.md) |
1515
| 3 | [source](src/advent_2020_clojure/day03.clj) | [blog](docs/day03.md) |
16-
| 4 | [source](src/advent_2020_clojure/day04.clj) | [blog](docs/day04.md) |
16+
| 4 | [source](src/advent_2020_clojure/day04.clj) | [blog](docs/day04.md) |
17+
| 5 | [source](src/advent_2020_clojure/day05.clj) | [blog](docs/day05.md) |

docs/day05.md

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Day Five:
2+
3+
* [Problem statement](https://adventofcode.com/2020/day/5)
4+
* [Solution code](https://github.com/abyala/advent-2020-clojure/blob/master/src/advent_2020_clojure/day05.clj)
5+
6+
---
7+
8+
## Part 1
9+
10+
I rather liked today's puzzle - simple problem, no tricky surprises. Also, I think I've already
11+
solved it four or five ways, so I like the variety of options!
12+
13+
We're looking for our seat on a plane, given instructions on how to get the row and column based on
14+
an input String. Of the 128 rows (0-127) and 8 columns (0-7), we split the rows in half and go to
15+
the front half (0-63) if the first character is `F` or the back half (64-127) if the first character
16+
is `B`. Then repeat for a total of 7 times until we find the right row. Then do the same for the
17+
last 3 characters using `L` for low and `R` for high to find the column. Then the seat ID is
18+
the row * 8 plus the column.
19+
20+
So first, let's split each 10-character seat assignment into two strings - the first 7 and the last 3.
21+
We could manually create a vector using `[(take 7 seat) (drop 7 seat)]`, but `(split-at 7 seat)`
22+
does the work for us. This will create a vector with two char sequences.
23+
24+
```clojure
25+
(defn split-seat [seat] (split-at 7 seat))
26+
```
27+
28+
Next, we need to figure out how to find the correct row or column using the so-called "binary
29+
space partition" algorithm. The easiest solution I could find involves treating the front and back
30+
portions of the seat as binary numbers - if we convert `F` and `L` into `0`, and `B` and `R` into `1`,
31+
then these Strings convert beautifully. From the test cases, `FBFBBFF` should resolve to row 44,
32+
which is `0101100`. Similarly, `RLR` is 5, which in binary is `101`.
33+
34+
Let's write a little helper function that converts a character into either a zero or one. Once
35+
again, we can use `(if (a-set test))` as a boolean expression to see if `test` is a member of the
36+
set `a-set`.
37+
38+
```clojure
39+
(defn to-binary-digit [c] (if (#{\F \L} c) 0 1))
40+
```
41+
42+
Now we have to do a little string manipulation to convert a sequence of characters into its integer
43+
value. We again use `as->` since we'll be mixing the collection operations `map` and `apply`, which
44+
use thread-last, with a non-collection operation that uses thread-first. The `binary-space-partition`
45+
function maps each digit to its binary form, then concatenates the sequence into one big string of
46+
zeros and ones, and then leverages Java's `Integer.parseInt` method with a radix of `2` to
47+
convert the String as binary into an integer.
48+
49+
```clojure
50+
(defn binary-space-partition [instructions]
51+
(as-> instructions x
52+
(map to-binary-digit x)
53+
(apply str x)
54+
(Integer/parseInt x 2)))
55+
```
56+
57+
Calculating a seat ID is now a fairly trivial task. We split the 10-digit string into the front
58+
and back halfs using `split-seat`, and our `let` statement immediately destructures this vector
59+
into two variables `[r c]` for row and column. Then we multiply the binary form of the row by 8,
60+
and add it to the binary form of the column.
61+
62+
```clojure
63+
(defn seat-id [seat]
64+
(let [[r c] (split-seat seat)]
65+
(-> (* (binary-space-partition r) 8)
66+
(+ (binary-space-partition c)))))
67+
```
68+
69+
Finally, to finish part 1, we need to find the largest seat ID. A little threading gets us the
70+
answer.
71+
72+
```clojure
73+
(defn part1 [input]
74+
(->> (str/split-lines input)
75+
(map seat-id)
76+
(apply max)))
77+
```
78+
79+
---
80+
81+
## Part 2
82+
83+
For part 2, we need to find which seat ID is not filled. The instructions say that the seat IDs
84+
from the input make a contiguous list, but there's one seat missing. It won't be the lowest or
85+
highest value, so there's one value in the list such that the seat ID one higher does not exist.
86+
87+
Again, there are lots of ways of doing this, but I think this is a good case for the `reduce`
88+
function. We'll make a function `missing-within-collection`, which takes a collection of numeric
89+
values and finds the first missing value. The input to `reduce` will be `(sort coll)`, so we
90+
can read through the list once instead of treating it as a set. Then the function to apply should
91+
look at the previous value calculated and the next value in the list. If `next` is one greater
92+
than `previous`, then we haven't found it yet, so the reducing function should return `next`.
93+
However, if `next` is not one greater than `previous`, then we can tell the `reduce` function that
94+
we've found our answer and it can stop doing its calculations. The `reduced` function accomplishes
95+
just that. This is `reduce`'s equivalent of applying a `break` within a loop, or an early `return`
96+
within a method.
97+
98+
```clojure
99+
(defn missing-within-collection [coll]
100+
(reduce (fn [prev v]
101+
(let [target (inc prev)]
102+
(if (= v target)
103+
v ; This isn't the answer. Keep reducing.
104+
(reduced target)))) ; We found it! Short-circuit with the value we didn't find.
105+
(sort coll)))
106+
```
107+
108+
And then, of course, I always want my `part1` and `part2` functions to be simple if at all possible.
109+
Both functions require reading the input data, converting each value into its seat ID, and then
110+
doing something to it. So we'll pull out most of the logic from the old `part1` into a new function
111+
called `apply-to-seat-ids`, which takes in the input data and the function to apply to collection
112+
of seat IDs. This should wrap everything up cleanly.
113+
114+
```clojure
115+
(defn apply-to-seat-ids [input f]
116+
(->> (str/split-lines input)
117+
(map seat-id)
118+
f))
119+
120+
(defn part1 [input]
121+
(apply-to-seat-ids input (partial apply max)))
122+
123+
(defn part2 [input]
124+
(apply-to-seat-ids input missing-within-collection))
125+
```

src/advent_2020_clojure/day05.clj

+8-19
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,18 @@
22
(:require [clojure.string :as str]))
33

44
(defn split-seat [seat] (split-at 7 seat))
5-
(defn low-instruction? [c] (#{\F \L} c))
5+
(defn to-binary-digit [c] (if (#{\F \L} c) 0 1))
66

7-
(defn midpoint [low high]
8-
(-> (- high low) (/ 2) (+ low)))
9-
10-
(defn binary-space-partition [num-vals instructions]
11-
(->> (reduce (fn [[low high] dir]
12-
(let [midpoint (midpoint low high)]
13-
(if (low-instruction? dir) [low midpoint] [midpoint high])))
14-
[0 num-vals]
15-
instructions)
16-
first))
17-
18-
(defn find-row [instructions]
19-
(binary-space-partition 128 instructions))
20-
21-
(defn find-column [instructions]
22-
(binary-space-partition 8 instructions))
7+
(defn binary-space-partition [instructions]
8+
(as-> instructions x
9+
(map to-binary-digit x)
10+
(apply str x)
11+
(Integer/parseInt x 2)))
2312

2413
(defn seat-id [seat]
2514
(let [[r c] (split-seat seat)]
26-
(-> (* (find-row r) 8)
27-
(+ (find-column c)))))
15+
(-> (* (binary-space-partition r) 8)
16+
(+ (binary-space-partition c)))))
2817

2918
(defn missing-within-collection [coll]
3019
(reduce (fn [prev v]

test/advent_2020_clojure/day05_test.clj

+8-13
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@
44

55
(def PUZZLE_DATA (slurp "resources/day05_data.txt"))
66

7-
(deftest midpoint-test
8-
(is (= 64 (midpoint 0 128)))
9-
(is (= 32 (midpoint 0 64)))
10-
(is (= 96 (midpoint 64 128))))
11-
12-
(deftest find-row-test
13-
(is (= 70 (find-row "BFFFBBF")))
14-
(is (= 14 (find-row "FFFBBBF")))
15-
(is (= 102 (find-row "BBFFBBF"))))
16-
17-
(deftest find-column-test
18-
(is (= 7 (find-column "RRR")))
19-
(is (= 4 (find-column "RLL"))))
7+
(deftest binary-space-partition-test
8+
(testing "Row examples"
9+
(is (= 70 (binary-space-partition "BFFFBBF")))
10+
(is (= 14 (binary-space-partition "FFFBBBF")))
11+
(is (= 102 (binary-space-partition "BBFFBBF"))))
12+
(testing "Column examples"
13+
(is (= 7 (binary-space-partition "RRR")))
14+
(is (= 4 (binary-space-partition "RLL")))))
2015

2116
(deftest seat-id-test
2217
(is (= 567 (seat-id "BFFFBBFRRR")))

0 commit comments

Comments
 (0)