Skip to content

Commit 68caa48

Browse files
committed
Implement Rust-only followups to Luhn exercise
This implements the idea I discussed here exercism#247 (comment) After students do the original Luhn exercise, they can now do "Luhn: Using the From Trait" to allow validation of multiple types. And after that, they can do "Luhn: Using a Custom Trait" to develop their own trait. "Luhn: Using the From Trait" is placed just after Bracket Push (which also uses `From`). In the Luhn implementation students will implement `From` for multiple types, so it makes sense to put it after Bracket Push where students only implement it for one type. "Luhn: Using a Custom Trait" moves to after Space Age. Space Age solutions use custom traits and `From`, so pairing these exercises made sense.
1 parent 7995681 commit 68caa48

File tree

11 files changed

+343
-0
lines changed

11 files changed

+343
-0
lines changed

config.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,16 @@
263263
"stack or recursion"
264264
]
265265
},
266+
{
267+
"slug": "luhn-from",
268+
"difficulty": 4,
269+
"topics": [
270+
"from trait",
271+
"str to digits",
272+
"iterators",
273+
"higher-order functions"
274+
]
275+
},
266276
{
267277
"slug": "queen-attack",
268278
"difficulty": 4,
@@ -298,6 +308,16 @@
298308
"Default Trait implementation"
299309
]
300310
},
311+
{
312+
"slug": "luhn-trait",
313+
"difficulty": 4,
314+
"topics": [
315+
"Custom Trait",
316+
"str to digits",
317+
"iterators",
318+
"higher-order functions"
319+
]
320+
},
301321
{
302322
"slug": "allergies",
303323
"difficulty": 4,

exercises/luhn-from/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
/target/
4+
5+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6+
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
7+
Cargo.lock

exercises/luhn-from/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[package]
2+
name = "luhn-from"
3+
version = "0.0.0"

exercises/luhn-from/HINTS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Luhn: Using the From Trait
2+
3+
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:
4+
5+
> `exercism fetch rust luhn`
6+
7+
In the original Luhn exercise you only validated strings, but the Luhn algorithm can be applied to integers as well.
8+
9+
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.

exercises/luhn-from/example.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
pub struct Luhn {
2+
digits: Vec<char>,
3+
}
4+
5+
impl Luhn {
6+
pub fn is_valid(&self) -> bool {
7+
if self.digits.iter().any(|c| c.is_alphabetic()) || self.digits.iter().count() == 1 {
8+
return false;
9+
}
10+
11+
self.digits
12+
.iter()
13+
.filter_map(|c| c.to_digit(10))
14+
.rev()
15+
.enumerate()
16+
.map(|(index, digit)| if index % 2 == 0 { digit } else { digit * 2 })
17+
.map(|digit| if digit > 9 { digit - 9 } else { digit })
18+
.sum::<u32>() % 10 == 0
19+
}
20+
}
21+
22+
impl From<String> for Luhn {
23+
fn from(s: String) -> Self {
24+
Luhn { digits: s.chars().collect() }
25+
}
26+
}
27+
28+
impl<'a> From<&'a str> for Luhn {
29+
fn from(s: &'a str) -> Self {
30+
Luhn::from(String::from(s))
31+
}
32+
}
33+
34+
impl From<u8> for Luhn {
35+
fn from(s: u8) -> Self {
36+
Luhn::from(s.to_string())
37+
}
38+
}
39+
40+
impl From<u16> for Luhn {
41+
fn from(s: u16) -> Self {
42+
Luhn::from(s.to_string())
43+
}
44+
}
45+
46+
impl From<u32> for Luhn {
47+
fn from(s: u32) -> Self {
48+
Luhn::from(s.to_string())
49+
}
50+
}
51+
52+
impl From<u64> for Luhn {
53+
fn from(s: u64) -> Self {
54+
Luhn::from(s.to_string())
55+
}
56+
}
57+
58+
impl From<usize> for Luhn {
59+
fn from(s: usize) -> Self {
60+
Luhn::from(s.to_string())
61+
}
62+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
extern crate luhn_from;
2+
3+
use luhn_from::*;
4+
5+
#[test]
6+
fn you_can_validate_from_a_str() {
7+
let valid = Luhn::from("046 454 286");
8+
let invalid = Luhn::from("046 454 287");
9+
assert!(valid.is_valid());
10+
assert!(!invalid.is_valid());
11+
}
12+
13+
#[test]
14+
#[ignore]
15+
fn you_can_validate_from_a_string() {
16+
let valid = Luhn::from(String::from("046 454 286"));
17+
let invalid = Luhn::from(String::from("046 454 287"));
18+
assert!(valid.is_valid());
19+
assert!(!invalid.is_valid());
20+
}
21+
22+
#[test]
23+
#[ignore]
24+
fn you_can_validate_from_a_u8() {
25+
let valid = Luhn::from(240u8);
26+
let invalid = Luhn::from(241u8);
27+
assert!(valid.is_valid());
28+
assert!(!invalid.is_valid());
29+
}
30+
31+
#[test]
32+
#[ignore]
33+
fn you_can_validate_from_a_u16() {
34+
let valid = Luhn::from(64_436u16);
35+
let invalid = Luhn::from(64_437u16);
36+
assert!(valid.is_valid());
37+
assert!(!invalid.is_valid());
38+
}
39+
40+
#[test]
41+
#[ignore]
42+
fn you_can_validate_from_a_u32() {
43+
let valid = Luhn::from(46_454_286u32);
44+
let invalid = Luhn::from(46_454_287u32);
45+
assert!(valid.is_valid());
46+
assert!(!invalid.is_valid());
47+
}
48+
49+
#[test]
50+
#[ignore]
51+
fn you_can_validate_from_a_u64() {
52+
let valid = Luhn::from(8273_1232_7352_0562u64);
53+
let invalid = Luhn::from(8273_1232_7352_0569u64);
54+
assert!(valid.is_valid());
55+
assert!(!invalid.is_valid());
56+
}
57+
58+
#[test]
59+
#[ignore]
60+
fn you_can_validate_from_a_usize() {
61+
let valid = Luhn::from(8273_1232_7352_0562usize);
62+
let invalid = Luhn::from(8273_1232_7352_0569usize);
63+
assert!(valid.is_valid());
64+
assert!(!invalid.is_valid());
65+
}
66+
67+
#[test]
68+
#[ignore]
69+
fn single_digit_string_is_invalid() {
70+
assert!(!Luhn::from("1").is_valid());
71+
}
72+
73+
#[test]
74+
#[ignore]
75+
fn single_zero_string_is_invalid() {
76+
assert!(!Luhn::from("0").is_valid());
77+
}
78+
79+
#[test]
80+
#[ignore]
81+
fn valid_canadian_sin_is_valid() {
82+
assert!(Luhn::from("046 454 286").is_valid());
83+
}
84+
85+
#[test]
86+
#[ignore]
87+
fn invalid_canadian_sin_is_invalid() {
88+
assert!(!Luhn::from("046 454 287").is_valid());
89+
}
90+
91+
#[test]
92+
#[ignore]
93+
fn invalid_credit_card_is_invalid() {
94+
assert!(!Luhn::from("8273 1232 7352 0569").is_valid());
95+
}
96+
97+
#[test]
98+
#[ignore]
99+
fn strings_that_contain_non_digits_are_invalid() {
100+
assert!(!Luhn::from("046a 454 286").is_valid());
101+
}

exercises/luhn-trait/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
/target/
4+
5+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6+
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
7+
Cargo.lock

exercises/luhn-trait/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[package]
2+
name = "luhn-trait"
3+
version = "0.0.0"

exercises/luhn-trait/HINTS.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Luhn: Using a Custom Trait
2+
3+
Before doing this exercise you should probably do the original Luhn exercise and its successor, "Luhn: Using the From Trait"
4+
5+
To get the original Luhn exercise, run `exercism fetch rust luhn`
6+
7+
To get the "Luhn: Using the From Trait" exercise, run `exercism fetch rust luhn-from`
8+
9+
In the original Luhn exercise you only validated strings, but the Luhn algorithm can be applied to integers as well.
10+
11+
In "Luhn: Using the From Trait" you implemented a From trait, which also required you to create a Luhn struct.
12+
13+
Instead of creating a Struct just to perform the validation, what if you you validated the primitives (i.e, String, u8, etc.) themselves?
14+
15+
In this exercise you'll create and implement a custom [trait](https://doc.rust-lang.org/book/traits.html) that performs the validation.
16+
17+
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).

exercises/luhn-trait/example.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
pub trait Luhn {
2+
fn valid_luhn(&self) -> bool;
3+
}
4+
5+
impl Luhn for String {
6+
fn valid_luhn(&self) -> bool {
7+
if self.chars().any(|c| c.is_alphabetic()) || self.chars().count() == 1 {
8+
return false;
9+
}
10+
11+
self.chars()
12+
.filter_map(|c| c.to_digit(10))
13+
.rev()
14+
.enumerate()
15+
.map(|(index, digit)| if index % 2 == 0 { digit } else { digit * 2 })
16+
.map(|digit| if digit > 9 { digit - 9 } else { digit })
17+
.sum::<u32>() % 10 == 0
18+
}
19+
}
20+
21+
impl<'a> Luhn for &'a str {
22+
fn valid_luhn(&self) -> bool {
23+
String::from(*self).valid_luhn()
24+
}
25+
}
26+
27+
impl Luhn for u8 {
28+
fn valid_luhn(&self) -> bool {
29+
self.to_string().valid_luhn()
30+
}
31+
}
32+
33+
impl Luhn for u16 {
34+
fn valid_luhn(&self) -> bool {
35+
self.to_string().valid_luhn()
36+
}
37+
}
38+
39+
impl Luhn for u32 {
40+
fn valid_luhn(&self) -> bool {
41+
self.to_string().valid_luhn()
42+
}
43+
}
44+
45+
impl Luhn for u64 {
46+
fn valid_luhn(&self) -> bool {
47+
self.to_string().valid_luhn()
48+
}
49+
}
50+
51+
impl Luhn for usize {
52+
fn valid_luhn(&self) -> bool {
53+
self.to_string().valid_luhn()
54+
}
55+
}

0 commit comments

Comments
 (0)