Skip to content

Commit

Permalink
Add luhn exercise (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
pfertyk authored Jan 30, 2024
1 parent b7d6434 commit 57d6b33
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 1 deletion.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 2
},
{
"slug": "luhn",
"name": "Luhn",
"uuid": "8f7a50ef-60fc-4e72-99de-c5dcf6ae2577",
"practices": [],
"prerequisites": [],
"difficulty": 3
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion exercises/practice/flatten-array/flatten_array.gd
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
func flatten(iterable):
pass
pass
64 changes: 64 additions & 0 deletions exercises/practice/luhn/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Instructions

Given a number determine whether or not it is valid per the Luhn formula.

The [Luhn algorithm][luhn] is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers.

The task is to check if a given string is valid.

## Validating a Number

Strings of length 1 or less are not valid.
Spaces are allowed in the input, but they should be stripped before checking.
All other non-digit characters are disallowed.

### Example 1: valid credit card number

```text
4539 3195 0343 6467
```

The first step of the Luhn algorithm is to double every second digit, starting from the right.
We will be doubling

```text
4_3_ 3_9_ 0_4_ 6_6_
```

If doubling the number results in a number greater than 9 then subtract 9 from the product.
The results of our doubling:

```text
8569 6195 0383 3437
```

Then sum all of the digits:

```text
8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80
```

If the sum is evenly divisible by 10, then the number is valid.
This number is valid!

### Example 2: invalid credit card number

```text
8273 1232 7352 0569
```

Double the second digits, starting from the right

```text
7253 2262 5312 0539
```

Sum the digits

```text
7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57
```

57 is not evenly divisible by 10, so this number is not valid.

[luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm
19 changes: 19 additions & 0 deletions exercises/practice/luhn/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"pfertyk"
],
"files": {
"solution": [
"luhn.gd"
],
"test": [
"luhn_test.gd"
],
"example": [
".meta/example.gd"
]
},
"blurb": "Given a number determine whether or not it is valid per the Luhn formula.",
"source": "The Luhn Algorithm on Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm"
}
22 changes: 22 additions & 0 deletions exercises/practice/luhn/.meta/example.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@export var card_num : String


func valid():
var num = card_num.replace(" ", "")
if len(num) <= 1:
return false

var checksum = 0
for index in range(len(num)):
var char = num[len(num) - 1 - index]
if not char.is_valid_int():
return false
if index % 2 == 1:
var digit = int(char)
digit *= 2
if digit > 9:
digit -= 9
checksum += digit
else:
checksum += int(char)
return checksum % 10 == 0
76 changes: 76 additions & 0 deletions exercises/practice/luhn/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[792a7082-feb7-48c7-b88b-bbfec160865e]
description = "single digit strings can not be valid"

[698a7924-64d4-4d89-8daa-32e1aadc271e]
description = "a single zero is invalid"

[73c2f62b-9b10-4c9f-9a04-83cee7367965]
description = "a simple valid SIN that remains valid if reversed"

[9369092e-b095-439f-948d-498bd076be11]
description = "a simple valid SIN that becomes invalid if reversed"

[8f9f2350-1faf-4008-ba84-85cbb93ffeca]
description = "a valid Canadian SIN"

[1cdcf269-6560-44fc-91f6-5819a7548737]
description = "invalid Canadian SIN"

[656c48c1-34e8-4e60-9a5a-aad8a367810a]
description = "invalid credit card"

[20e67fad-2121-43ed-99a8-14b5b856adb9]
description = "invalid long number with an even remainder"

[7e7c9fc1-d994-457c-811e-d390d52fba5e]
description = "invalid long number with a remainder divisible by 5"

[ad2a0c5f-84ed-4e5b-95da-6011d6f4f0aa]
description = "valid number with an even number of digits"

[ef081c06-a41f-4761-8492-385e13c8202d]
description = "valid number with an odd number of spaces"

[bef66f64-6100-4cbb-8f94-4c9713c5e5b2]
description = "valid strings with a non-digit added at the end become invalid"

[2177e225-9ce7-40f6-b55d-fa420e62938e]
description = "valid strings with punctuation included become invalid"

[ebf04f27-9698-45e1-9afe-7e0851d0fe8d]
description = "valid strings with symbols included become invalid"

[08195c5e-ce7f-422c-a5eb-3e45fece68ba]
description = "single zero with space is invalid"

[12e63a3c-f866-4a79-8c14-b359fc386091]
description = "more than a single zero is valid"

[ab56fa80-5de8-4735-8a4a-14dae588663e]
description = "input digit 9 is correctly converted to output digit 9"

