Repository: exercism/clojure
Pull request #652 (2024-07-14 16:14:24)

luhn: sync
For #48in24

https://forum.exercism.org/t/48in24-exercise-04-02-luhn/10467

Example solution was updated so that it passes the new tests

github-actions[bot]: Hello. Thanks for opening a PR on Exercism 🙂

We ask that all changes to Exercism are discussed on our Community Forum before being opened on GitHub. To enforce this, we automatically close all PRs that are submitted. That doesn't mean your PR is rejected but that we want the initial discussion about it to happen on our forum where a wide range of key contributors across the Exercism ecosystem can weigh in.

You can use [this link](https://forum.exercism.org/new-topic?title=luhn:%20sync&body=For%20#48in24%0D%0A%0D%0Ahttps://forum.exercism.org/t/48in24-exercise-04-02-luhn/10467%0D%0A%0D%0AExample%20solution%20was%20updated%20so%20that%20it%20passes%20the%20new%20tests&category=clojure ) to copy this into a new topic on the forum. If we decide the PR is appropriate, we'll reopen it and continue with it, so please don't delete your local branch.

If you're interested in learning more about this auto-responder, please read [this blog post](https://exercism.org/blog/contribution-guidelines-nov-2023).

---

_Note: If this PR has been pre-approved, please link back to this PR on the forum thread and a maintainer or staff member will reopen it._
github-actions[bot]: This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.

If this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.
```
[no important files changed]
```

 For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!

Patch:
diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md
index c7c7d3e..8cbe791 100644
--- a/exercises/practice/luhn/.docs/instructions.md
+++ b/exercises/practice/luhn/.docs/instructions.md
@@ -2,35 +2,31 @@
 
 Given a number determine whether or not it is valid per the Luhn formula.
 
-The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is
-a simple checksum formula used to validate a variety of identification
-numbers, such as credit card numbers and Canadian Social Insurance
-Numbers.
+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
-------
+## 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.
+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
+### 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
+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:
+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
@@ -42,9 +38,10 @@ Then sum all of the digits:
 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!
+If the sum is evenly divisible by 10, then the number is valid.
+This number is valid!
 
-## Example 2: invalid credit card number
+### Example 2: invalid credit card number
 
 ```text
 8273 1232 7352 0569
@@ -63,3 +60,5 @@ Sum the digits
 ```
 
 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
index fcc88e6..c03a923 100644
--- a/exercises/practice/luhn/.meta/config.json
+++ b/exercises/practice/luhn/.meta/config.json
@@ -21,5 +21,5 @@
   },
   "blurb": "Given a number determine whether or not it is valid per the Luhn formula.",
   "source": "The Luhn Algorithm on Wikipedia",
-  "source_url": "http://en.wikipedia.org/wiki/Luhn_algorithm"
+  "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm"
 }
