August 29, 2018

Exercism - Armstrong Numbers


An Armstrong number is a number that is the sum of its own digits each raised to the power of the number of digits.

For example:

  • 9 is an Armstrong number, because 9 = 91 = 9
  • 10 is not an Armstrong number, because 10 != 12 + 02 = 1
  • 153 is an Armstrong number, because: 153 = 13 + 53 + 33 = 1 + 125 + 27 = 153
  • 154 is not an Armstrong number, because: 154 != 13 + 53 + 43 = 1 + 125 + 64 = 190

Write some code to determine whether a number is an Armstrong number.

(ns armstrong-numbers
  (:require [cljs.test :refer-macros [deftest testing is run-tests]]))

(defn armstrong? [n]
(deftest armstrong-number-5
  (testing "Single digit numbers are Armstrong numbers"
    (is (armstrong? 5))))

(deftest not-armstrong-number-10
  (testing "There are no 2 digit Armstrong numbers"
    (is (not (armstrong? 10)))))

(deftest armstrong-number-153
  (testing "Three digit number that is an Armstrong number"
    (is (armstrong? 153))))

(deftest not-armstrong-number-100
  (testing "Three digit number that is not an Armstrong number"
    (is (not (armstrong? 100)))))

(deftest armstrong-number-9474
  (testing "Four digit number that is an Armstrong number"
    (is (armstrong? 9474))))

(deftest not-armstrong-number-9475
  (testing "Four digit number that is not an Armstrong number"
    (is (not (armstrong? 9476)))))

(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)))))

This was my first iteration.

Note: The code on this page is modified slightly for Clojurescript. js/parseInt is JavaScript interop and will not work in Clojure. Instead, use Character/digit or Integer/parseInt. Or better yet - see below for a way to do it with math.

(defn digits [n]
  (map #(js/parseInt (str %))  (seq (str n))))

(defn exp [x n]
  (reduce * (repeat n x)))

(defn numdigits [n]
  (count (digits n)))

(defn multiplier [n]
  (map #(exp % (numdigits n)) (digits n)))

(defn adder [n]
  (reduce + (multiplier n)))

(defn armstrong? [n]
  (= (adder n) n))

I tried to tackle the problem in multiple stages, defining functions that each get us one step closer to the solution. It worked, but my mentor encouraged me to try a more idiomatic approach. So I basically took the functions and squashed them together:

(defn digits [n]
  (map #(js/parseInt (str %))
    (seq (str n))))

(defn armstrong [n]
  (reduce + (map #(Math/pow % (count (digits n)))
    (digits n))))

(defn armstrong? [n]
  (== (armstrong n) n))

Also by using Java's Math/pow I got to eliminate the exponent function. I decided to take this and run with it, however we still could do better:

(defn armstrong? [n]
  (let [digits (->> n str (map #(js/parseInt % 10)))]
    (->> digits
         (map #(Math/pow % (count digits)))
         (apply +)
         (== n))))

The difference is mostly stylistic, but readability ought to be a strong priority. By using the threading macros, it is much easier to discern the order of operations.

Here's how to get the digits of a number without turning it into a string or using interop:

(defn digits [n]
  (if (>= n 10)
      (conj (digits (quot n 10))
            (rem n 10))
(digits 1234)
Tags: coding exercises KLIPSE Exercism Clojure