2011/2/16 Marek Stępniowski <mstepniow...@gmail.com>:
> On Thu, Feb 17, 2011 at 12:34 AM, HB <hubaghd...@gmail.com> wrote:
>> I'm trying to write a function that determines if a number is a prime
>> or not.
>> Here is my first shot:
>>
>> (defn prime? [num]
>>  (loop [i 2]
>>    (if (<= (* i i) num)
>>      false)
>>    (recur (inc i)))
>>  true)
>>
>> It is not working to be sure :)
>>
>> Please blow my mind with various implementations.
>
> It seems that you're mixing loop expression in Clojure with a Java
> loop. The loop in Clojure is just a binding and a point to which each
> recur will jump, instead of the start of function. You're lacking a
> stop condition in the loop.
>
> Here is the solution similiar to yours, but with a proper accumulator
> and stop condition:
>
> (defn prime? [n]
>  (let [m (Math/sqrt n))]
>   (loop [k 2]
>     (cond
>      (> k m) true
>      (zero? (mod n k)) false
>      true (recur (inc k))))))
>
> IMHO more idiomatic Clojure solution, using seq operations:
>
> (defn prime? [n]
>  (if (< n 2)
>   false
>   (not-any? #(zero? (rem n %))
>             (range 2 (min (inc (Math/sqrt n)) n)))))

This sometimes tests one higher number than it should:

user=> (defn prime? [n]
 (if (< n 2)
  false
  (not-any? #(zero? (do (println %) (rem n %)))
            (range 2 (min (inc (Math/sqrt n)) n)))))
#'user/prime?
user=> (prime? 47)
2
3
4
5
6
7
true
user=>

Coercing (Math/sqrt n) to int fixes this, as it truncates 6.92 to 6 so
it calls (range 2 7) rather than (range 2 7.92). The latter includes 7
and the former omits it. But if n is an integer square it makes no
difference: (Math/sqrt 49) is 7.0, (int 7.0) is 7, (inc 7) is 8, and
(range 2 8) includes 7, so (prime? 49) doesn't incorrectly report
true.

Perfect squares are also worst-case (other than actual primes), and
for positive integers greater than or equal to 2, the (min ... n)
around everything also should never make a difference, so:

(defn prime? [n]
  (if-not (< n 2)
    (let [x (Math/sqrt n)
          y (int x)]
      (if-not (== x y)
        (not-any? #(zero? (do (println %) (rem n %)))
          (range 2 (inc y)))))))

could be a bit more efficient. (It's likely to depend on your data. If
squares are common in the data this may help; if not the (== x y) test
may not be worth it.)

Another clear optimization is

user=> (defn prime? [n]
  (if-not (< n 2)
    (let [x (Math/sqrt n)
          y (int x)]
      (if-not (== x y)
        (not-any? #(zero? (do (println %) (rem n %)))
          (cons 2
            (take-nth 2
              (range 3 (inc y)))))))))
#'user/prime?
user=> (prime? 149)
2
3
5
7
9
11
true

This skips testing for divisibility by even numbers higher than 2.

In fact that can be taken further, to testing only divisibility by
/primes/ up to sqrt(n):

user=> (defn prime? [n]
  (if-not (< n 2)
    (let [x (Math/sqrt n)
          y (int x)]
      (if-not (== x y)
        (not-any? #(zero? (do (println %) (rem n %)))
          (filter user/prime? (range 2 (inc y))))))))
#'user/prime?
user=> (def prime? (memoize prime?))
#'user/prime?
user=> (prime? 149)
2
2
2
2
2
3
2
3
2
3
2
3
5
7
11
true
user=> (prime? 151)
2
3
5
7
11
true
user=>

This last of course gobbles memory remembering earlier primes. The
"user/prime?" is required in 1.2 for the function to call the memoized
version of itself that is created after. It might be desired to hoist
the var lookup out of the loop with

(defn prime? [n]
  (if-not (< n 2)
    (let [x (Math/sqrt n)
          y (int x)]
      (if-not (== x y)
        (let [prime? user/prime?]
          (not-any? #(zero? (do (println %) (rem n %)))
            (filter prime? (range 2 (inc y)))))))))

and one can add further optimizations, particularly effective at large
n, by adding a probable-prime filter -- wrap the whole n >= 2 case in
(if (probable-prime? n) ...) and put a cheap probable-prime test in
probable-prime?, that is, a test that returns true reliably for prime
numbers and false for vast swathes of composite numbers. For truly
enormous n you'll want to lose the memoize/filter prime? part and
instead use filter probable-prime? on the range, as well as (if
(probable-prime? n) ...) around the bulk of the function, so as not to
blow the heap. Wikipedia will have plenty of information on
probable-prime tests, such as testing if a number is pseudo-prime to
some base (there are only 245 composite pseudo-primes to base 2 below
a million). Other probable-prime tests, such as randomly attempting
elliptic curve factorizations, come into their own for larger n. There
are also absolute primality tests that don't work by directly checking
divisibility by all the primes up to the number's square root, but
they are inefficient except for very large numbers, where they can
become superior. See http://en.wikipedia.org/wiki/Primality_test for
more on these topics.

Making all of this even more efficient is possible via resorting to
loop/recur with primitive math, of course.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to