Edit 2008-04-29: more and better results available here.
I've been waffling about glancing at Python. It comes off as a little ... dry? Indentation drives scope? I was afraid it would be another Objective-C; popular, modeled after successful languages, but just gets stuck in my mental parser. Even Visual Basic and Pascal (q.v. Inside Macintosh) passed that gate.
Anyways, Project Euler seemed like a good way to learn a new language. Problem #1 is summing multiples of 3 or 5 less than one thousand. Some basic iteration handles the task pretty easily -- might as well be all trendy and mix in lambda functions. And, while the hood is open, why not try it in a couple of languages and generate an unscientific performance graph?
So back to the implementation -- the iterative approach is easy. But Python 2.5-ish natively supports set operations like union and intersection. Combine that with a built-in method to generate numeric sequences and the problem is basically solved. For the win -- it outperformed the iterative approach! Oh, and every language tested except Python showed negative numbers when the sum scrolled past 2**31.
Groovy, Python, C, and Emacs Lisp competed for best performance. And by competed, I mean quick and dirty code (esp. Lisp) run a few times on my MacBook Pro. Graphics first, then the code.
The lisp numbers are the sketchiest -- (current-time) only spit out seconds.
Basic info: Groovy 1.5.5 binary release, Python 2.5.1 shipped with Leopard, GCC 4.0.1, Carbon Emacs Spring 2008
Python, iterative
import sys
def multiple_checker(val):
return lambda test: test and val and test % val == 0
mof3 = multiple_checker(3)
mof5 = multiple_checker(5)
sum = 0
for i in range(10 ** int(sys.argv[1])):
if mof3(i) or mof5(i):
sum = sum + i
print sum
Python, set oriented
import sys
exp = int(sys.argv[1])
sum = 0
for mof3or5 in set(range(3, 10**exp, 3)) | set(range(5, 10**exp, 5)):
sum = sum + mof3or5
print sum
Groovy, iterative
exp = Integer.parseInt(args[0])
mof = { val, test -> val && test && test % val == 0 }
mof3 = mof.curry(3)
mof5 = mof.curry(5)
sum = 0 as BigDecimal
(3..<(10**exp)).each {
if (mof3(it) || mof5(it)) {
sum += it
}
}
println sum
Emacs Lisp, iterative (be nice, I'm really weak here)
(defun multiple_of (a b)
(= 0 (% a b))
)
(defun val-to-add (val)
(cond
((multiple_of val 3) val)
((multiple_of val 5) val)
(0)
)
)
(defun sum-of-3or5-multiples (exp)
(let ((num 3) (sum 0.0))
(while (< num (expt 10 exp))
(setq sum (+ sum (val-to-add num)))
(setq num (1+ num))
)
sum
)
)
C, iterative. Not very much code here, would have probably taken more lines of Java!
#include <stdio.h>
#include <math.h>
#define multiple_of(a, b) ((a == 0 || b == 0) ? 0 : a % b == 0)
int main(int argc, char** argv)
{
int exp = atoi(argv[1]);
double sum = 0;
int i;
for (i = 3; i < pow(10, exp); i++)
if (multiple_of(i, 3) || multiple_of(i, 5))
sum += i;
printf("%0.0f\n", sum);
return 0;
}
4 Comments
Leave a comment
0 TrackBacks
Listed below are links to blogs that reference this entry: Nice, Python..
TrackBack URL for this entry: http://www.nearinfinity.com/mt/mt-tb.cgi/484



If you'd def all the variables, you'd get a much better performance on the Groovy example.
And for instance as well using a BigInteger instead of BigDecimal.
Another, probably faster Python solution that still generates individual terms then sums them would be
limit = 10**exp
total =sum(xrange(3, limit, 3)) + sum(xrange(5, limit, 5))
print total
The above uses xrange instead of range so doesn't need space to create large intermediate lists.
I would be interested in finding out how it fared in comparison to your other solutions.
- Paddy.
Just for grins, I decided to test the Groovy performance changes recommended by Guillaume. Simply changing the BigDecimal to BigInteger resulted in only a 3.15% performance boost. However, also changing all the variables to be 'def'd, resulted in a 61.18% improvement. Also removing the currying (which he didn't suggest and I didn't think would make any difference) resulted in a total improvement 67.10%. I added a couple of other small tweaks and was able to get the total improvement down to 70.81%. Not bad but you lose some of the coolness of the language in the process.
I can't believe the Emacs Lisp folks haven't chimed in with optimization hints yet!! Actually, that performed fairly well esp once compiled.
Marc -- would you please send me the optimized solution? I'll use that as the most tweaked Groovy version. It should do way better than this new version using collect() to generate a sequence of multiple like Python's range().
Next week I'll post a followup. I'll try to be more methodical and include other languages.