Optimizations Generating Armstrong Numbers

Total Page:16

File Type:pdf, Size:1020Kb

Optimizations Generating Armstrong Numbers

Optimizations Generating Armstrong Numbers 1 Timothy J Rolfe Department of Computer Science Eastern Washington University 319F CEB Cheney, WA 99004-2493 USA [email protected] http://penguin.ewu.edu/~trolfe/

Abstract This article explores various optimizations that can be used to speed the discovery of Armstrong numbers (also known as Narcissistic numbers): “an n-digit number that is the sum of the nth powers of its digits”.1 Optimizations: compare the parity of the least-significant digit of the number and that of the sum of its digits; use a dynamic programming approach for the powers of digits; examine the parity of the sum of digits excluding the least-significant digit; compute forward bounds on the possible numbers and on the possible sums of d n. The final optimization completes in about 2% of the time of the initial implementation.

General Terms Algorithms, Performance

Keywords Armstrong Number, Narcissistic Number, Optimization

Categories and Subject Descriptors D.2.8 Metrics — Performance measures F.2.2 Nonnumerical Algorithms and Problems — Computations on discrete structures

Introduction An Armstrong number (also known as a Narcissistic number) is defined thus: “an n-digit number that is the sum of the nth powers of its digits”.2 In 1940 the mathematician G. H. Hardy dismissed Armstrong numbers:

(a) 8712 and 9801are the only four-figure numbers which are integral multiples of their ‘reversals’: 8712 = 4  2178, 9801 = 9  1089 and there are no other numbers below 10,000 which have this property.

1 © ACM, (2011). This is the author’s version of the work. It is posted here by permission of ACM for your personal use. Not for redistribution. The definitive version was published in Inroads, Vol 2, No. 2, (June 2011) http://dx.doi.org/10.1145/1963533.1963548.

Printed 2018/Apr/5 at 11:19 Page 1 Optimizations Generating Armstrong Numbers Page 2

(b)There are just four numbers (after 1) which are the sums of the cubes of their digits, viz. 153 = 13 + 53 + 33, 370 = 33 + 73 + 03 371 = 33 + 73 + 13, 407 = 43 + 03 + 73. These are odd facts, very suitable for puzzle columns and likely to amuse amateurs, but there is nothing in them which appeals much to a mathematician.3

While Hardy does credit his source (Rouse Ball’s Mathematical Recreations, 11th edition, 1939), Hardy has chosen to “take two, almost at random”, and consequently does not give any page references. While the EWU library provides the author access to the 12th edition (University of Toronto Press, 1974), it runs to xvii + 428 pages.

It would appear there are plenty of amateurs around to be amused by Hardy’s instance (b). There is the connection to an oddly specific number of fish reported in John’s Gospel: “So Simon Peter got into the boat, and hauled the net ashore, full of large fish, a hundred and fifty- three of them.” 4 Plenty of ink has flowed trying to motivate such a specific number. (For instance, it is the 17th triangular number, that is, the sum of integers from 1 to 17.)

Lionel Deimel discussed this example as an instance of “Digital Invariants” (specifically of pluperfect digital invariants).5 (Martin Gardner has a discussion of such numbers as well in The Incredible Dr. Matrix.6) Deimel later reported on the origin of the name “Armstrong numbers” as referring to Michael F. Armstrong7 in elementary courses in Fortran at the University of Rochester around 1966. The image of the two-page “A Brief Introduction to Armstrong Numbers” is available through the URL given in the end-notes.8

Web searches provide copious examples of programs to test for the Armstrong number characteristic, but not very many for generating such numbers in large amounts. This article will address generating the Armstrong numbers that will fit within 32-bit integers and look for various optimizations in generating those numbers.

Base Case The brute-force implementation is fairly obvious: using a backtracking recursive program, cycle the most significant digit from 1 through 9, and cycle all other digits from 0 through 9. [Thus this is inherently an order-10n problem.] For each n-digit number so generated, test it for the Armstrong characteristic. In the base implementation, the number acting as the base for the computation is passed as a character string, along with the index idx of the digit to be cycled. Effectively this insures processing from the most significant digit down to the least significant — big-endian behavior as far as subscripts are concerned. The substring from 0 to (idx–1) is deemed to be fixed; the digit in the idxth position will be cycled from its current contents up through 9, while the substring starting at (idx+1) contains the digits still to be processed. The initial state is always 1 followed by n–1 zeroes. In the recursion, if we receive a base string of length n and the index idx is equal to n, then a full candidate number has been generated.

Version of 2018/Apr/5 at 11:19 Optimizations Generating Armstrong Numbers Page 3