[b9887ee8-8337-46c5-bc45-3bcab51bc36f]
description = "very long input is valid"

[8a7c0e24-85ea-4154-9cf1-c2db90eabc08]
description = "valid luhn with an odd number of digits and non zero first digit"

[39a06a5a-5bad-4e0f-b215-b042d46209b1]
description = "using ascii value for non-doubled non-digit isn't allowed"

[f94cf191-a62f-4868-bc72-7253114aa157]
description = "using ascii value for doubled non-digit isn't allowed"

[8b72ad26-c8be-49a2-b99c-bcc3bf631b33]
description = "non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed"
5 changes: 5 additions & 0 deletions exercises/practice/luhn/luhn.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@export var card_num : String


func valid():
pass
117 changes: 117 additions & 0 deletions exercises/practice/luhn/luhn_test.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
func test_single_digit_strings_can_not_be_valid(solution_script):
solution_script.card_num = "1"
return [solution_script.valid(), false]


func test_a_single_zero_is_invalid(solution_script):
solution_script.card_num = "0"
return [solution_script.valid(), false]


func test_a_simple_valid_sin_that_remains_valid_if_reversed(solution_script):
solution_script.card_num = "059"
return [solution_script.valid(), true]


func test_a_simple_valid_sin_that_becomes_invalid_if_reversed(solution_script):
solution_script.card_num = "59"
return [solution_script.valid(), true]


func test_a_valid_canadian_sin(solution_script):
solution_script.card_num = "055 444 285"
return [solution_script.valid(), true]


func test_invalid_canadian_sin(solution_script):
solution_script.card_num = "055 444 286"
return [solution_script.valid(), false]


func test_invalid_credit_card(solution_script):
solution_script.card_num = "8273 1232 7352 0569"
return [solution_script.valid(), false]


func test_invalid_long_number_with_an_even_remainder(solution_script):
solution_script.card_num = "1 2345 6789 1234 5678 9012"
return [solution_script.valid(), false]


func test_invalid_long_number_with_a_remainder_divisible_by_5(solution_script):
solution_script.card_num = "1 2345 6789 1234 5678 9013"
return [solution_script.valid(), false]


func test_valid_number_with_an_even_number_of_digits(solution_script):
solution_script.card_num = "095 245 88"
return [solution_script.valid(), true]


func test_valid_number_with_an_odd_number_of_spaces(solution_script):
solution_script.card_num = "234 567 891 234"
return [solution_script.valid(), true]


func test_valid_strings_with_a_non_digit_added_at_the_end_become_invalid(solution_script):
solution_script.card_num = "059a"
return [solution_script.valid(), false]


func test_valid_strings_with_punctuation_included_become_invalid(solution_script):
solution_script.card_num = "055-444-285"
return [solution_script.valid(), false]


func test_valid_strings_with_symbols_included_become_invalid(solution_script):
solution_script.card_num = "055# 444$ 285"
return [solution_script.valid(), false]


func test_single_zero_with_space_is_invalid(solution_script):
solution_script.card_num = " 0"
return [solution_script.valid(), false]


func test_more_than_a_single_zero_is_valid(solution_script):
solution_script.card_num = "0000 0"
return [solution_script.valid(), true]


func test_input_digit_9_is_correctly_converted_to_output_digit_9(solution_script):
solution_script.card_num = "091"
return [solution_script.valid(), true]


func test_very_long_input_is_valid(solution_script):
solution_script.card_num = "9999999999 9999999999 9999999999 9999999999"
return [solution_script.valid(), true]


func test_valid_luhn_with_an_odd_number_of_digits_and_non_zero_first_digit(solution_script):
solution_script.card_num = "109"
return [solution_script.valid(), true]


func test_using_ascii_value_for_non_doubled_non_digit_isn_t_allowed(solution_script):
solution_script.card_num = "055b 444 285"
return [solution_script.valid(), false]


func test_using_ascii_value_for_doubled_non_digit_isn_t_allowed(solution_script):
solution_script.card_num = ":9"
return [solution_script.valid(), false]


func test_non_numeric_non_space_char_in_the_middle_with_a_sum_that_s_divisible_by_10_isn_t_allowed(solution_script):
solution_script.card_num = "59%59"
return [solution_script.valid(), false]


func test_is_valid_can_be_called_repeatedly(solution_script):
# This test was added, because we saw many implementations
# in which the first call to valid() worked, but the
# second call failed.
solution_script.card_num = "055 444 285"
return [solution_script.valid(), true]
return [solution_script.valid(), true]

0 comments on commit 57d6b33

Please sign in to comment.