A while ago I stumbled upon Happy Numbers as explained in
programming praxis, and offered an implementation of them in SQL and in
Emacs Lisp. Yeah, I know. Why not, though?

Today I'm back on that topic and as I'm toying with Common Lisp I though it would be a good excuse to learn me some new tricks. As you can see from the earlier blog entry, last time I did attack the digits problem quite lightly. Let's try a better approach now.
(defun digits (n) "return the list of the digits of N" (nreverse (loop for x = n then r for (r d) = (multiple-value-list (truncate x 10)) collect d until (zerop r))))
As you can see I wanted to use that facility I like very much, the for
x = n then r way to handle first loop iteration differently from the
next ones. But I've been hinted on #lisp that there's a much better way to
write same code:
(defun integer-digits (integer) "stassats version" (nreverse (loop with remainder do (setf (values integer remainder) (truncate integer 10)) collect remainder until (zerop integer))))
That code runs about twice as fast as the previous one and is easier to
reason about. It's using setf and the form setf values, something nice to
discover as it seems to be quite powerful. Let's see how to use it, even if
it's really simple:
CL-USER> (integer-digits 12304501) (1 2 3 0 4 5 0 1)
Let's move on to solving the Happy Numbers problem though:
(defun sum-of-squares-of-digits (integer) (loop with remainder do (setf (values integer remainder) (truncate integer 10)) sum (* remainder remainder) until (zerop integer))) (defun happy? (n &optional seen) "return true when n is a happy number" (let* ((happiness (sum-of-squares-of-digits n))) (cond ((eq 1 happiness) t) ((memq happiness seen) nil) (t (happy? happiness (push happiness seen)))))) (defun find-happy-numbers (limit) "find all happy numbers from 1 to limit" (loop for n from 1 to limit when (happy? n) collect n))
And here's how it goes:
CL-USER> (find-happy-numbers 100)
(1 7 10 13 19 23 28 31 32 44 49 68 70 79 82 86 91 94 97 100)
CL-USER> (time (length (find-happy-numbers 1000000)))
(LENGTH (FIND-HAPPY-NUMBERS 1000000))
took 1,621,413 microseconds (1.621413 seconds) to run.
116,474 microseconds (0.116474 seconds, 7.18%) of which was spent in GC.
During that period, and with 4 available CPU cores,
1,431,332 microseconds (1.431332 seconds) were spent in user mode
145,941 microseconds (0.145941 seconds) were spent in system mode
185,438,208 bytes of memory allocated.
1 minor page faults, 0 major page faults, 0 swaps.
143071
Of course that code is much faster than the one I wrote before both in SQL
and Emacs Lisp, the reason being that instead of writing the number into a
string with (format t "~d" number) then subseq to get them one after the
other, we're now using truncate.
Happy hacking!
Update
It turns out that to solve math related problem, some maths hindsight is helping. Who would have believed that? So if you want to easily get some more performances out of the previous code, just try that solution:
(defvar *depressed-squares* '(0 4 16 20 37 42 58 89 145) "see http://oeis.org/A039943") (defun undepressed? (n) "same as happy?, using a static list of unhappy sums" (cond ((eq 1 n) t) ((member n *depressed-squares*) nil) (t (let ((h (sum-of-squares-of-digits n))) (undepressed? h))))) (defun find-undepressed-numbers (limit) "find all happy numbers from 1 to limit" (loop for n from 1 to limit when (undepressed? n) collect n))
Time to compare:
CL-USER> (time (length (find-happy-numbers 1000000)))
(LENGTH (FIND-HAPPY-NUMBERS 1000000))
took 1,938,048 microseconds (1.938048 seconds) to run.
290,902 microseconds (0.290902 seconds, 15.01%) of which was spent in GC.
During that period, and with 4 available CPU cores,
1,778,021 microseconds (1.778021 seconds) were spent in user mode
140,862 microseconds (0.140862 seconds) were spent in system mode
185,438,208 bytes of memory allocated.
3,320 minor page faults, 0 major page faults, 0 swaps.
143071
CL-USER> (time (length (find-undepressed-numbers 1000000)))
(LENGTH (FIND-UNDEPRESSED-NUMBERS 1000000))
took 1,036,847 microseconds (1.036847 seconds) to run.
5,372 microseconds (0.005372 seconds, 0.52%) of which was spent in GC.
During that period, and with 4 available CPU cores,
1,018,708 microseconds (1.018708 seconds) were spent in user mode
16,982 microseconds (0.016982 seconds) were spent in system mode
2,289,152 bytes of memory allocated.
143071
CL-USER>
Tags
Previous Articles
- PostgreSQL for developers Friday, November 02 2012, 16:22
- Concurrent Hello Sunday, November 04 2012, 23:04
- Editing SQL Tuesday, November 06 2012, 09:55
- About Vimgolf Sunday, November 11 2012, 20:52
Next Articles
- M-x ack Thursday, November 22 2012, 17:36
- Inline Extensions Thursday, December 13 2012, 11:34
- Extensions Templates Tuesday, January 08 2013, 17:53
- Lost in scope Wednesday, January 09 2013, 11:07

