Skip to content

Commit 9573b57

Browse files
committed
Day 2
1 parent 7346c1f commit 9573b57

File tree

6 files changed

+1178
-1
lines changed

6 files changed

+1178
-1
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ _Warning:_ I write long explanations. So... yeah.
1010

1111
| Day # | Source | Blog Post |
1212
| ----- | ------ | --------- |
13-
| 1 | [source](src/advent_2020_clojure/day01.clj) | [blog](docs/day1.md) |
13+
| 1 | [source](src/advent_2020_clojure/day01.clj) | [blog](docs/day1.md) |
14+
| 2 | [source](src/advent_2020_clojure/day02.clj) | [blog](docs/day2.md) |

docs/day2.md

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Day Two: Password Philosophy
2+
3+
* [Problem statement](https://adventofcode.com/2020/day/2)
4+
* [Solution code](https://github.com/abyala/advent-2020-clojure/blob/master/src/advent_2020_clojure/day02.clj)
5+
6+
---
7+
8+
## Part 1
9+
10+
Today's problem was a pretty straightforward one. We are given an input that
11+
represents password rules and password attempts, and we need to return the number
12+
of passwords that meet their rules.
13+
14+
In part one, we learn that the password rule states the number of times a particular
15+
character must appear within the password itself. The format of each line is
16+
`XX-YY C: SSSSS` where `C` is the test character that must appear between `XX` and
17+
`YY` times (inclusively) within the string `SSSSS`.
18+
19+
I tend to write a `parse-line` in most AoC problems; often I'll usually add a
20+
`parse-input` function too. This time, though, `parse-line` was enough. To do the
21+
data extraction, I used `re-matches`, which is a function that takes in a regex
22+
pattern and the test string. A Clojure regex is just a string preceded by a hash
23+
sign, so `#"\d*"` would be a pattern. `re-matches` returns a sequence of the
24+
entire String and then each group, so that's a great candidate for destructuring.
25+
Since I don't need the entire string, I use an underscore for the first binding.
26+
27+
To finish the `parse-line` function, I parse Integers out of the two values, and
28+
then grab the first character in the third string. Clojure uses Java's `String`
29+
class, but will natively convert it into a list of characters when using a
30+
sequence function. So with a binding strangely being called `letter`, I can just
31+
request `(first letter)` to get the first characger. I could create a map out of
32+
my data, but the problem is simple enough that a vector of `[min max c word]`
33+
seems fine to me.
34+
35+
```clojure
36+
(defn parse-line [line]
37+
(let [[_ min max letter word] (re-matches #"(\d+)-(\d+) (\w): (\w+)" line)]
38+
[(Integer/parseInt min) (Integer/parseInt max) (first letter) word]))
39+
```
40+
41+
Next, I need a function to apply to test the rule against the password. We later
42+
learn that this tobogggan test was actually a phoney sled password test, so hence
43+
my function name. Rather than use the `frequencies` function, which will show up
44+
in a later problem I'm sure, I just filtered each character based on whether or
45+
not it matched character `c`. Note again that a String can be fed into the `map`
46+
function since it gets converted into a character array automatically.
47+
48+
Finally, as we saw in problem 1, the Clojure equivalent of
49+
`if ((min <= matches) && (matches <= max))` is the very clean `(<= min matches max)`.
50+
51+
```clojure
52+
(defn sled-password? [[min max c word]]
53+
(let [matches (->> (filter (partial = c) word)
54+
count)]
55+
(<= min matches max)))
56+
```
57+
58+
Then I need to do the actual logic for my `solve` function, and there's nothing
59+
tricky here. Using a thread-last pipeline, we split the input String by line,
60+
map each String using the `parse-line` function into its vector, filter out the
61+
passwords that pass the rule, and
62+
finally count up the number of matches. For `part1`, I just call this `solve`
63+
function with the input data and the ruleset, in this case `sled-password?`
64+
65+
```clojure
66+
(defn solve [input rule]
67+
(->> (str/split-lines input)
68+
(map parse-line)
69+
(filter rule)
70+
count))
71+
72+
(defn part1 [input] (solve input sled-password?))
73+
```
74+
75+
---
76+
77+
## Part 2
78+
79+
Well how convenient is this -- we have to do the same thing we did in part 1,
80+
but instead we need to apply different logic to see if the password is correct.
81+
The new rule is that within the password String `SSSSS`, the characters at index
82+
`XX` xor `YY` must be character `C`. It's not complicated, but it takes a tiny
83+
bit more work than part 1.
84+
85+
First of all, Clojure doesn't have a function for `xor` for some reason. So, I
86+
guess I'll have to make it myself. My function will apply a function `f` to a
87+
collection, and return true if the function returns `true` for exactly one value.
88+
89+
```clojure
90+
(defn xor [f coll]
91+
(= 1 (count (filter f coll))))
92+
```
93+
94+
So to test the password, we just use another function. The input are the two indexes
95+
`min` and `max`, although the directions state that the rules are 1-indexed while
96+
Clojure is 0-indexed, so we'll need to `dec` the values. We make an anonymous
97+
function to test if `get word n` (returns the nth character in the String)
98+
matches the character `c`. Finally, we use the new `xor` function, passing in
99+
the anonymous function and the sequence of indexes.
100+
101+
One quick note: because I put the `xor` function in a separate namespace called
102+
`utils`, I need to provide access to it. I could type in
103+
`advent-2020-clojure.utils/xor`, but that's silly. I could also `require` the
104+
namespace and map it to `utils`, typing in `utils/xor`. But in this case, I
105+
think `xor` is fundamental enough of a function that I'll leverage `:use` to
106+
add a direct reference of the `xor` function into the current namespace.
107+
108+
109+
```clojure
110+
(ns advent-2020-clojure.day02
111+
(:require [clojure.string :as str])
112+
(:use [advent-2020-clojure.utils :only [xor]]))
113+
114+
(defn toboggan-password? [[min max c word]]
115+
(xor #(= c (get word (dec %)))
116+
[min max]))
117+
```
118+
119+
And this passes my "is the part2 function pretty" standard, since it's all
120+
reuse, baby!
121+
122+
```clojure
123+
(defn part2 [input] (solve input toboggan-password?))
124+
```

0 commit comments

Comments
 (0)