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)))))
3 Answers 3
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 along
.
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))))
-
\$\begingroup\$ boxing and unboxing that goes with calling global. Could you elaborate that a bit? \$\endgroup\$Aseem Bansal– Aseem Bansal2015年11月14日 14:22:54 +00:00Commented Nov 14, 2015 at 14:22
-
\$\begingroup\$ @AseemBansal Arguments are passed to and the result returned from Clojure functions as Java
Object
s. This is evident in theinvoke
methods ofclojure.core.IFn
. So numbers such aslong
s have to be passed asObject
s such asLong
s. \$\endgroup\$Thumbnail– Thumbnail2015年11月14日 16:10:37 +00:00Commented Nov 14, 2015 at 16:10
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.
Stylistically this is pretty good. I have two stylistic suggestions, plus a bit of code-golf:
- Stylistically, I would prefer for
sum-once
to take a number and callstr
there. Also, there are a few standards around "subsidiary" functions - I prefer a "-" prefix. So it would besum-digits
and-sum-digits
. You don't need to convert a string to a
char-array
- it is already a sequence.user=> (seq "4312") (4円 3円 1円 2円)
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))))
-
\$\begingroup\$ In (3), replace magic number
48
with self-explanatory(int 0円)
? \$\endgroup\$Thumbnail– Thumbnail2015年11月13日 14:55:49 +00:00Commented Nov 13, 2015 at 14:55
(sum-digits "4312")
gives 1 \$\endgroup\$edit
you'll seelang-clj
as the language tag. \$\endgroup\$