为什么Project Euler Problem 12的算法如此之慢?

时间:2010-09-17 17:41:02

标签: performance algorithm scala

我在Scala中为PE P12创建了解决方案,但速度非常慢。有人可以告诉我为什么吗?如何优化这个? calculateDevisors() - 天真的方法和calculateNumberOfDivisors() - 除数函数具有相同的速度:/

import annotation.tailrec

def isPrime(number: Int): Boolean = {
  if (number < 2 || (number != 2 && number % 2 == 0) || (number != 3 && number % 3 == 0))
    false
  else {
    val sqrtOfNumber = math.sqrt(number) toInt

    @tailrec def isPrimeInternal(divisor: Int, increment: Int): Boolean = {
      if (divisor > sqrtOfNumber)
        true
      else if (number % divisor == 0)
        false
      else
        isPrimeInternal(divisor + increment, 6 - increment)
    }

    isPrimeInternal(5, 2)
  }
}

def generatePrimeNumbers(count: Int): List[Int] = {
  @tailrec def generatePrimeNumbersInternal(number: Int = 3, index: Int = 0,
                                            primeNumbers: List[Int] = List(2)): List[Int] = {
    if (index == count)
      primeNumbers
    else if (isPrime(number))
      generatePrimeNumbersInternal(number + 2, index + 1, primeNumbers :+ number)
    else
      generatePrimeNumbersInternal(number + 2, index, primeNumbers)
  }

  generatePrimeNumbersInternal();
}

val primes = Stream.cons(2, Stream.from(3, 2) filter {isPrime(_)})

def calculateDivisors(number: Int) = {
  for {
    divisor &lt;- 1 to number
    if (number % divisor == 0)
  } yield divisor
}

@inline def decomposeToPrimeNumbers(number: Int) = {
  val sqrtOfNumber = math.sqrt(number).toInt

  @tailrec def decomposeToPrimeNumbersInternal(number: Int, primeNumberIndex: Int = 0,
                                               factors: List[Int] = List.empty[Int]): List[Int] = {
    val primeNumber = primes(primeNumberIndex)

    if (primeNumberIndex > sqrtOfNumber)
      factors
    else if (number % primeNumber == 0)
      decomposeToPrimeNumbersInternal(number / primeNumber, primeNumberIndex, factors :+ primeNumber)
    else
      decomposeToPrimeNumbersInternal(number, primeNumberIndex + 1, factors)
  }

  decomposeToPrimeNumbersInternal(number) groupBy {n => n} map {case (n: Int, l: List[Int]) => (n, l size)}
}

@inline def calculateNumberOfDivisors(number: Int) = {
  decomposeToPrimeNumbers(number) map {case (primeNumber, exponent) => exponent + 1} product
}

@tailrec def calculate(number: Int = 12300): Int = {
  val triangleNumber = ((number * number) + number) / 2
  val startTime = System.currentTimeMillis()
  val numberOfDivisors = calculateNumberOfDivisors(triangleNumber)
  val elapsedTime = System.currentTimeMillis() - startTime

  printf("%d: V: %d D: %d T: %dms\n", number, triangleNumber, numberOfDivisors, elapsedTime)

  if (numberOfDivisors > 500)
    triangleNumber
  else
    calculate(number + 1)
}

println(calculate())

5 个答案:

答案 0 :(得分:2)

您可以先检查 的速度是多少。例如,您的主要计算非常非常缓慢。对于每个号码n,您尝试将n除以每个数字从5到sqrt(n),跳过2和3的倍数。不仅不会跳过已知的数字而不是素数,但即使你解决了这个问题,这个算法的复杂性比传统的Eratosthenes筛子还要much worse。请参阅Sieve here的一个Scala实现。

这并不是说你的其余代码也不是次优,但我会把它留给别人。

修改

确实,对Stream的索引访问非常糟糕。这是一个与Stream一起使用的重写,而不是将所有内容都转换为Array。另外,请注意第一个if之前的注释,以查找代码中可能存在的错误。

  @tailrec def decomposeToPrimeNumbersInternal(number: Int, primes: Stream[Int],
                                               factors: List[Int] = List.empty[Int]): List[Int] = {
    val primeNumber = primes.head

    // Comparing primeNumberIndex with sqrtOfNumber didn't make any sense
    if (primeNumber > sqrtOfNumber) 
      factors
    else if (number % primeNumber == 0)
      decomposeToPrimeNumbersInternal(number / primeNumber, primes, factors :+ primeNumber)
    else
      decomposeToPrimeNumbersInternal(number, primes.tail, factors)
  }

答案 1 :(得分:1)

相比......慢?你怎么知道这是Scala的一个问题,而不是你的算法?

无可否认,快速阅读代码表明您可能会一遍又一遍地重新计算素数和其他值。 isPrimeInternal跳出来作为可能出现问题的可能情况。

答案 2 :(得分:0)

你的代码不可编辑,有些部分缺失,所以我猜这里。经常伤害性能的一些事情是在集合中发生装箱/拆箱。我注意到的另一件事是你将你的素数作为一个流 - 这是一件好事 - 但是不要在你的isPrime函数中利用它,它使用一个原始的2,3轮(1和5 mod 6)代替。我可能错了,但试着用

替换它
def isPrime(number: Int): Boolean = {
  val sq = math.sqrt(number + 0.5).toInt
  ! primes.takeWhile(_ <= sq).exists(p => number % p == 0)
}

答案 3 :(得分:0)

我的scala算法,用于计算给定数字的除数。它在解决方案中运行良好 项目欧拉问题12。

def countDivisors(numberToFindDivisor:BigInt):Int = {

def countWithAcc(numberToFindDivisor: BigInt, currentCandidate: Int, currentCountOfDivisors: Int,limit: BigInt): Int = {

  if (currentCandidate >= limit) currentCountOfDivisors

  else {

    if (numberToFindDivisor % currentCandidate == 0)

      countWithAcc(numberToFindDivisor, currentCandidate + 1, currentCountOfDivisors + 2, numberToFindDivisor / currentCandidate)

    else

      countWithAcc(numberToFindDivisor, currentCandidate + 1, currentCountOfDivisors, limit)

  }

}

countWithAcc(numberToFindDivisor, 1, 0, numberToFindDivisor + 1)

}

答案 4 :(得分:-2)

通过仅检查数字平方根的除数,可以大大提高
calculateDivisors。每当你在sqrt下面找到一个除数时,你也会在上面找到一个除数。

def calculateDivisors(n: Int) = {
  var res = 1
  val intSqrt = Math.sqrt(n).toInt
  for (i <- 2 until intSqrt) {
      if (n % i == 0) {
          res += 2
      }
  }
  if (n == intSqrt * intSqrt) {
      res += 1
  }
  res
}