I came across this while doing the 2018 Advent of Code (Day 2, Part 1) solution in Rust.
The problem to solve:
Take the count of strings that have exactly two of the same letter, multiplied by the count of strings that have exactly three of the same letter.
INPUT
abcdega
hihklmh
abqasbb
aaaabcd
- The first string
abcdega
hasa
repeated twice. - The second string
hihklmh
hash
repeated three times. - The third string
abqasbb
hasa
repeated twice, andb
repeated three times, so it counts for both. - The fourth string
aaaabcd
contains a letter repeated4
times (not2
, or3
) so it does not count.
So the result should be:
2
strings that contained a double letter (first and third) multiplied by 2
strings that contained a triple letter (second and third) = 4
The Question:
const PUZZLE_INPUT: &str =
"
abcdega
hihklmh
abqasbb
aaaabcd
";
fn letter_counts(id: &str) -> [u8;26] {
id.chars().map(|c| c as u8).fold([0;26], |mut counts, c| {
counts[usize::from(c - b'a')] += 1;
counts
})
}
fn has_repeated_letter(n: u8, letter_counts: &[u8;26]) -> bool {
letter_counts.iter().any(|&count| count == n)
}
fn main() {
let ids_iter = PUZZLE_INPUT.lines().map(letter_counts);
let num_ids_with_double = ids_iter.clone().filter(|id| has_repeated_letter(2, id)).count();
let num_ids_with_triple = ids_iter.filter(|id| has_repeated_letter(3, id)).count();
println!("{}", num_ids_with_double * num_ids_with_triple);
}
Consider line 21
. The function letter_counts
takes only one argument, so I can use the syntax: .map(letter_counts)
on elements that match the type of the expected argument. This is really nice to me! I love that I don't have to create a closure: .map(|id| letter_counts(id))
. I find both to be readable, but the former version without the closure is much cleaner to me.
Now consider lines 22
and 23
. Here, I have to use the syntax: .filter(|id| has_repeated_letter(3, id))
because the has_repeated_letter
function takes two arguments. I would really like to do .filter(has_repeated_letter(3))
instead.
Sure, I could make the function take a tuple instead, map to a tuple and consume only a single argument... but that seems like a terrible solution. I'd rather just create the closure.
Leaving out the only argument is something that Rust lets you do. Why would it be any harder for the compiler to let you leave out the last argument, provided that it has all of the other n-1
arguments for a function that takes n
arguments.
I feel like this would make the syntax a lot cleaner, and it would fit in a lot better with the idiomatic functional style that Rust prefers.
I am certainly no expert in compilers, but implementing this behavior seems like it would be straightforward. If my thinking is incorrect, I would love to know more about why that is so.