From 57d6b332a742e888a6bb5e842029264b962d6f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=C2=A0Fertyk?= Date: Tue, 30 Jan 2024 17:54:50 +0100 Subject: [PATCH] Add luhn exercise (#40) --- config.json | 8 ++ .../practice/flatten-array/flatten_array.gd | 2 +- exercises/practice/luhn/.docs/instructions.md | 64 ++++++++++ exercises/practice/luhn/.meta/config.json | 19 +++ exercises/practice/luhn/.meta/example.gd | 22 ++++ exercises/practice/luhn/.meta/tests.toml | 76 ++++++++++++ exercises/practice/luhn/luhn.gd | 5 + exercises/practice/luhn/luhn_test.gd | 117 ++++++++++++++++++ 8 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/luhn/.docs/instructions.md create mode 100644 exercises/practice/luhn/.meta/config.json create mode 100644 exercises/practice/luhn/.meta/example.gd create mode 100644 exercises/practice/luhn/.meta/tests.toml create mode 100644 exercises/practice/luhn/luhn.gd create mode 100644 exercises/practice/luhn/luhn_test.gd diff --git a/config.json b/config.json index a40f441..5c0bfee 100644 --- a/config.json +++ b/config.json @@ -122,6 +122,14 @@ "practices": [], "prerequisites": [], "difficulty": 2 + }, + { + "slug": "luhn", + "name": "Luhn", + "uuid": "8f7a50ef-60fc-4e72-99de-c5dcf6ae2577", + "practices": [], + "prerequisites": [], + "difficulty": 3 } ] }, diff --git a/exercises/practice/flatten-array/flatten_array.gd b/exercises/practice/flatten-array/flatten_array.gd index 40f0687..58ef34d 100644 --- a/exercises/practice/flatten-array/flatten_array.gd +++ b/exercises/practice/flatten-array/flatten_array.gd @@ -1,2 +1,2 @@ func flatten(iterable): - pass + pass diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md new file mode 100644 index 0000000..8cbe791 --- /dev/null +++ b/exercises/practice/luhn/.docs/instructions.md @@ -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 diff --git a/exercises/practice/luhn/.meta/config.json b/exercises/practice/luhn/.meta/config.json new file mode 100644 index 0000000..54db742 --- /dev/null +++ b/exercises/practice/luhn/.meta/config.json @@ -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" +} diff --git a/exercises/practice/luhn/.meta/example.gd b/exercises/practice/luhn/.meta/example.gd new file mode 100644 index 0000000..0c56dda --- /dev/null +++ b/exercises/practice/luhn/.meta/example.gd @@ -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 diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml new file mode 100644 index 0000000..c0be0c4 --- /dev/null +++ b/exercises/practice/luhn/.meta/tests.toml @@ -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" diff --git a/exercises/practice/luhn/luhn.gd b/exercises/practice/luhn/luhn.gd new file mode 100644 index 0000000..24724f5 --- /dev/null +++ b/exercises/practice/luhn/luhn.gd @@ -0,0 +1,5 @@ +@export var card_num : String + + +func valid(): + pass diff --git a/exercises/practice/luhn/luhn_test.gd b/exercises/practice/luhn/luhn_test.gd new file mode 100644 index 0000000..304e335 --- /dev/null +++ b/exercises/practice/luhn/luhn_test.gd @@ -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]