Skip to content

Commit a7f610b

Browse files
committed
Implement Luhn Trait
This implements the Trait-based API that I proposed here exercism#247 (comment) As pointed out by Peter, using traits like this is probably an anti-pattern. I'm somewhat on the fence about demonstrating it.
1 parent fb3029c commit a7f610b

File tree

6 files changed

+144
-0
lines changed

6 files changed

+144
-0
lines changed

config.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@
140140
"higher-order functions"
141141
]
142142
},
143+
{
144+
"slug": "luhn-trait",
145+
"difficulty": 1,
146+
"topics": [
147+
"traits",
148+
"str to digits",
149+
"iterators",
150+
"higher-order functions"
151+
]
152+
},
143153
{
144154
"slug": "largest-series-product",
145155
"difficulty": 4,

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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Luhn Validation with a Trait
2+
3+
Before doing this exercise you should probably do the original Luhn exercise and its successor, Luhn From Trait
4+
5+
- `exercism fetch rust luhn`
6+
- `exercism fetch rust luhn-from`
7+
8+
In the original Luhn exercise you only validated strings, but the Luhn algorithm can be applied integers as well.
9+
10+
In Luhn From you implemented a From trait, which also required you to create a Luhn struct.
11+
12+
Instead of creating a Struct just to perform the validation, what if you you validated the primitives (i.e, String, u8, etc.) themselves?
13+
14+
In this exercise you'll create and implement a custom [trait](https://doc.rust-lang.org/book/traits.html) that performs the validation
15+
16+
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 Luhn for &'static 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+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
extern crate luhn_trait;
2+
3+
use luhn_trait::*;
4+
5+
#[test]
6+
fn you_can_validate_from_a_str() {
7+
assert!("046 454 286".valid_luhn());
8+
assert!(!"046 454 287".valid_luhn());
9+
}
10+
11+
#[test]
12+
fn you_can_validate_from_a_string() {
13+
assert!(String::from("046 454 286").valid_luhn());
14+
assert!(!String::from("046 454 287").valid_luhn());
15+
}
16+
17+
#[test]
18+
fn you_can_validate_from_a_u8() {
19+
assert!(240u8.valid_luhn());
20+
assert!(!241u8.valid_luhn());
21+
}
22+
23+
#[test]
24+
fn you_can_validate_from_a_u16() {
25+
let valid = 64_436u16;
26+
let invalid = 64_437u16;
27+
assert!(valid.valid_luhn());
28+
assert!(!invalid.valid_luhn());
29+
}
30+
31+
#[test]
32+
fn you_can_validate_from_a_u32() {
33+
let valid = 46_454_286u32;
34+
let invalid = 46_454_287u32;
35+
assert!(valid.valid_luhn());
36+
assert!(!invalid.valid_luhn());
37+
}
38+
39+
#[test]
40+
fn you_can_validate_from_a_u64() {
41+
let valid = 8273_1232_7352_0562u64;
42+
let invalid = 8273_1232_7352_0569u64;
43+
assert!(valid.valid_luhn());
44+
assert!(!invalid.valid_luhn());
45+
}
46+
47+
#[test]
48+
fn you_can_validate_from_a_usize() {
49+
let valid = 8273_1232_7352_0562usize;
50+
let invalid = 8273_1232_7352_0569usize;
51+
assert!(valid.valid_luhn());
52+
assert!(!invalid.valid_luhn());
53+
}

0 commit comments

Comments
 (0)