1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
// the PhantomData instances in this file are just to stop compiler complaints
// about missing generics; feel free to remove them
/// A Matcher is a single rule of fizzbuzz: given a function on T, should
/// a word be substituted in? If yes, which word?
pub struct Matcher<T> {
function: Box<dyn Fn(T) -> bool>,
word: String
}
impl<T> Matcher<T> {
pub fn new<F: Fn(T) -> bool, S: ToString>(_matcher: F, _subs: S) -> Matcher<T>
where
F: Fn(T) -> bool + 'static,
S: ToString
{
Self {
function: Box::new(_matcher),
word: _subs.to_string()
}
}
}
/// A Fizzy is a set of matchers, which may be applied to an iterator.
///
/// Strictly speaking, it's usually more idiomatic to use `iter.map()` than to
/// consume an iterator with an `apply` method. Given a Fizzy instance, it's
/// pretty straightforward to construct a closure which applies it to all
/// elements of the iterator. However, we're using the `apply` pattern
/// here because it's a simpler interface for students to implement.
///
/// Also, it's a good excuse to try out using impl trait.
pub struct Fizzy<T> {
matchers: Vec<Matcher<T>>
}
impl<T> Fizzy<T> where T: ToString + Copy {
pub fn new() -> Self {
Self { matchers: vec![] }
}
// feel free to change the signature to `mut self` if you like
#[must_use]
pub fn add_matcher(mut self, _matcher: Matcher<T>) -> Self {
self.matchers.push(_matcher);
self
}
/// map this fizzy onto every element of an iterator, returning a new iterator
pub fn apply<I>(self, _iter: I) -> impl Iterator<Item = String>
where
I: Iterator<Item = T>
{
_iter.map(move |elem| {
let mut matched = false;
let mut result = String::new();
for matcher in self.matchers.iter() {
if (matcher.function)(elem) {
result.push_str(&matcher.word);
matched = true;
}
}
if matched {
result
} else {
elem.to_string()
}
})
}
}
/// convenience function: return a Fizzy which applies the standard fizz-buzz rules
pub fn fizz_buzz<T: ToString + Copy>() -> Fizzy<T>
where
T: From<u8> + std::ops::Rem<Output = T> + PartialEq
{
Fizzy::new()
.add_matcher(Matcher::new(|n: T| n % 3.into() == 0.into(), "fizz"))
.add_matcher(Matcher::new(|n: T| n % 5.into() == 0.into(), "buzz"))
}