views:

319

answers:

4

I was trying to solve Project Euler problem number 7 using scala 2.8

First solution implemented by me takes ~8 seconds

def problem_7:Int = {
    var num = 17;
    var primes = new ArrayBuffer[Int]();
    primes += 2
    primes += 3
    primes += 5
    primes += 7
    primes += 11
    primes += 13

    while (primes.size < 10001){

        if (isPrime(num, primes)) primes += num
        if (isPrime(num+2, primes)) primes += num+2

        num += 6
    }
    return primes.last;
}

def isPrime(num:Int, primes:ArrayBuffer[Int]):Boolean = {
    // if n == 2 return false;
    // if n == 3 return false;
    var r = Math.sqrt(num)  
    for (i <- primes){
        if(i <= r ){
            if (num % i == 0) return false;
        }
    }
    return true;
}

Later I tried the same problem without storing prime numbers in array buffer. This take .118 seconds.

def problem_7_alt:Int = {
    var limit = 10001;
    var count = 6;
    var num:Int = 17;

    while(count < limit){

        if (isPrime2(num)) count += 1;
        if (isPrime2(num+2)) count += 1;

        num += 6;
    }
    return num;
}

def isPrime2(n:Int):Boolean = {
    // if n == 2 return false;
    // if n == 3 return false;

    var r = Math.sqrt(n)
    var f = 5;
    while (f <= r){
        if (n % f == 0) {
            return false;
        } else if (n % (f+2) == 0) {
            return false;
        }
            f += 6;
    }
    return true;
}

I tried using various mutable array/list implementations in Scala but was not able to make solution one faster. I do not think that storing Int in a array of size 10001 can make program slow. Is there some better way to use lists/arrays in scala?

+1  A: 

I think you have to think out of the box :)

Because the problem is manageable, you can use Sieve of Eratosthenes to solve it very efficiently.

AraK
I wanted to experiment with collection library.Sieve's approach is mentioned for problem 3 @ scala bloghttp://www.scala-blogs.org/2007/12/project-euler-fun-in-scala.html
Nishu
+1  A: 

Here's a recursive solution (using the isPrime function from your first solution). It seems to be good Scala style to prefer immutability (i.e. to try not to use vars) so I've done that here (in fact there are no vars or vals!). I don't have a Scala installation here though so can't tell if this is actually any quicker!

def problem_7:Int = { 
  def isPrime_(n: Int) = (n % 6 == 1 || n % 6 == 5) && isPrime(n)
  def process(n: Int, acc: List[Int]): Int = {
    if (acc.size == 10001) acc.head
    else process(n+1, if isPrime_(n) n :: acc else acc) 
  }
  process(1, Nil)
}
ams
+3  A: 

The problem here is that ArrayBuffer is parameterized, so what it really stores are references to Object. Any reference to an Int is automatically boxed and unboxed as needed, which makes it very slow. It is incredibly slow with Scala 2.7, which uses a Java primitive to do that, which does it very slowly. Scala 2.8 takes another approach, making it faster. But any boxing/unboxing will slow you down. Furthermore, you are first looking up the ArrayBuffer in the heap, and then looking up again for java.lang.Integer containing the Int -- two memory accesses, which makes it way slower than your other solution.

When Scala collections become specialized, it should be plenty faster. Whether it should be enough to beat your second version or not, I don't know.

Now, what you may do to get around that is to use Array instead. Because Java's Array are not erased, you avoid the boxing/unboxing.

Also, when you use for-comprehensions, your code is effectively stored in a method which is called for each element. So you are also making many method calls, which is another reason this is slower. Alas, someone wrote a plugin for Scala which optimizes at least one case of for-comprehensions to avoid that.

Daniel
using Array makes it work in 2 seconds. var primes = new Array[Int](10001);Still. trying to figure out how to use java.lang.Integer
Nishu
@Nishu Scala automatically box and unbox `Int`. I'm not sure you'll gain anything by using `java.lang.Integer` directly.
Daniel
+2  A: 

Using Array should make it work in about zero seconds with the right algorithm. This, for example, takes about 7 milliseconds on my system:

class Primes(bufsize: Int) {
  var n = 1
  val pbuf = new Array[Int](bufsize max 1)
  pbuf(0) = 2
  def isPrime(num: Int): Boolean = {
    var i = 0
    while (i < n && pbuf(i)*pbuf(i) <= num) {
      if (num % pbuf(i) == 0) return false
      i += 1
    }
    if (pbuf(i)*pbuf(i) < num) {
      i = pbuf(i)
      while (i*i <= num) {
        if (num % i == 0) return false
        i += 2
      }
    }
    return true;
  }
  def fillBuf {
    var i = 3
    n = 1
    while (n < bufsize) {
      if (isPrime(i)) { pbuf(n) = i; n += 1 }
      i += 2
    }
  }
  def lastPrime = { if (n<bufsize) fillBuf ; pbuf(pbuf.length-1) }
}
object Primes {
  def timedGet(num: Int) = {
    val t0 = System.nanoTime
    val p = (new Primes(num)).lastPrime
    val t1 = System.nanoTime
    (p , (t1-t0)*1e-9)
  }
}

Result (on second call; first has some overhead):

scala> Primes.timedGet(10001)
res1: (Int, Double) = (104743,0.00683394)
Rex Kerr
Creating array of given size (given approach) seems to be running quite fast. I think time can be further reduced by calculating sqrt(num) once instead of multiplying pbuf(i)*pbuf(i) in every iteration
Nishu
@Nishu: Agreed, `sqrt(num)` is better for thousands of primes. Otherwise, it's about 100x more expensive than integer multiply.
Rex Kerr
@Rex i tried to use http://en.wikipedia.org/wiki/Fast_inverse_square_rootIt does not provide a noticeable difference in this particular case though. Overkilling problem?
Nishu
@Nishu: Probably, since integer division is a lot more expensive than integer multiplication (haven't measured it lately; used to be at least 10x), and conditionals are also relatively expensive. Almost all the time is taken up in the `num % i == 0` test, I imagine.
Rex Kerr