Skip to content

Commit

Permalink
Merge pull request #248 from IanWhitney/implement_luhn_from
Browse files Browse the repository at this point in the history
Implement Luhn parts 2 & 3: From Trait and Custom Trait
  • Loading branch information
IanWhitney authored Feb 26, 2017
2 parents ad41380 + 68caa48 commit e0a4c6e
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 0 deletions.
20 changes: 20 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions exercises/luhn-from/.gitignore
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions exercises/luhn-from/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[package]
name = "luhn-from"
version = "0.0.0"
9 changes: 9 additions & 0 deletions exercises/luhn-from/HINTS.md
Original file line number Diff line number Diff line change
@@ -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.
62 changes: 62 additions & 0 deletions exercises/luhn-from/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
pub struct Luhn {
digits: Vec<char>,
}

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::<u32>() % 10 == 0
}
}

impl From<String> 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<u8> for Luhn {
fn from(s: u8) -> Self {
Luhn::from(s.to_string())
}
}

impl From<u16> for Luhn {
fn from(s: u16) -> Self {
Luhn::from(s.to_string())
}
}

impl From<u32> for Luhn {
fn from(s: u32) -> Self {
Luhn::from(s.to_string())
}
}

impl From<u64> for Luhn {
fn from(s: u64) -> Self {
Luhn::from(s.to_string())
}
}

impl From<usize> for Luhn {
fn from(s: usize) -> Self {
Luhn::from(s.to_string())
}
}
101 changes: 101 additions & 0 deletions exercises/luhn-from/tests/luhn-from.rs
Original file line number Diff line number Diff line change
@@ -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());
}
7 changes: 7 additions & 0 deletions exercises/luhn-trait/.gitignore
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions exercises/luhn-trait/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[package]
name = "luhn-trait"
version = "0.0.0"
17 changes: 17 additions & 0 deletions exercises/luhn-trait/HINTS.md
Original file line number Diff line number Diff line change
@@ -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).
55 changes: 55 additions & 0 deletions exercises/luhn-trait/example.rs
Original file line number Diff line number Diff line change
@@ -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::<u32>() % 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()
}
}
59 changes: 59 additions & 0 deletions exercises/luhn-trait/tests/luhn-trait.rs
Original file line number Diff line number Diff line change
@@ -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());
}

0 comments on commit e0a4c6e

Please sign in to comment.