|
| 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