More idiomatic way to calculate GS1 check digit in Clojure

checksum, clojure

Solution

(defn check-digit 
  [s] 
  (let [digits        (map #(Integer/parseInt (str %)) s)
        [chk & body]  (reverse digits)
        sum           (apply + (map * body (cycle [3 1])))
        moddiff       (mod (- 10 sum) 10)]
       moddiff))  

This implementation uses two clojure idioms I am conscious of:

- `let` to manage local decomposition (and reuse)

- `map` with a second collection, being an infinite lazy sequence 'adjacent' to the problem.

Also de-structures list so that would be easy to write the checking predicate as `(= moddiff chk)`.

Problem

I am trying to calculate a GS1 check digit and have come up with the following code. The algorithm for calculating a check digit is: - Reverse the barcode - Drop the last digit (calculated check digit) - Add the digits together with first, third, fifth, e.t.c. digit multiplied by 3 and even digits multiplied by 1. - Subtract the sum from nearest equal or higher multiple of ten It sounds simple typed out but the solution I came up with seemed a bit inelegant. It does work but I want to know if there is a more elegant way of writing this. ``` (defn abs "(abs n) is the absolute value of n" [n] (cond (not (number? n)) (throw (IllegalArgumentException. "abs requires a number")) (neg? n) (- n) :else n)) (defn sum-seq "adds (first number times 3) with (second number)" [coll] (+ (* (first coll) 3) (second coll))) (defn sum-digit [s] (reduce + (map sum-seq (partition 2 2 '(0) (map #(Integer/parseInt %) (drop 2 (clojure.string/split (clojure.string/reverse s) #""))))))) (defn mod-higher10 "Subtracts the sum from nearest equal or higher multiple of ten" [i] (if (zero? (rem i 10)) 0 (- 10(rem i 10)))) (defn check-digit "calculates a GS1 check digit" [s] (mod-higher10 (sum-digit s))) (= (check-digit "7311518182472") 2) (= (check-digit "7311518152284") 4) (= (check-digit "7311518225261") 1) (= (check-digit "7311518241452") 2) (= (check-digit "7311518034399") 9) (= (check-digit "7311518005955") 5) (= (check-digit "7311518263393") 3) (= (check-digit "7311518240943") 3) (= (check-digit "00000012345687") 7) (= (check-digit "012345670") 0) ```

Original source