From 3d70d5480cb1250c20d1a36d20e1e1c3c282af0a Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sat, 14 Jan 2017 17:17:09 -0600 Subject: [PATCH 1/5] Implement Luhn Closes #237 This mostly follows the current test suite (https://github.com/exercism/x-common/issues/491), with one change. The final canonical test adds an alphabetical character to a known invalid string and then asserts that it's still invalid. Instead what the test should do is alter a known-valid string and show that it is now invalid. That's what I've done here. I'll also propose the change to canonical test suite. --- exercises/luhn/.gitignore | 7 +++++++ exercises/luhn/Cargo.toml | 3 +++ exercises/luhn/example.rs | 14 ++++++++++++++ exercises/luhn/tests/luhn.rs | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 exercises/luhn/.gitignore create mode 100644 exercises/luhn/Cargo.toml create mode 100644 exercises/luhn/example.rs create mode 100644 exercises/luhn/tests/luhn.rs diff --git a/exercises/luhn/.gitignore b/exercises/luhn/.gitignore new file mode 100644 index 000000000..0e49cdd58 --- /dev/null +++ b/exercises/luhn/.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/Cargo.toml b/exercises/luhn/Cargo.toml new file mode 100644 index 000000000..6dc3dff8d --- /dev/null +++ b/exercises/luhn/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "luhn" +version = "0.0.0" diff --git a/exercises/luhn/example.rs b/exercises/luhn/example.rs new file mode 100644 index 000000000..800bb17ad --- /dev/null +++ b/exercises/luhn/example.rs @@ -0,0 +1,14 @@ +pub fn is_valid(candidate: &str) -> bool { + if candidate.chars().any(|c| c.is_alphabetic()) || candidate.chars().count() == 1 { + return false; + } + + candidate.chars() + .filter(|c| c.is_numeric()) + .map(|c| c.to_digit(10).unwrap()) + .rev() + .enumerate() + .map(|(index, digit)| if index % 2 == 0 { digit } else { digit * 2 }) + .map(|digit| if digit > 10 { digit - 9 } else { digit }) + .sum::() % 10 == 0 +} diff --git a/exercises/luhn/tests/luhn.rs b/exercises/luhn/tests/luhn.rs new file mode 100644 index 000000000..421877561 --- /dev/null +++ b/exercises/luhn/tests/luhn.rs @@ -0,0 +1,33 @@ +extern crate luhn; + +use luhn::*; + +#[test] +fn single_digit_string_is_invalid() { + assert!(!is_valid("1")); +} + +#[test] +fn single_zero_string_is_invalid() { + assert!(!is_valid("0")); +} + +#[test] +fn valid_canadian_sin_is_valid() { + assert!(is_valid("046 454 286")); +} + +#[test] +fn invalid_canadian_sin_is_invalid() { + assert!(!is_valid("046 454 287")); +} + +#[test] +fn invalid_credit_card_is_invalid() { + assert!(!is_valid("8273 1232 7352 0569")); +} + +#[test] +fn strings_that_contain_non_digits_are_invalid() { + assert!(!is_valid("046a 454 286")); +} From f280c5aa0c866ee8c3889160dedd3ed065e82502 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sat, 14 Jan 2017 17:47:52 -0600 Subject: [PATCH 2/5] Ignore all but first test --- exercises/luhn/tests/luhn.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/exercises/luhn/tests/luhn.rs b/exercises/luhn/tests/luhn.rs index 421877561..bcf809e5e 100644 --- a/exercises/luhn/tests/luhn.rs +++ b/exercises/luhn/tests/luhn.rs @@ -8,26 +8,31 @@ fn single_digit_string_is_invalid() { } #[test] +#[ignore] fn single_zero_string_is_invalid() { assert!(!is_valid("0")); } #[test] +#[ignore] fn valid_canadian_sin_is_valid() { assert!(is_valid("046 454 286")); } #[test] +#[ignore] fn invalid_canadian_sin_is_invalid() { assert!(!is_valid("046 454 287")); } #[test] +#[ignore] fn invalid_credit_card_is_invalid() { assert!(!is_valid("8273 1232 7352 0569")); } #[test] +#[ignore] fn strings_that_contain_non_digits_are_invalid() { assert!(!is_valid("046a 454 286")); } From 0bee7de87794312ac05b2a794533495f57094c55 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sat, 14 Jan 2017 17:48:07 -0600 Subject: [PATCH 3/5] Filter + Map = filter_map https://github.com/exercism/xrust/pull/247/files#r96126729 --- exercises/luhn/example.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exercises/luhn/example.rs b/exercises/luhn/example.rs index 800bb17ad..77a2c6d8e 100644 --- a/exercises/luhn/example.rs +++ b/exercises/luhn/example.rs @@ -4,8 +4,7 @@ pub fn is_valid(candidate: &str) -> bool { } candidate.chars() - .filter(|c| c.is_numeric()) - .map(|c| c.to_digit(10).unwrap()) + .filter_map(|c| c.to_digit(10)) .rev() .enumerate() .map(|(index, digit)| if index % 2 == 0 { digit } else { digit * 2 }) From 00715b50c1b2f94785c5621bf43ac4f846966334 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sat, 14 Jan 2017 17:55:20 -0600 Subject: [PATCH 4/5] Place Luhn before Largest Series Product I don't think my solution is that far from what people will do. The [existing crate](https://lunemec.github.io/rust-luhn/src/luhn2/src/lib.rs.html) uses another crate to handle digits > 10 after doubling. If people go down that path their solutions will vary a bit from mine, but not by a bunch. So the solution mostly requires a good handle on - Converting a string to digits - Working with iteration The conversion of a string to digits is also part of Largest Series Product, so we could put the exercise to right before LSP to reinforce that concept. --- config.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config.json b/config.json index d42737d53..aaec8e669 100644 --- a/config.json +++ b/config.json @@ -122,6 +122,15 @@ "match" ] }, + { + "slug": "luhn", + "difficulty": 1, + "topics": [ + "str to digits", + "iterators", + "higher-order functions" + ] + }, { "slug": "largest-series-product", "difficulty": 1, From 1c1e8b4d1765265403b5bd5272bfaf7d7ccc453f Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sun, 15 Jan 2017 07:30:55 -0600 Subject: [PATCH 5/5] Fix logical error https://github.com/exercism/xrust/pull/247#discussion_r96135608 --- exercises/luhn/example.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/luhn/example.rs b/exercises/luhn/example.rs index 77a2c6d8e..da6c35021 100644 --- a/exercises/luhn/example.rs +++ b/exercises/luhn/example.rs @@ -8,6 +8,6 @@ pub fn is_valid(candidate: &str) -> bool { .rev() .enumerate() .map(|(index, digit)| if index % 2 == 0 { digit } else { digit * 2 }) - .map(|digit| if digit > 10 { digit - 9 } else { digit }) + .map(|digit| if digit > 9 { digit - 9 } else { digit }) .sum::() % 10 == 0 }