Skip to content

Implement Luhn #247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 15, 2017
Merged

Implement Luhn #247

merged 5 commits into from
Jan 15, 2017

Conversation

IanWhitney
Copy link
Contributor

Closes #237

This mostly follows the current test suite
(exercism/problem-specifications#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.

Closes exercism#237

This mostly follows the current test suite
(exercism/problem-specifications#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.
}

candidate.chars()
.filter(|c| c.is_numeric())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can I suggest filter_map here? It seems perfect for this use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep.

@IanWhitney
Copy link
Contributor Author

IanWhitney commented Jan 14, 2017

Thinking about placement. I don't think my solution is that far from what people will do. The existing crate 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

I would put it between Sum of Multiples and Scrabble Score.

Update:

The conversion of a string to digits is also part of Largest Series Product, so we could move the exercise to right before LSP to reinforce that concept.

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.
IanWhitney pushed a commit to IanWhitney/x-common that referenced this pull request Jan 15, 2017
While implementing these tests for Rust I found that the final test was
not well written (my fault!)

exercism/rust#247

I added the alphabetical character to an already-invalid string. So the
test doesn't show anything useful.

Instead the test should show that a known-valid string is made invalid
by the inclusion of a non-digit
@IanWhitney
Copy link
Contributor Author

IanWhitney commented Jan 15, 2017

This works, but I've been thinking about some other, more Rusty ways of approaching it.

Option 1: Use From

This way we could validate strings and different types of unsized ints. The test API would look like:

let luhn = Luhn::from("012345");
assert!(!luhn.is_valid());
let luhn = Luhn::from(12345);
assert(!luhn.is_valid());

Option 2: Use Traits

Students would create a LuhnValidator trait and then implement it for different types. Test API would look like:

let string_luhn = String::from("012345");
assert!(!string_luhn.valid_luhn());

let u8_luhn: u8 = 8; 
assert!(!u8_luhn.valid_luhn());
//and so on

@IanWhitney
Copy link
Contributor Author

Option 3

Do all of them. Have 3 Luhn exercises that show different ways of solving the problem, so that students can see each approach.

@petertseng
Copy link
Member

I know you already placed, but I'm going to make my placement comment without looking at where you placed to avoid bias, and if our ideas match up then great.

Place no later than robot-simulator.
Place no earlier than hamming.

Fantastic, we agree.

Copy link
Member

@petertseng petertseng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could validate strings and different types of unsized ints

A bit of a shame that strings with leading zeroes lose their leading zeroes, but fortunately this doesn't affect correcntess.

Students would create a LuhnValidator trait and then implement it for different types.

Yeah, interesting way to add functions to existing types. Do we have any exercise that already does this? I think it's a good thing to show off at some point.
Looking at our git grep "impl.*for", I guess not.

Hmm, https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits says "It is considered poor style to implement methods on such primitive types, even though it is possible.", I wonder if we should still show it anyway.

.rev()
.enumerate()
.map(|(index, digit)| if index % 2 == 0 { digit } else { digit * 2 })
.map(|digit| if digit > 10 { digit - 9 } else { digit })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, but this code says !is_valid("59") and is_valid("50"), when instead it should say is_valid("59") and !is_valid("50").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch. Should be digit > 9.


#[test]
#[ignore]
fn valid_canadian_sin_is_valid() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long cat is long.

(No change necessary)

@IanWhitney
Copy link
Contributor Author

Just this week I was trying to figure out how to monkey-patch Ruby to add methods to the number 2. Not that this is a good idea, but it was interesting. For the same reason I think show traits on primitives could be interesting, though the documentation of the exercise should say that it is not recommended.

I'm going to experiment with Option 3 and will either update this PR or create a new one.

Insti pushed a commit to exercism/problem-specifications that referenced this pull request Jan 15, 2017
* Luhn: Update test of non-digit

While implementing these tests for Rust I found that the final test was
not well written (my fault!)

exercism/rust#247

I added the alphabetical character to an already-invalid string. So the
test doesn't show anything useful.

Instead the test should show that a known-valid string is made invalid
by the inclusion of a non-digit
@IanWhitney IanWhitney merged commit 2886848 into exercism:master Jan 15, 2017
@IanWhitney IanWhitney deleted the implement_luhn branch January 15, 2017 21:06
IanWhitney pushed a commit to IanWhitney/xrust that referenced this pull request Jan 16, 2017
This is an implementation of the idea I discussed here

exercism#247 (comment)

After students do the original Luhn exercise, they can learn how to use
the From trait to allow validation of multiple types.

I've put this right after Luhn, which might be a bit early for
introducing Traits.
IanWhitney pushed a commit to IanWhitney/xrust that referenced this pull request Jan 16, 2017
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.
IanWhitney pushed a commit to IanWhitney/xrust that referenced this pull request Feb 25, 2017
This is an implementation of the idea I discussed here

exercism#247 (comment)

After students do the original Luhn exercise, they can learn how to use
the From trait to allow validation of multiple types.

I've put this right after Luhn, which might be a bit early for
introducing Traits.
IanWhitney pushed a commit to IanWhitney/xrust that referenced this pull request Feb 25, 2017
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.
IanWhitney pushed a commit to IanWhitney/xrust that referenced this pull request Feb 26, 2017
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.
IanWhitney pushed a commit to IanWhitney/xrust that referenced this pull request Feb 26, 2017
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants