Nice, Python.

| | Comments (4) | TrackBacks (0)

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.


perf graph


perf stats

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

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.

Paddy3118 said:

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.

Marc said:

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.

Seth said:

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.

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