Skip to content

Commit 10230ff

Browse files
committed
Solve Luhn_algorithm
1 parent 1fde93e commit 10230ff

File tree

6 files changed

+326
-0
lines changed

6 files changed

+326
-0
lines changed

luhn/.exercism/metadata.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"track":"rust","exercise":"luhn","id":"1a853db0b19e459f9d5d65d16935e744","url":"https://exercism.io/my/solutions/1a853db0b19e459f9d5d65d16935e744","handle":"iostafi","is_requester":true,"auto_approve":false}

luhn/.gitignore

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

luhn/Cargo.toml

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

luhn/README.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Luhn
2+
3+
Given a number determine whether or not it is valid per the Luhn formula.
4+
5+
The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is
6+
a simple checksum formula used to validate a variety of identification
7+
numbers, such as credit card numbers and Canadian Social Insurance
8+
Numbers.
9+
10+
The task is to check if a given string is valid.
11+
12+
Validating a Number
13+
------
14+
15+
Strings of length 1 or less are not valid. Spaces are allowed in the input,
16+
but they should be stripped before checking. All other non-digit characters
17+
are disallowed.
18+
19+
## Example 1: valid credit card number
20+
21+
```text
22+
4539 1488 0343 6467
23+
```
24+
25+
The first step of the Luhn algorithm is to double every second digit,
26+
starting from the right. We will be doubling
27+
28+
```text
29+
4_3_ 1_8_ 0_4_ 6_6_
30+
```
31+
32+
If doubling the number results in a number greater than 9 then subtract 9
33+
from the product. The results of our doubling:
34+
35+
```text
36+
8569 2478 0383 3437
37+
```
38+
39+
Then sum all of the digits:
40+
41+
```text
42+
8+5+6+9+2+4+7+8+0+3+8+3+3+4+3+7 = 80
43+
```
44+
45+
If the sum is evenly divisible by 10, then the number is valid. This number is valid!
46+
47+
## Example 2: invalid credit card number
48+
49+
```text
50+
8273 1232 7352 0569
51+
```
52+
53+
Double the second digits, starting from the right
54+
55+
```text
56+
7253 2262 5312 0539
57+
```
58+
59+
Sum the digits
60+
61+
```text
62+
7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57
63+
```
64+
65+
57 is not evenly divisible by 10, so this number is not valid.
66+
67+
## Rust Installation
68+
69+
Refer to the [exercism help page][help-page] for Rust installation and learning
70+
resources.
71+
72+
## Writing the Code
73+
74+
Execute the tests with:
75+
76+
```bash
77+
$ cargo test
78+
```
79+
80+
All but the first test have been ignored. After you get the first test to
81+
pass, open the tests source file which is located in the `tests` directory
82+
and remove the `#[ignore]` flag from the next test and get the tests to pass
83+
again. Each separate test is a function with `#[test]` flag above it.
84+
Continue, until you pass every test.
85+
86+
If you wish to run all ignored tests without editing the tests source file, use:
87+
88+
```bash
89+
$ cargo test -- --ignored
90+
```
91+
92+
To run a specific test, for example `some_test`, you can use:
93+
94+
```bash
95+
$ cargo test some_test
96+
```
97+
98+
If the specific test is ignored use:
99+
100+
```bash
101+
$ cargo test some_test -- --ignored
102+
```
103+
104+
To learn more about Rust tests refer to the [online test documentation][rust-tests]
105+
106+
Make sure to read the [Modules][modules] chapter if you
107+
haven't already, it will help you with organizing your files.
108+
109+
## Further improvements
110+
111+
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.
112+
113+
To format your solution, inside the solution directory use
114+
115+
```bash
116+
cargo fmt
117+
```
118+
119+
To see, if your solution contains some common ineffective use cases, inside the solution directory use
120+
121+
```bash
122+
cargo clippy --all-targets
123+
```
124+
125+
## Submitting the solution
126+
127+
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.
128+
129+
## Feedback, Issues, Pull Requests
130+
131+
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!
132+
133+
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).
134+
135+
[help-page]: https://exercism.io/tracks/rust/learning
136+
[modules]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
137+
[cargo]: https://doc.rust-lang.org/book/ch14-00-more-about-cargo.html
138+
[rust-tests]: https://doc.rust-lang.org/book/ch11-02-running-tests.html
139+
140+
## Source
141+
142+
The Luhn Algorithm on Wikipedia [http://en.wikipedia.org/wiki/Luhn_algorithm](http://en.wikipedia.org/wiki/Luhn_algorithm)
143+
144+
## Submitting Incomplete Solutions
145+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

luhn/src/lib.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use std::str::Chars;
2+
3+
/// Check a Luhn checksum.
4+
pub fn is_valid(code: &str) -> bool {
5+
let code_without_spaces = code.replace(" ", "");
6+
if code_without_spaces.len() <= 1 {
7+
return false;
8+
}
9+
let chs: Chars = code_without_spaces.chars();
10+
let mut chs_vec: Vec<char> = chs.collect::<Vec<char>>();
11+
chs_vec.reverse();
12+
let chs_slice = &chs_vec[..];
13+
14+
let sum: Option<u32> = chs_slice.chunks(2).fold(Some(0), |sum, chunk| {
15+
if sum.is_none() {
16+
return sum;
17+
}
18+
let ch_left = chunk.get(0).unwrap_or(&'a');
19+
let maybe_digit_left = ch_left.to_digit(10);
20+
21+
let ch_right = chunk.get(1).unwrap_or(&'0');
22+
let maybe_digit_right = ch_right
23+
.to_digit(10)
24+
.and_then(|digit| Some(luhn_double_digit(digit)));
25+
if let (Some(left), Some(right)) = (maybe_digit_left, maybe_digit_right) {
26+
Some(sum.unwrap_or(0) + left + right)
27+
} else {
28+
None
29+
}
30+
});
31+
sum.map_or_else(|| false, |v| v % 10 == 0)
32+
}
33+
34+
fn luhn_double_digit(digit: u32) -> u32 {
35+
let double = digit * 2;
36+
if double > 9 {
37+
double - 9
38+
} else {
39+
double
40+
}
41+
}

luhn/tests/luhn.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use luhn::*;
2+
3+
fn process_valid_case(number: &str, is_luhn_expected: bool) {
4+
assert_eq!(is_valid(number), is_luhn_expected);
5+
}
6+
7+
#[test]
8+
fn test_single_digit_strings_can_not_be_valid() {
9+
process_valid_case("1", false);
10+
}
11+
12+
#[test]
13+
#[ignore]
14+
fn test_a_single_zero_is_invalid() {
15+
process_valid_case("0", false);
16+
}
17+
18+
#[test]
19+
#[ignore]
20+
fn test_a_simple_valid_sin_that_remains_valid_if_reversed() {
21+
process_valid_case("059", true);
22+
}
23+
24+
#[test]
25+
#[ignore]
26+
fn test_a_simple_valid_sin_that_becomes_invalid_if_reversed() {
27+
process_valid_case("59", true);
28+
}
29+
30+
#[test]
31+
#[ignore]
32+
fn test_a_valid_canadian_sin() {
33+
process_valid_case("055 444 285", true);
34+
}
35+
36+
#[test]
37+
#[ignore]
38+
fn test_invalid_canadian_sin() {
39+
process_valid_case("055 444 286", false);
40+
}
41+
42+
#[test]
43+
#[ignore]
44+
fn test_invalid_credit_card() {
45+
process_valid_case("8273 1232 7352 0569", false);
46+
}
47+
48+
#[test]
49+
#[ignore]
50+
fn test_valid_number_with_an_even_number_of_digits() {
51+
process_valid_case("095 245 88", true); // 0_ 5_ 4_ 8_ = 09 12 85 87 = 10 15 15 = 40
52+
}
53+
54+
#[test]
55+
#[ignore]
56+
fn strings_that_contain_non_digits_are_invalid() {
57+
process_valid_case("055a 444 285", false);
58+
}
59+
60+
#[test]
61+
#[ignore]
62+
fn test_valid_strings_with_punctuation_included_become_invalid() {
63+
process_valid_case("055-444-285", false);
64+
}
65+
66+
#[test]
67+
#[ignore]
68+
fn symbols_are_invalid() {
69+
process_valid_case("055£ 444$ 285", false);
70+
}
71+
72+
#[test]
73+
#[ignore]
74+
fn test_single_zero_with_space_is_invalid() {
75+
process_valid_case(" 0", false);
76+
}
77+
78+
#[test]
79+
#[ignore]
80+
fn test_more_than_a_single_zero_is_valid() {
81+
process_valid_case("0000 0", true);
82+
}
83+
84+
#[test]
85+
#[ignore]
86+
fn test_input_digit_9_is_correctly_converted_to_output_digit_9() {
87+
process_valid_case("091", true);
88+
}
89+
90+
#[test]
91+
#[ignore]
92+
/// using ascii value for doubled non-digit isn't allowed
93+
/// Convert non-digits to their ascii values and then offset them by 48 sometimes accidentally declare an invalid string to be valid.
94+
/// This test is designed to avoid that solution.
95+
fn test_using_ascii_value_for_doubled_nondigit_isnt_allowed() {
96+
process_valid_case(":9", false);
97+
}
98+
99+
#[test]
100+
#[ignore]
101+
/// valid strings with a non-digit added at the end become invalid
102+
fn test_valid_strings_with_a_nondigit_added_at_the_end_become_invalid() {
103+
process_valid_case("059a", false);
104+
}
105+
106+
#[test]
107+
#[ignore]
108+
/// valid strings with symbols included become invalid
109+
fn test_valid_strings_with_symbols_included_become_invalid() {
110+
process_valid_case("055# 444$ 285", false);
111+
}
112+
113+
#[test]
114+
#[ignore]
115+
/// using ascii value for non-doubled non-digit isn't allowed
116+
/// Convert non-digits to their ascii values and then offset them by 48 sometimes accidentally declare an invalid string to be valid.
117+
/// This test is designed to avoid that solution.
118+
fn test_using_ascii_value_for_nondoubled_nondigit_isnt_allowed() {
119+
process_valid_case("055b 444 285", false);
120+
}
121+
122+
#[test]
123+
#[ignore]
124+
/// valid number with an odd number of spaces
125+
fn test_valid_number_with_an_odd_number_of_spaces() {
126+
process_valid_case("234 567 891 234", true);
127+
}

0 commit comments

Comments
 (0)