diff --git a/config.json b/config.json index ddd2d106a..51ff5a0f7 100644 --- a/config.json +++ b/config.json @@ -263,6 +263,16 @@ "stack or recursion" ] }, + { + "slug": "luhn-from", + "difficulty": 4, + "topics": [ + "from trait", + "str to digits", + "iterators", + "higher-order functions" + ] + }, { "slug": "queen-attack", "difficulty": 4, @@ -298,6 +308,16 @@ "Default Trait implementation" ] }, + { + "slug": "luhn-trait", + "difficulty": 4, + "topics": [ + "Custom Trait", + "str to digits", + "iterators", + "higher-order functions" + ] + }, { "slug": "allergies", "difficulty": 4, diff --git a/exercises/luhn-from/.gitignore b/exercises/luhn-from/.gitignore new file mode 100644 index 000000000..0e49cdd58 --- /dev/null +++ b/exercises/luhn-from/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# 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 \ No newline at end of file diff --git a/exercises/luhn-from/Cargo.toml b/exercises/luhn-from/Cargo.toml new file mode 100644 index 000000000..8fdafb024 --- /dev/null +++ b/exercises/luhn-from/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "luhn-from" +version = "0.0.0" diff --git a/exercises/luhn-from/HINTS.md b/exercises/luhn-from/HINTS.md new file mode 100644 index 000000000..1bf1850e5 --- /dev/null +++ b/exercises/luhn-from/HINTS.md @@ -0,0 +1,9 @@ +# Luhn: Using the From Trait + +Before doing this exercise you should probably do the original Luhn exercise. If you have not completed Luhn, you can get it by running the command: + +> `exercism fetch rust luhn` + +In the original Luhn exercise you only validated strings, but the Luhn algorithm can be applied to integers as well. + +In this exercise you'll implement the [From trait](https://doc.rust-lang.org/std/convert/trait.From.html) to convert strings, strs and unsigned integers into a Struct that performs the validation. diff --git a/exercises/luhn-from/example.rs b/exercises/luhn-from/example.rs new file mode 100644 index 000000000..22debd7c6 --- /dev/null +++ b/exercises/luhn-from/example.rs @@ -0,0 +1,62 @@ +pub struct Luhn { + digits: Vec, +} + +impl Luhn { + pub fn is_valid(&self) -> bool { + if self.digits.iter().any(|c| c.is_alphabetic()) || self.digits.iter().count() == 1 { + return false; + } + + self.digits + .iter() + .filter_map(|c| c.to_digit(10)) + .rev() + .enumerate() + .map(|(index, digit)| if index % 2 == 0 { digit } else { digit * 2 }) + .map(|digit| if digit > 9 { digit - 9 } else { digit }) + .sum::() % 10 == 0 + } +} + +impl From for Luhn { + fn from(s: String) -> Self { + Luhn { digits: s.chars().collect() } + } +} + +impl<'a> From<&'a str> for Luhn { + fn from(s: &'a str) -> Self { + Luhn::from(String::from(s)) + } +} + +impl From for Luhn { + fn from(s: u8) -> Self { + Luhn::from(s.to_string()) + } +} + +impl From for Luhn { + fn from(s: u16) -> Self { + Luhn::from(s.to_string()) + } +} + +impl From for Luhn { + fn from(s: u32) -> Self { + Luhn::from(s.to_string()) + } +} + +impl From for Luhn { + fn from(s: u64) -> Self { + Luhn::from(s.to_string()) + } +} + +impl From for Luhn { + fn from(s: usize) -> Self { + Luhn::from(s.to_string()) + } +} diff --git a/exercises/luhn-from/tests/luhn-from.rs b/exercises/luhn-from/tests/luhn-from.rs new file mode 100644 index 000000000..1f7db93bd --- /dev/null +++ b/exercises/luhn-from/tests/luhn-from.rs @@ -0,0 +1,101 @@ +extern crate luhn_from; + +use luhn_from::*; + +#[test] +fn you_can_validate_from_a_str() { + let valid = Luhn::from("046 454 286"); + let invalid = Luhn::from("046 454 287"); + assert!(valid.is_valid()); + assert!(!invalid.is_valid()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_string() { + let valid = Luhn::from(String::from("046 454 286")); + let invalid = Luhn::from(String::from("046 454 287")); + assert!(valid.is_valid()); + assert!(!invalid.is_valid()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_u8() { + let valid = Luhn::from(240u8); + let invalid = Luhn::from(241u8); + assert!(valid.is_valid()); + assert!(!invalid.is_valid()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_u16() { + let valid = Luhn::from(64_436u16); + let invalid = Luhn::from(64_437u16); + assert!(valid.is_valid()); + assert!(!invalid.is_valid()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_u32() { + let valid = Luhn::from(46_454_286u32); + let invalid = Luhn::from(46_454_287u32); + assert!(valid.is_valid()); + assert!(!invalid.is_valid()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_u64() { + let valid = Luhn::from(8273_1232_7352_0562u64); + let invalid = Luhn::from(8273_1232_7352_0569u64); + assert!(valid.is_valid()); + assert!(!invalid.is_valid()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_usize() { + let valid = Luhn::from(8273_1232_7352_0562usize); + let invalid = Luhn::from(8273_1232_7352_0569usize); + assert!(valid.is_valid()); + assert!(!invalid.is_valid()); +} + +#[test] +#[ignore] +fn single_digit_string_is_invalid() { + assert!(!Luhn::from("1").is_valid()); +} + +#[test] +#[ignore] +fn single_zero_string_is_invalid() { + assert!(!Luhn::from("0").is_valid()); +} + +#[test] +#[ignore] +fn valid_canadian_sin_is_valid() { + assert!(Luhn::from("046 454 286").is_valid()); +} + +#[test] +#[ignore] +fn invalid_canadian_sin_is_invalid() { + assert!(!Luhn::from("046 454 287").is_valid()); +} + +#[test] +#[ignore] +fn invalid_credit_card_is_invalid() { + assert!(!Luhn::from("8273 1232 7352 0569").is_valid()); +} + +#[test] +#[ignore] +fn strings_that_contain_non_digits_are_invalid() { + assert!(!Luhn::from("046a 454 286").is_valid()); +} diff --git a/exercises/luhn-trait/.gitignore b/exercises/luhn-trait/.gitignore new file mode 100644 index 000000000..0e49cdd58 --- /dev/null +++ b/exercises/luhn-trait/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# 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 \ No newline at end of file diff --git a/exercises/luhn-trait/Cargo.toml b/exercises/luhn-trait/Cargo.toml new file mode 100644 index 000000000..e0bd9df67 --- /dev/null +++ b/exercises/luhn-trait/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "luhn-trait" +version = "0.0.0" diff --git a/exercises/luhn-trait/HINTS.md b/exercises/luhn-trait/HINTS.md new file mode 100644 index 000000000..fd9ce2b3c --- /dev/null +++ b/exercises/luhn-trait/HINTS.md @@ -0,0 +1,17 @@ +# Luhn: Using a Custom Trait + +Before doing this exercise you should probably do the original Luhn exercise and its successor, "Luhn: Using the From Trait" + +To get the original Luhn exercise, run `exercism fetch rust luhn` + +To get the "Luhn: Using the From Trait" exercise, run `exercism fetch rust luhn-from` + +In the original Luhn exercise you only validated strings, but the Luhn algorithm can be applied to integers as well. + +In "Luhn: Using the From Trait" you implemented a From trait, which also required you to create a Luhn struct. + +Instead of creating a Struct just to perform the validation, what if you you validated the primitives (i.e, String, u8, etc.) themselves? + +In this exercise you'll create and implement a custom [trait](https://doc.rust-lang.org/book/traits.html) that performs the validation. + +Note: It is [not idiomatic Rust to implement traits on on primitives](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits). In this exercise we're showing something that you _can_ do, not something you _should_ do. If you find yourself implementing traits on primitives, perhaps you have a case of [Primitive Obsession](http://wiki.c2.com/?PrimitiveObsession). diff --git a/exercises/luhn-trait/example.rs b/exercises/luhn-trait/example.rs new file mode 100644 index 000000000..b703231ff --- /dev/null +++ b/exercises/luhn-trait/example.rs @@ -0,0 +1,55 @@ +pub trait Luhn { + fn valid_luhn(&self) -> bool; +} + +impl Luhn for String { + fn valid_luhn(&self) -> bool { + if self.chars().any(|c| c.is_alphabetic()) || self.chars().count() == 1 { + return false; + } + + self.chars() + .filter_map(|c| c.to_digit(10)) + .rev() + .enumerate() + .map(|(index, digit)| if index % 2 == 0 { digit } else { digit * 2 }) + .map(|digit| if digit > 9 { digit - 9 } else { digit }) + .sum::() % 10 == 0 + } +} + +impl<'a> Luhn for &'a str { + fn valid_luhn(&self) -> bool { + String::from(*self).valid_luhn() + } +} + +impl Luhn for u8 { + fn valid_luhn(&self) -> bool { + self.to_string().valid_luhn() + } +} + +impl Luhn for u16 { + fn valid_luhn(&self) -> bool { + self.to_string().valid_luhn() + } +} + +impl Luhn for u32 { + fn valid_luhn(&self) -> bool { + self.to_string().valid_luhn() + } +} + +impl Luhn for u64 { + fn valid_luhn(&self) -> bool { + self.to_string().valid_luhn() + } +} + +impl Luhn for usize { + fn valid_luhn(&self) -> bool { + self.to_string().valid_luhn() + } +} diff --git a/exercises/luhn-trait/tests/luhn-trait.rs b/exercises/luhn-trait/tests/luhn-trait.rs new file mode 100644 index 000000000..6393c964e --- /dev/null +++ b/exercises/luhn-trait/tests/luhn-trait.rs @@ -0,0 +1,59 @@ +extern crate luhn_trait; + +use luhn_trait::*; + +#[test] +fn you_can_validate_from_a_str() { + assert!("046 454 286".valid_luhn()); + assert!(!"046 454 287".valid_luhn()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_string() { + assert!(String::from("046 454 286").valid_luhn()); + assert!(!String::from("046 454 287").valid_luhn()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_u8() { + assert!(240u8.valid_luhn()); + assert!(!241u8.valid_luhn()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_u16() { + let valid = 64_436u16; + let invalid = 64_437u16; + assert!(valid.valid_luhn()); + assert!(!invalid.valid_luhn()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_u32() { + let valid = 46_454_286u32; + let invalid = 46_454_287u32; + assert!(valid.valid_luhn()); + assert!(!invalid.valid_luhn()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_u64() { + let valid = 8273_1232_7352_0562u64; + let invalid = 8273_1232_7352_0569u64; + assert!(valid.valid_luhn()); + assert!(!invalid.valid_luhn()); +} + +#[test] +#[ignore] +fn you_can_validate_from_a_usize() { + let valid = 8273_1232_7352_0562usize; + let invalid = 8273_1232_7352_0569usize; + assert!(valid.valid_luhn()); + assert!(!invalid.valid_luhn()); +}