Today's problem was a pretty straightforward one. We are given an input that represents password rules and password attempts, and we need to return the number of passwords that meet their rules.
In part one, we learn that the password rule states the number of times a particular
character must appear within the password itself. The format of each line is
XX-YY C: SSSSS where C is the test character that must appear between XX and
YY times (inclusively) within the string SSSSS.
I tend to write a parse-line in most AoC problems; often I'll usually add a
parse-input function too. This time, though, parse-line was enough. To do the
data extraction, I used re-matches, which is a function that takes in a regex
pattern and the test string. A Clojure regex is just a string preceded by a hash
sign, so #"\d*" would be a pattern. re-matches returns a sequence of the
entire String and then each group, so that's a great candidate for destructuring.
Since I don't need the entire string, I use an underscore for the first binding.
To finish the parse-line function, I parse Integers out of the two values, and
then grab the first character in the third string. Clojure uses Java's String
class, but will natively convert it into a list of characters when using a
sequence function. So with a binding strangely being called letter, I can just
request (first letter) to get the first characger. I could create a map out of
my data, but the problem is simple enough that a vector of [min max c word]
seems fine to me.
(defn parse-line [line]
(let [[_ min max letter word] (re-matches #"(\d+)-(\d+) (\w): (\w+)" line)]
[(Integer/parseInt min) (Integer/parseInt max) (first letter) word]))Next, I need a function to apply to test the rule against the password. We later
learn that this tobogggan test was actually a phoney sled password test, so hence
my function name. Rather than use the frequencies function, which will show up
in a later problem I'm sure, I just filtered each character based on whether or
not it matched character c. Note again that a String can be fed into the map
function since it gets converted into a character array automatically.
Finally, as we saw in problem 1, the Clojure equivalent of
if ((min <= matches) && (matches <= max)) is the very clean (<= min matches max).
(defn sled-password? [[min max c word]]
(let [matches (->> (filter (partial = c) word)
count)]
(<= min matches max)))Then I need to do the actual logic for my solve function, and there's nothing
tricky here. Using a thread-last pipeline, we split the input String by line,
map each String using the parse-line function into its vector, filter out the
passwords that pass the rule, and
finally count up the number of matches. For part1, I just call this solve
function with the input data and the ruleset, in this case sled-password?
(defn solve [input rule]
(->> (str/split-lines input)
(map parse-line)
(filter rule)
count))
(defn part1 [input] (solve input sled-password?))Well how convenient is this -- we have to do the same thing we did in part 1,
but instead we need to apply different logic to see if the password is correct.
The new rule is that within the password String SSSSS, the characters at index
XX xor YY must be character C. It's not complicated, but it takes a tiny
bit more work than part 1.
First of all, Clojure doesn't have a function for xor for some reason. So, I
guess I'll have to make it myself. My function will apply a function f to a
collection, and return true if the function returns true for exactly one value.
(defn xor [f coll]
(= 1 (count (filter f coll))))So to test the password, we just use another function. The input are the two indexes
min and max, although the directions state that the rules are 1-indexed while
Clojure is 0-indexed, so we'll need to dec the values. We make an anonymous
function to test if get word n (returns the nth character in the String)
matches the character c. Finally, we use the new xor function, passing in
the anonymous function and the sequence of indexes.
One quick note: because I put the xor function in a separate namespace called
utils, I need to provide access to it. I could type in
advent-2020-clojure.utils/xor, but that's silly. I could also require the
namespace and map it to utils, typing in utils/xor. But in this case, I
think xor is fundamental enough of a function that I'll leverage :use to
add a direct reference of the xor function into the current namespace.
(ns advent-2020-clojure.day02
(:require [clojure.string :as str])
(:use [advent-2020-clojure.utils :only [xor]]))
(defn toboggan-password? [[min max c word]]
(xor #(= c (get word (dec %)))
[min max]))And this passes my "is the part2 function pretty" standard, since it's all reuse, baby!
(defn part2 [input] (solve input toboggan-password?))