1
\$\begingroup\$

I wrote a simple program to sum the digits of a number in Clojure for learning purpose. Not very complex (I hope) so not adding any explanation. I am a beginner in Clojure so please don't mind pointing out even the most obvious mistakes.

NOTE: It recursively calculates the sum of digits in the result until a single digit result is obtained. For example, given "4312", 1 is returned (4312 -> 10 -> 1).

(defn sum-once [x]
  (reduce +
          (map
            #(Integer/parseInt (str %))
            (seq (char-array x)))))

(defn sum-digits [x]
  (let [y (sum-once x)]
    (if (< y 10) y
                 (sum-digits (str y)))))
\$\endgroup\$
4
  • \$\begingroup\$ @ferada What is the language tag that you used? \$\endgroup\$ Commented Nov 12, 2015 at 12:59
  • \$\begingroup\$ should it work - since (sum-digits "4312") gives 1 \$\endgroup\$
    – birdspider
    Commented Nov 12, 2015 at 13:08
  • \$\begingroup\$ @birdspider Yeah it is supposed to work like that. Recursively till a single digit output is given. Added that in the question. Sorry for the confusion. \$\endgroup\$ Commented Nov 12, 2015 at 13:09
  • \$\begingroup\$ @AseemBansal I marked it as Clojure, so if you click on edit you'll see lang-clj as the language tag. \$\endgroup\$
    – ferada
    Commented Nov 12, 2015 at 13:26

3 Answers 3

1
\$\begingroup\$

Retaining your names, I'd rewrite the functions as follows:

(defn sum-once [x]
  (reduce +
          (map
            #(- (int %) (int \0))
            (str x))))

(defn sum-digits [x]
  (let [y (sum-once x)]
    (if (< y 10) 
      y
      (recur y))))

The only substantive change to Casey's answer is to replace the recursive call of sum-digits with recur. This recognises tail recursion and implements it by a goto: faster and not subject to stack overflow.


Edit

We can probably (untested) make things faster by calculating the digit sum inline:

(defn sum-digits [x]
  (let [y (loop [x (long x), ans 0]
            (if (zero? x)
              ans
              (recur (quot x 10) (+ ans (mod x 10)))))]
    (if (< y 10) 
      y
      (recur y))))

This avoids layers of sequence functions and the boxing and unboxing that goes with calling global functions.

  • There is no conversion to and from characters.
  • We get the digits right to left.
  • The (long x) conversion is a hint enabling the compiler to infer that everything is a long.

By the way, you can get the same result from the remainder of dividing the original number by 9, except that 0 translates to 9, unless the original number was zero:

(defn sum-digits [n]
  (if (zero? n)
    0
    (let [r (mod n 9)]
      (if (zero? r) 9 r))))
\$\endgroup\$
2
  • \$\begingroup\$ boxing and unboxing that goes with calling global. Could you elaborate that a bit? \$\endgroup\$ Commented Nov 14, 2015 at 14:22
  • \$\begingroup\$ @AseemBansal Arguments are passed to and the result returned from Clojure functions as Java Objects. This is evident in the invoke methods of clojure.core.IFn. So numbers such as longs have to be passed as Objects such as Longs. \$\endgroup\$
    – Thumbnail
    Commented Nov 14, 2015 at 16:10
3
\$\begingroup\$

I was confused by your code until reading the comments. You want to change the name of sum-once to sum-digits or digit-sum (pick this one!) or anything like it, since that's what it really does.

Your current sum-digits should become digital-root (that's the term for what you're trying to achieve).

The digital root (also repeated digital sum) of a non-negative integer is the (single digit) value obtained by an iterative process of summing digits, on each iteration using the result from the previous iteration to compute a digit sum. The process continues until a single-digit number is reached.

\$\endgroup\$
2
\$\begingroup\$

Stylistically this is pretty good. I have two stylistic suggestions, plus a bit of code-golf:

  1. Stylistically, I would prefer for sum-once to take a number and call str there. Also, there are a few standards around "subsidiary" functions - I prefer a "-" prefix. So it would be sum-digits and -sum-digits.
  2. You don't need to convert a string to a char-array - it is already a sequence.

    user=> (seq "4312")
    (\4 \3 \1 \2)
    
  3. This is total code-golf, but you can just subtract individual characters from the ASCII value of 0, which happens to be 48:

    (defn -sum-digits
      [val]
      (reduce + 
              (map #(- (int %) 48) 
                   (str val))))
    
\$\endgroup\$
1
  • \$\begingroup\$ In (3), replace magic number 48 with self-explanatory (int \0)? \$\endgroup\$
    – Thumbnail
    Commented Nov 13, 2015 at 14:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.