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"))
}