Compute the appropriate sum and compare it with the value of the base string. As Java code, this is the implementation: static boolean discover(String base, int idx) { int n = base.length(); if (idx == n) // We have a full one { int digit[] = new int[n], sum = 0, k;

for (k = 0; k < n; k++) digit[k] = base.charAt(k) - '0'; for (k = sum = 0; k < n; k++) sum += (int) Math.pow(digit[k], n); if (sum != Integer.valueOf(base)) return false; // Report the successful Armstrong number System.out.printf("%d^%d", digit[0], n); for (k = 1; k < n; k++) System.out.printf(" + %d^%d", digit[k], n); System.out.printf(" = %d\n", sum); return true; } else { String front = base.substring(0, idx), back = base.substring(idx+1); char digit = base.charAt(idx); boolean rtn = false;

for ( ; digit <= '9'; digit++ ) // Avoid short-circuit evaluation's disabling recursion rtn = discover(front + digit + back, idx+1) || rtn; return rtn; } }

Running this code on the fastest computer to which I have access, computing the Armstrong numbers of orders 1 through 9 required 2282.34 seconds

First Optimization: Parity The first optimization flows from recognizing that the parity of the sum of d n is the same as the parity of the sum of d: for n > 0, d n will be the same parity as that of d. Thus if the parity of the sum of d differs from the parity of the least significant digit, this cannot be a solution. That means that half of the candidates can be discarded without computing the sum of d n. Only a few

Version of 2018/Apr/5 at 11:19 Optimizations Generating Armstrong Numbers Page 4 extra lines are required: as the individual digits are extracted, build also the sum of those digits. Then check that digit sum modulo 2 with the least significant digit modulo 2.

for (k = 0; k < n; k++) { digit[k] = base.charAt(k) - '0'; sum += digit[k]; } if (sum%2 != digit[n-1]%2) return false;

Not surprisingly, this cuts approximately in half the time required since a major component of the calculation (after the recursion) is computing the sum of d n, something that can be avoided in approximately half of the potential final solutions, reducing the time to 1205.801 seconds, about 53% of the time required when parity is not checked.

Second Optimization: Dynamic Programming The most significant time saving available, though, is to recognize that the major expense of the check is computing d n, which is done repeatedly, and this is an obvious circumstance in which dynamic programming / memorization can be used: for the digits 0 through 9, compute a table of d n for the largest n expected. To accomplish this, one can insert code to generate a matrix power[][] holding, for the digits 0 through 9, the powers from 0 through the largest value of n to be used. This is a one-time expense. In return, Math.pow(digit[k], n) in the calculations can be replaced with power[digit[k]][n]. Computing the power has the expense of the recursive call and order-log(n) operations for the divide-and-conquer calculation9. This is replaced with a constant-time operation. Time required now reduces to 152.932 seconds, about 13% of the time required for the first optimization above.

Third Optimization: Shift the Parity Check The third optimization grew out of what was expected to be a minor improvement: before, when there is a potential solution, the sum of d n was done in the final check. It is, however, possible to build a partial sum representing the fixed portion of solution in progress. The parameter list is expanded to include “int partial”, with zero as the initial value while determining the most significant digit. Then the recursive portion can build the partial sum in the recursive call.

for ( ; digit <= '9'; digit++ ) { String fwd = front + digit + back; int pp1 = partial+power[digit-'0'][n];

// Avoid short-circuit evaluation's disabling recursion rtn = discover(fwd, pp1, idx+1) || rtn; }

Version of 2018/Apr/5 at 11:19 Optimizations Generating Armstrong Numbers Page 5

This generates a small improvement, about 5%. What it does give us, though, is the opportunity to shift the parity check from idx == n to occur at idx == n–1, that is, at the point at which we are determining the least-significant digit. We know that the over-all number must agree in parity with the least-significant digit. That means that the partial sum not including the least-significant digit must be even for that digit to drive the parity.

if (idx == n-1) // Doing lowest-order digit // Check parity; if even, lowest-order digit determines. // Otherwise this cannot generate a solution. if (partial%2 == 1) // Odd parity return false;

In effect, one can discard half of the potential solutions, and that is borne out: in the captured timing statistics. Time required now reduces to 81.592 seconds, about 53% of the time for the second optimization.

Final Optimization: Forward Bound on Partial Solutions When one is dealing with backtracking, it is useful to consider feasibility of partial solutions, and to truncate the decision tree at the point that the partial solution is infeasible.10 That is what the third optimization does, declaring a partial solution infeasible if the (n–1) sum of d n has odd parity. This can be expanded to look at the range of the possible solutions (as xyz00… through xyz99…) versus the range of the possible summations of d n (xn + yn + zn as lower bound and xn + yn + zn + (n–idx)*9n as upper bound). One must, of course, beware of integer overflow in computing these bounds.

// Range as xyz00... through xyz99... int lower = Integer.valueOf(base), upper = lower + (int)Math.pow(10, n-idx)-1; // Lower possible value is partial for trailing zeros int highv = partial + power[9][n]*(n-idx); // Trailing nines

// Bounds check: [partial..highv] vs. [lower..upper] if (highv < lower || partial > upper) { if (DEBUG) System.out.printf("Reject: %d to %d vs. %d to %d\n", partial, highv, lower, upper); return false; }

Adding debugging code allows one to find the number of potential n-digit candidates that are eliminated by this bounding. There is no pruning for order-1 and order 2, of course.

Version of 2018/Apr/5 at 11:19 Optimizations Generating Armstrong Numbers Page 6

Order 3 4 5 6 7 8 Candidates omitted 110 2,270 29,210 333,940 3,682,310 38,957,860

Time required now reduces to 41.872 seconds, about 51% of the time for the third optimization. Overall, from start to finish, the final time of 41.872 seconds is approximately 2% of the initial time required, 2282.34 seconds.

Summary The five implementations discussed above were run to generate the Armstrong numbers from order-1 up through order-9. Since the language is Java, the wall-clock time was captured by means of System.nanoTime() — the computers were unloaded except for the program and operating system overhead, so that this presumably gives a good processing time estimate. One computer is the author’s desk computer running Windows-XP; the other, the fastest computer to which the author has ready access, running the Ubuntu implementation of Linux. The table shows both the time required for each implementation (base level, then the four optimizations in the order they are shown above) and then the ratio of the two adjacent times, as base/opt.1, opt.1/opt2, opt.2/opt3, and opt.3/opt4. The final line shows the over-all time ratios.

Office Desk Computer Intel(R) Xeon(R) 3.00GHz CPU Opt. level Time Ratio Time Ratio Base level 6790.461 2282.34 Opt. 1 3830.953 1.773 1205.801 1.893 Opt. 2 653.782 5.86 152.932 7.885 Opt. 3 372.299 1.756 81.592 1.874 Opt. 4 199.302 1.868 41.872 1.949 Base level/Opt.4 34.071 Base level/Opt.4 54.508

The author began experimenting with Armstrong numbers as a potential problem for the ACM International Collegiate Programming Contest’s Pacific Northwest regional contest, and was trying to find out how many numbers can be computed in 120 seconds, the “Time Limit Exceeded” value in use for the regional contest. It was not, however, used for the contest because this could easily be subject to “pre-computing”: on the team’s machine, execute an inefficient algorithm in the background, collecting the answers. By the structure of this problem, that means a ragged two dimensional matrix giving the solutions for n from 1 through 9. The program submitted for judging, then, simply initializes that matrix and prints out the numbers for each requested value of n.

http://penguin.ewu.edu/~trolfe/Armstrong/ provides access to the five Java programs and the Excel workbook with the numerical experiment described above.

Version of 2018/Apr/5 at 11:19 Optimizations Generating Armstrong Numbers Page 7

Acknowledgement The author freely admits to mining the Wikipedia page on this topic and its references while preparing the introduction.11

Version of 2018/Apr/5 at 11:19 1 Weisstein, Eric W. "Narcissistic Number." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/NarcissisticNumber.html. Accessed 2010 December 15.

2 Ibid.

3 G. H. Hardy, A Mathematician’s Apology (First Electronic Edition, Version 1.0; University of Alberta Mathematical Sciences Society, March 2005), pp. 24-25. Accessible through http://www.math.ualberta.ca/~mss/misc/A%20Mathematician's%20Apology.pdf. Accessed 2010 December 20.

4 John 21:11, taken from The Complete Bible: An American Translation, Old Testament translation by J.M.Powis Smith et al., the Apocrypha and New Testament translated by Edgar J. Goodspeed (The University of Chicago Press, 1939), New Testament p. 109.

5 Lionel Deimel, http://www.deimel.org/rec_math/DI_0.htm. Access 2010 December 20.

6 Martin Gardner, The Incredible Dr. Matrix (Charles Scribner’s Sons, 1976) p. 36, but especially pp. 205-209.

7 Lionel Deimel, http://www.deimel.org/rec_math/DI_6.htm. Accessed 2010 December 20.

8 http://www.deimel.org/rec_math/armstrong.pdf. Accessed 2010 December 20.

9 Anany Levitin, Introduction to the Design and Analysis of Algorithms (2nd edition; Pearson Education, Inc., 2007) p. 159. Also discussed in Thomas H. Cormen et al., Introduction to Algorithms (3rd edition; MIT Press, 2009), pp. 956-58.

10 Sanjoy Dasgupta et al., Algorithms (McGraw Hill Higher Education, 2008) pp. 272 ff.

11 http://en.wikipedia.org/wiki/Narcissistic_number. Accessed 2010 December 20.

Recommended publications