项目Euler 7 Scala问题

时间:2010-03-11 18:48:53

标签: arrays scala mutable

我试图使用scala 2.8

来解决Project Euler第7期问题

我实施的第一个解决方案需要大约8秒

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;
}

后来我尝试了同样的问题而没有在数组缓冲区中存储素数。这需要.118秒。

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;
}

我尝试在Scala中使用各种可变数组/列表实现,但无法更快地解决问题。我不认为将Int存储在大小为10001的数组中可能会使程序变慢。有没有更好的方法在scala中使用列表/数组?

4 个答案:

答案 0 :(得分:5)

这里的问题是ArrayBuffer是参数化的,所以它真正存储的是对Object的引用。对Int的任何引用都会根据需要自动装箱和取消装箱,这使得它非常慢。使用Scala 2.7的速度非常慢,它使用Java原语来做到这一点,这非常缓慢。 Scala 2.8采用了另一种方法,使其更快。但任何拳击/拆箱都会让你失望。此外,您首先在堆中查找ArrayBuffer,然后再次查找包含java.lang.Integer的{​​{1}} - 两次内存访问,这使得它比您的其他解决方案慢

当Scala集合变得专业化时,它应该更快。是否应该足以击败你的第二个版本,我不知道。

现在,您可以采取的措施是使用Int代替。因为Java的Array没有被删除,所以你可以避免装箱/拆箱。

此外,当您使用for-comprehensions时,您的代码将有效地存储在为每个元素调用的方法中。所以你也在进行很多方法调用,这是另一个原因,这是另一个原因。唉,有人为Scala编写了一个插件,该插件优化了至少一个for-comprehension案例,以避免这种情况。

答案 1 :(得分:2)

使用Array应该使用正确的算法使其在大约零秒内工作。例如,这在我的系统上大约需要7毫秒:

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)
  }
}

结果(第二次通话;首先有一些开销):

scala> Primes.timedGet(10001)
res1: (Int, Double) = (104743,0.00683394)

答案 2 :(得分:1)

我认为你必须开箱即用:)

由于问题可以解决,您可以使用Sieve of Eratosthenes非常有效地解决问题。

答案 3 :(得分:1)

这是一个递归解决方案(使用第一个解决方案中的isPrime函数)。喜欢不变性似乎是不错的Scala风格(即尽量不使用var)所以我在这里做了(实际上没有var s或val s !)。我这里没有Scala安装,所以无法判断这实际上是否更快!

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)
}