Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,18 @@
"move_semantics"
]
},
{
"slug": "fizzy",
"uuid": "6b209749-d4af-45c2-bbdc-27603ce6979f",
"core": false,
"unlocked_by": "luhn",
"difficulty": 7,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note on positioning: the Rust track currently has three exercises other than fizzy with the "generics" topic. None of them are core exercises. The easiest of these is a difficulty 4, unlocked by "luhn", so I chose to make this a peer exercise.

I don't believe that it's worth worrying too much about positioning, because Rust is currently in the middle of the track reordering project, and we can expect the position of this exercise to shift, possibly substantially.

"topics": [
"generics",
"impl_trait",
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a Rust-specific topic, but I think it's appropriate here.

"iterators"
]
},
{
"slug": "roman-numerals",
"uuid": "498be645-734a-49b7-aba7-aae1e051e1f0",
Expand Down
8 changes: 8 additions & 0 deletions exercises/fizzy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Generated by Cargo
# will have compiled files and executables
/target/
**/*.rs.bk

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
10 changes: 10 additions & 0 deletions exercises/fizzy/.meta/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FizzBuzz is a children's game of counting through the integers. For each of them, if it's divisible by three, substitute the word "fizz"; if divisible by five, substitue "buzz"; if both, say both; if neither, say the number. It is not particularly difficult to implement, though it enjoyed some popularity for a time as [a quick way to tell whether entry-level programming applicants knew how to program _at all_](https://blog.codinghorror.com/why-cant-programmers-program/).

It has since fallen somewhat into disfavor for that task, because applicants began memorizing FizzBuzz implementations instead of learning to program.

We're going to do something more interesting than the basics: your task in this exercise is to implement FizzBuzz:

- with fully-customizable rules about what numbers produce what words
- fully generic on a very restricted minimal trait set
- such that it works just as well for the Collatz Sequence as for steadily increasing numbers
- with convenient helpers to make its use ergonomic
4 changes: 4 additions & 0 deletions exercises/fizzy/.meta/metadata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
blurb: "Implement FizzBuzz using advanced generics"
source: "Peter Goodspeed-Niklaus"
source_url: "https://github.com/coriolinus/fizzy"
7 changes: 7 additions & 0 deletions exercises/fizzy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "fizzy"
version = "0.0.0"
edition = "2018"

[dependencies]

92 changes: 92 additions & 0 deletions exercises/fizzy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Fizzy

FizzBuzz is a children's game of counting through the integers. For each of them, if it's divisible by three, substitute the word "fizz"; if divisible by five, substitue "buzz"; if both, say both; if neither, say the number. It is not particularly difficult to implement, though it enjoyed some popularity for a time as [a quick way to tell whether entry-level programming applicants knew how to program _at all_](https://blog.codinghorror.com/why-cant-programmers-program/).

It has since fallen somewhat into disfavor for that task, because applicants began memorizing FizzBuzz implementations instead of learning to program.

We're going to do something more interesting than the basics: your task in this exercise is to implement FizzBuzz:

- with fully-customizable rules about what numbers produce what words
- fully generic on a very restricted minimal trait set
- such that it works just as well for the Collatz Sequence as for steadily increasing numbers
- with convenient helpers to make its use ergonomic

## Rust Installation

Refer to the [exercism help page][help-page] for Rust installation and learning
resources.

## Writing the Code

Execute the tests with:

```bash
$ cargo test
```

All but the first test have been ignored. After you get the first test to
pass, open the tests source file which is located in the `tests` directory
and remove the `#[ignore]` flag from the next test and get the tests to pass
again. Each separate test is a function with `#[test]` flag above it.
Continue, until you pass every test.

If you wish to run all tests without editing the tests source file, use:

```bash
$ cargo test -- --ignored
```

To run a specific test, for example `some_test`, you can use:

```bash
$ cargo test some_test
```

If the specific test is ignored use:

```bash
$ cargo test some_test -- --ignored
```

To learn more about Rust tests refer to the [online test documentation][rust-tests]

Make sure to read the [Modules](https://doc.rust-lang.org/book/ch07-02-modules-and-use-to-control-scope-and-privacy.html) chapter if you
haven't already, it will help you with organizing your files.

## Further improvements

After you have solved the exercise, please consider using the additional utilities, described in the [installation guide](https://exercism.io/tracks/rust/installation), to further refine your final solution.

To format your solution, inside the solution directory use

```bash
cargo fmt
```

To see, if your solution contains some common ineffective use cases, inside the solution directory use

```bash
cargo clippy --all-targets
```

## Submitting the solution

Generally you should submit all files in which you implemented your solution (`src/lib.rs` in most cases). If you are using any external crates, please consider submitting the `Cargo.toml` file. This will make the review process faster and clearer.

## Feedback, Issues, Pull Requests

The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help!

If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md).

[help-page]: https://exercism.io/tracks/rust/learning
[modules]: https://doc.rust-lang.org/book/ch07-02-modules-and-use-to-control-scope-and-privacy.html
[cargo]: https://doc.rust-lang.org/book/ch14-00-more-about-cargo.html
[rust-tests]: https://doc.rust-lang.org/book/ch11-02-running-tests.html

## Source

Peter Goodspeed-Niklaus [https://github.com/coriolinus/fizzy](https://github.com/coriolinus/fizzy)

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
169 changes: 169 additions & 0 deletions exercises/fizzy/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use std::ops::Rem;

pub type MatchFn<T> = Box<dyn Fn(T) -> bool>;

pub struct Matcher<T> {
matcher: MatchFn<T>,
subs: String,
}

impl<T> Matcher<T> {
pub fn new<F, S>(matcher: F, subs: S) -> Matcher<T>
where
F: Fn(T) -> bool + 'static,
S: AsRef<str>,
{
Matcher {
matcher: Box::new(matcher),
subs: subs.as_ref().to_string(),
}
}
}

#[derive(Default)]
pub struct Fizzy<T>(Vec<Matcher<T>>);

impl<T> Fizzy<T>
where
T: Copy + ToString,
{
pub fn new() -> Self {
Fizzy(Vec::new())
}

pub fn add_matcher(mut self, matcher: Matcher<T>) -> Self {
let Fizzy(ref mut matchers) = self;
matchers.push(matcher);
self
}

pub fn apply_to(&self, item: T) -> String {
let Fizzy(ref matchers) = self;
let mut out = String::new();
for matcher in matchers {
if (matcher.matcher)(item) {
out += &matcher.subs;
}
}
if out.is_empty() {
out = item.to_string()
}
out
}

/// convenience function: equivalent to `iter.map(move |item| self.apply_to(item))`.
pub fn apply<I>(self, iter: I) -> impl Iterator<Item = String>
where
I: Iterator<Item = T>,
{
iter.map(move |item| self.apply_to(item))
}
}

impl<T> From<Vec<Matcher<T>>> for Fizzy<T> {
fn from(matchers: Vec<Matcher<T>>) -> Fizzy<T> {
Fizzy(matchers)
}
}

pub fn fizz_buzz<T>() -> Fizzy<T>
where
T: Copy + Default + From<u8> + PartialEq + Rem<Output = T> + 'static,
{
let three: T = 3.into();
let five: T = 5.into();

Fizzy(vec![
Matcher::new(move |n| n % three == T::default(), "fizz"),
Matcher::new(move |n| n % five == T::default(), "buzz"),
])
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_fizz_buzz() {
let expect = vec![
"1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13",
"14", "fizzbuzz", "16",
];
let got = fizz_buzz().apply(1..=16).collect::<Vec<_>>();
assert_eq!(expect, got);
}

#[test]
fn test_fizz_buzz_u8() {
let expect = vec![
"1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13",
"14", "fizzbuzz", "16",
];
let got = fizz_buzz().apply(1_u8..=16).collect::<Vec<_>>();
assert_eq!(expect, got);
}

#[test]
fn test_fizz_buzz_u64() {
let expect = vec![
"1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13",
"14", "fizzbuzz", "16",
];
let got = fizz_buzz().apply(1_u64..=16).collect::<Vec<_>>();
assert_eq!(expect, got);
}

#[test]
fn test_fizz_buzz_nonsequential() {
let collatz_12 = &[12, 6, 3, 10, 5, 16, 8, 4, 2, 1];
let expect = vec![
"fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1",
];
let got = fizz_buzz()
.apply(collatz_12.into_iter().cloned())
.collect::<Vec<_>>();
assert_eq!(expect, got);
}

#[test]
fn test_fizz_buzz_custom() {
let expect = vec![
"1", "2", "Fizz", "4", "Buzz", "Fizz", "Bam", "8", "Fizz", "Buzz", "11", "Fizz", "13",
"Bam", "BuzzFizz", "16",
];
let fizzer = Fizzy::new()
.add_matcher(Matcher::new(|n| n % 5 == 0, "Buzz"))
.add_matcher(Matcher::new(|n| n % 3 == 0, "Fizz"))
.add_matcher(Matcher::new(|n| n % 7 == 0, "Bam"))
.apply(1..=16);
let got = fizzer.collect::<Vec<_>>();
assert_eq!(expect, got);
}

#[test]
fn test_map() {
let expect = vec![
"1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13",
"14", "fizzbuzz", "16",
];
let fb = fizz_buzz();
let got = (1..=16)
.map(move |item| fb.apply_to(item))
.collect::<Vec<_>>();
assert_eq!(expect, got);
}

#[test]
fn test_fizz_buzz_f64() {
let expect = vec![
"1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13",
"14", "fizzbuzz", "16",
];
// a tiny bit more complicated because range isn't natively implemented on floats
let got = fizz_buzz()
.apply(std::iter::successors(Some(1.0), |prev| Some(prev + 1.0)))
.take(16)
.collect::<Vec<_>>();
assert_eq!(expect, got);
}
}
46 changes: 46 additions & 0 deletions exercises/fizzy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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>(std::marker::PhantomData<T>);

impl<T> Matcher<T> {
pub fn new<F, S>(_matcher: F, _subs: S) -> Matcher<T> {
unimplemented!()
}
}

/// 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>(std::marker::PhantomData<T>);

impl<T> Fizzy<T> {
pub fn new() -> Self {
unimplemented!()
}

// feel free to change the signature to `mut self` if you like
pub fn add_matcher(self, _matcher: Matcher<T>) -> Self {
unimplemented!()
}

/// map this fizzy onto every element of an interator, returning a new iterator
pub fn apply<I>(self, _iter: I) -> impl Iterator<Item = String> {
// unimplemented!() doesn't actually work, here; () is not an Iterator
// that said, this is probably not the actual implementation you desire
Vec::new().into_iter()
}
}

/// convenience function: return a Fizzy which applies the standard fizz-buzz rules
pub fn fizz_buzz<T>() -> Fizzy<T> {
unimplemented!()
}
Loading