1
\$\begingroup\$

Background

I am doing an exercise in clojure to make a function that can determine if a number is an Armstrong Number or not.

The solution I came up with us a bit more complicated than it would be if there was not a requirement to take 17 digit numbers which start to cause calculation errors with Math/pow etc...

Tests

(ns armstrong-numbers-test
(:require [clojure.test :refer [deftest is testing]]
            [armstrong-numbers :refer [armstrong?]]))

(deftest armstrong-number-9926315

(testing "Seven digit number that is an Armstrong number"
    (is (armstrong? 9926315))))

(deftest not-armstrong-number-9926314
(testing "Seven digit number that is not an Armstrong number"
    (is (not (armstrong? 9926314)))))

(deftest armstrong-number-21897142587612075
(testing "Seventeen digit number that is an Armstrong number"
    (is (armstrong? 21897142587612075))))

My Solution

(ns armstrong-numbers)

(defn less-than-one? [input] (< input 1))
(defn one-tenth [input] (/ (- input (mod input 10)) 10))
(defn for-every-digit [input accumulator accumulator-function]
(if (less-than-one? input)
    accumulator
    (for-every-digit (one-tenth input) (accumulator-function input accumulator) accumulator-function)))
(defn pow-by-reduction [x n] (reduce * (repeat n x)))
(defn armstrong? [num]
(let [num-digits (for-every-digit num 0 (fn [input accumulator] (+ accumulator 1)))]
    (== num (for-every-digit num 0 (fn [input accumulator] (+ accumulator (pow-by-reduction (mod input 10) num-digits)))))))

Update

I worked on it some more, and got it down to this which I am pretty happy with...

(ns armstrong-numbers)

(defn one-tenth [input] (/ (- input (mod input 10)) 10))
(defn pow-by-reduction [x n] (reduce * (repeat n x)))
(defn armstrong? [num]
  (== num (let [seq (map (fn [x] (mod x 10)) (take-while (fn [x] (>= x 1)) (iterate one-tenth num)))]
            (apply + (map (fn [x] (pow-by-reduction x (count seq))) seq)))))
\$\endgroup\$
1
\$\begingroup\$

Here is how I would code it. It takes a lot of care to properly account for BigInteger values. We will use the Google Guava BigInteger lib to augment what we get from Java:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:import
    [java.math RoundingMode]
    [com.google.common.math BigIntegerMath]
    ))

(defn num-digits
  [n]
  (let [n (biginteger n)]
    (inc (BigIntegerMath/log10 n RoundingMode/DOWN))))

(dotest
  (is= 1 (num-digits 1))
  (is= 1 (num-digits 9))
  (is= 2 (num-digits 10))
  (is= 2 (num-digits 99))
  (is= 3 (num-digits 100))
  (is= 3 (num-digits 999))
  (is= 4 (num-digits 1000))
  (is= 4 (num-digits 5555)))

I like to write the tests inline at first. We need a custom remainder function since Guava doesn't have one

(defn bigint-quot-rem
  [n d]
  (let [n    (biginteger n)
        d    (biginteger d)
        quot (BigIntegerMath/divide n d RoundingMode/DOWN)
        rem  (- n (* quot d))]
    [quot rem]))

(dotest
  (is= [1 2] (bigint-quot-rem 5 3))
  (is= [2 0] (bigint-quot-rem 6 3)))

Then a function to extract the digits of the number

(defn digits
  [n]
  (assert (pos? n))
  (loop
    [accum []
     value (biginteger n)]
    (if (zero? value)
      (mapv biginteger (reverse accum))
      (let [[value-next last-digit] (bigint-quot-rem value 10)
            accum-next (append accum last-digit)]
        (recur accum-next value-next)))))

(dotest
  (is= [3 1 4] (digits 314))
  (is= [1] (digits 1))
  (is= [1 3] (digits 13)))

and finally we can test for the armstrong property

(defn armstrong?
  [n]
  (let [n          (bigint n)
        ndigits    (num-digits n)
        digits     (digits n)
        digits-exp (mapv #(.pow % ndigits)
                     digits)
        powsum     (apply + digits-exp)
        result     (= n powsum)]
    result))

(dotest

  (is= 3 (num-digits 151))
  (isnt (armstrong? 151))
  (is (armstrong? 153))
  (isnt (armstrong? 154))

  (is (armstrong? 9926315))
  (isnt (armstrong? 9926314))

  (is (armstrong? 4679307774N))
  (is (armstrong? 28116440335967N))
  (is (armstrong? 63105425988599693916N))
  (is (armstrong? 188451485447897896036875N))
  (is (armstrong? 115132219018763992565095597973971522401N))

  (is (armstrong? 21897142587612075N))
  )

with result

-----------------------------------
   Clojure 1.10.3    Java 15.0.2
-----------------------------------

Testing tst.demo.core

Ran 5 tests containing 25 assertions.
0 failures, 0 errors.

The above is using my favorite template project. Extra test values are from

https://codegolf.stackexchange.com/questions/83272/all-armstrong-numbers

Note also that, in Clojure, bigint is different than biginteger.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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