diff --git a/exercises/practice/luhn/.meta/src/example.clj b/exercises/practice/luhn/.meta/src/example.clj
index c6b4521..4290395 100644
--- a/exercises/practice/luhn/.meta/src/example.clj
+++ b/exercises/practice/luhn/.meta/src/example.clj
@@ -1,34 +1,47 @@
-(ns luhn
-  (:require [clojure.string :as string]))
-
-(defn to-reversed-digits
-  "returns a lazy sequence of least to most significant digits of n"
-  [n]
-  (->> [n 0]
-       (iterate (fn [[i _]] [(quot i 10) (mod i 10)]))
-       (take-while (complement #{[0 0]}))
-       (map second)
-       rest))
-
-(defn checksum
-  "returns the luhn checksum of n, assuming it has a check digit"
-  [n]
-  (-> (->> n
-           to-reversed-digits
-           (map * (cycle [1 2]))
-           (map #(if (>= % 10) (- % 9) %))
-           (apply +))
-      (mod 10)))
-
-(defn string->long
-  "Strips any non-digit characters and converts the string into a Long"
-  [n]
-  (-> n (string/replace #"[^0-9]+" "") Long/parseLong))
+(ns luhn)
+
+(defn string->digits
+  [s]
+  (map #(Character/digit ^char % 10) s))
+
+(defn valid-integer?
+  [digits]
+  (and (> (count digits) 1)
+       (every? #(<= 0 % 9) digits)))
+
+(defn zero-pad
+  [digits]
+  (if (even? (count digits))
+    digits
+    (conj digits 0)))
+
+(defn transform-digit
+  [index val]
+  (if (odd? index)
+    val
+    (let [dval (* 2 val)]
+      (if (> dval 9)
+        (- dval 9)
+        dval))))
+
+(defn get-luhn-value
+  [digits]
+  (let [padded-digits (zero-pad digits)]
+    (->> padded-digits
+         (map-indexed transform-digit)
+         (reduce +))))
+
+(defn valid-luhn-value?
+  [val]
+  (int? (/ val 10)))
+
+(defn remove-spaces
+  [s]
+  (remove #{\space} s))
 
 (defn valid?
-  "whether n has a valid luhn check-digit"
-  [n]
-  ; Numbers with non digit/whitespace or only 1 digit are invalid
-  (if (or (re-find #"[^0-9\s]+" n) (>= 1 (count (string/trim n))))
-    false
-    (zero? (-> n string->long checksum))))
+  "Returns true if the given string represents a valid luhn number, else false"
+  [s]
+  (let [digits (string->digits (remove-spaces s))]
+    (and (valid-integer? digits)
+         (valid-luhn-value? (get-luhn-value digits)))))


Test patch:
diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml
index 9bb8224..c0be0c4 100644
--- a/exercises/practice/luhn/.meta/tests.toml
+++ b/exercises/practice/luhn/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# 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"
@@ -26,6 +33,9 @@ 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"
 
@@ -50,8 +60,17 @@ 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/test/luhn_test.clj b/exercises/practice/luhn/test/luhn_test.clj
index d706786..892c03f 100644
--- a/exercises/practice/luhn/test/luhn_test.clj
+++ b/exercises/practice/luhn/test/luhn_test.clj
@@ -2,41 +2,90 @@
   (:require [clojure.test :refer [deftest is testing]]
             luhn))
 
-(deftest single-digit-strings
-    (is (false? (luhn/valid? "1"))))
+(deftest single-digit-strings-are-invalid
+  (testing "Single digit strings can not be valid"
+    (is (false? (luhn/valid? "1")))))
 
-(deftest single-zero
-    (is (false? (luhn/valid? "0"))))
+(deftest single-zero-is-invalid
+  (testing "A single zero is invalid"
+    (is (false? (luhn/valid? "0")))))
 
-(deftest sin
-    (is (true? (luhn/valid? " 5 9 "))))
+(deftest simple-valid-SIN-that-remains-valid-if-reversed
+  (testing "A simple valid SIN that remains valid if reversed"
+    (is (true? (luhn/valid? "059")))))
 
-(deftest Canadian-SIN
-    (is (true? (luhn/valid? "046 454 286"))))
+(deftest simple-valid-SIN-that-becomes-invalid-if-reversed
+  (testing "A simple valid SIN that becomes invalid if reversed"
+    (is (true? (luhn/valid? "59")))))
 
-(deftest invalid-Canadian-SIN
-    (is (false? (luhn/valid? "046 454 287"))))
+(deftest valid-canadian-SIN
+  (testing "A valid Canadian SIN"
+    (is (true? (luhn/valid? "055 444 285")))))
+
+(deftest invalid-canadian-SIN
+  (testing "Invalid Canadian SIN"
+    (is (false? (luhn/valid? "055 444 286")))))
 
 (deftest invalid-credit-card
-    (is (false? (luhn/valid? "8273 1232 7352 0569"))))
+  (testing "Invalid credit card"
+    (is (false? (luhn/valid? "8273 1232 7352 0569")))))
+
+(deftest invalid-long-number-with-an-even-remainder
+  (testing "Invalid long number with an even remainder"
+    (is (false? (luhn/valid? "1 2345 6789 1234 5678 9012")))))
+
+(deftest invalid-long-number-with-a-remainder-divisible-by-5
+  (testing "Invalid long number with a remainder divisible by 5"
+    (is (false? (luhn/valid? "1 2345 6789 1234 5678 9013")))))
+
+(deftest valid-number-with-an-even-number-of-digits
+  (testing "Valid number with an even number of digits"
+    (is (true? (luhn/valid? "095 245 88")))))
+
+(deftest valid-number-with-an-odd-number-of-spaces
+  (testing "Valid number with an odd number of spaces"
+    (is (true? (luhn/valid? "234 567 891 234")))))
+
+(deftest valid-strings-with-a-non-digit-added-at-the-end-become-invalid
+  (testing "Valid strings with a non-digit added at the end become invalid"
+    (is (false? (luhn/valid? "059a")))))
+
+(deftest valid-strings-with-punctuation-included-become-invalid
+  (testing "Valid strings with punctuation included become invalid"
+    (is (false? (luhn/valid? "055-444-285")))))
+
+(deftest valid-strings-with-symbols-included-become-invalid
+  (testing "Valid strings with symbols included become invalid"
+    (is (false? (luhn/valid? "055# 444$ 285")))))
+
+(deftest single-zero-with-space-is-invalid
+  (testing "Single zero with space is invalid"
+    (is (false? (luhn/valid? " 0")))))
 
-(deftest non-digit-added
-    (is (false? (luhn/valid? "046a 454 286"))))
+(deftest more-than-a-single-zero-is-valid
+  (testing "More than a single zero is valid"
+    (is (true? (luhn/valid? "0000 0")))))
 
-(deftest punctuation
-    (is (false? (luhn/valid? "055-444-285"))))
+(deftest input-digit-9-is-correctly-converted-to-output-digit-9
+  (testing "Input digit 9 is correctly converted to output digit 9"
+    (is (true? (luhn/valid? "091")))))
 
-(deftest symbols
-    (is (false? (luhn/valid? "055£ 444$ 285"))))
+(deftest very-long-input-is-valid
+  (testing "Very long input is valid"
+    (is (true? (luhn/valid? "9999999999 9999999999 9999999999 9999999999")))))
 
-(deftest single-zero-with-space
-    (is (false? (luhn/valid? " 0"))))
+(deftest valid-luhn-with-an-odd-number-of-digits-and-non-zero-first-digit
+  (testing "Valid luhn with an odd number of digits and non zero first digit"
+    (is (true? (luhn/valid? "109")))))
 
-(deftest lots-of-zeros
-    (is (true? (luhn/valid? " 00000"))))
+(deftest using-ascii-value-for-non-doubled-non-digit-is-not-allowed
+  (testing "Using ascii value for non-doubled non-digit isn't allowed"
+    (is (false? (luhn/valid? "055b 444 285")))))
 
-(deftest another-valid-sin
-    (is (true? (luhn/valid? "055 444 285"))))
+(deftest using-ascii-value-for-doubled-non-digit-is-not-allowed
+  (testing "Using ascii value for doubled non-digit isn't allowed"
+    (is (false? (luhn/valid? ":9")))))
 
-(deftest nine-doubled
-    (is (true? (luhn/valid? "091"))))
+(deftest non-numeric-non-space-char-in-middle-with-sum-that-is-divisible-by-10-is-not-allowed
+  (testing "Non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed"
+    (is (false? (luhn/valid? "59%59")))))