Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't this running in constant space (and how do I make it so it does)?

Tags:

clojure

I'm doing Project Euler to learn Clojure.

The purpose of this function is to calculate the lcm of the set of integers from 1 to m.

(lcm 10) returns 2520

This is a rather brute-force way of doing this. In theory, we go through each number from m to infinity and return the first number for which all values 1 through m divide that number evenly.

If I understand what 'lazy' means correctly (and if I am truly being lazy here), then this should run in constant space. There's no need to hold more than the list of numbers from 1 to m and 1 value from the infinite set of numbers that we're looping through.

I am, however, getting a java.lang.OutOfMemoryError: Java heap space at m values greater than 17.

 (defn lcm [m]
  (let [xs (range 1 (+ m 1))]
  (first (for [x (iterate inc m) :when 
          (empty? 
              (filter (fn [y] (not (factor-of? y x))) xs))] x))))

Thanks!

like image 648
Tyler Avatar asked Jun 20 '10 09:06

Tyler


People also ask

What does it mean to use constant space?

Constant space is the one that is fixed for that algorithm, generally equals to space used by input and local variables. Auxiliary Space is the extra/temporary space used by an algorithm.

What does it mean to have constant space complexity?

O(1) Complexity: We consider constant space complexity when the program doesn't contain any loop, recursive function, or call to any other functions.

Is space complexity always constant?

We can clearly see that the space complexity is constant, so, it can be expressed in big-O notation as O(1). Again, let's list all variables present in the above code: array – the function's only argument – the space taken by the array is equal 4n bytes where n is the length of the array. size – a 4-byte integer.

Why do we disregard constants when calculating the big-O?

Note that the big-O expressions do not have constants or low-order terms. This is because, when N gets large enough, constants and low-order terms don't matter (a constant-time algorithm will be faster than a linear-time algorithm, which will be faster than a quadratic-time algorithm).


1 Answers

As far as I can tell, your code is in fact lazy (also in the sense that it's in no hurry to reach the answer... ;-) -- see below), however it generates piles upon piles upon piles of garbage. Just consider that (lvm 17) amounts to asking for over 1.2 million lazy filtering operations on (range 1 18). I can't reproduce your out-of-memory problem, but I'd tentatively conjecture it might be an issue with your memory & GC settings.

Now although I realise that your question is not actually about algorithms, note that the production of all that garbage, the carrying out of all those filtering operations etc. not only utterly destroy the space complexity of this, but the time complexity as well. Why not use an actual LCM algorithm? Like the one exploiting lcm(X) = gcd(X) / product(X) for X a set of natural numbers. The GCD can be calculated with Euclid's algorithm.

(defn gcd
  ([x y]
     (cond (zero? x) y
           (< y x)   (recur y x)
           :else     (recur x (rem y x))))
  ([x y & zs]
     (reduce gcd (gcd x y) zs)))

(defn lcm
  ([x y] (/ (* x y) (gcd x y)))
  ([x y & zs]
     (reduce lcm (lcm x y) zs)))

With the above in place, (apply lcm (range 1 18)) will give you your answer in short order.

like image 177
Michał Marczyk Avatar answered Oct 05 '22 10:10

Michał Marczyk