views:

297

answers:

4

Hi,

I have created solution for PE P12 in Scala but is very very slow. Can somebody can tell me why? How to optimize this? calculateDevisors() - naive approach and calculateNumberOfDivisors() - divisor function has the same speed :/

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())
A: 

calculateDivisors can be greatly improved by only checking for divisors up to the square root of the number. Each time you find a divisor below the sqrt, you also find one above.

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
}
Kim
calculateDivisors returns devisors "as is" not the number of devisors, this is a test code
Evil Ipos
@Evil Ipos - you can still only go up to the square root. Use the fact that if `x` is a divisor of `n`, then `n / x` is also a divisor of `n`. Watch out for the case when `n` is a perfect square.
IVlad
+2  A: 

Slow compared to....? How do you know it's an issue with Scala, and not with your algorithm?

An admittedly quick read of the code suggests you might be recalculating primes and other values over and over. isPrimeInternal jumps out as a possible case where this might be a problem.

Rodney Gitzel
I never say it is a Scala issue. I know it is a issue of my code. What's wrong with isPrimeInternal?
Evil Ipos
Might want to edit the title, then ;) "Scala: Why it is so slow?..."
Rodney Gitzel
+1  A: 

Your code is not compilable, some parts are missing, so I'm guessing here. Some thing that frequently hurts performance is boxing/unboxing taking place in collections. Another thing that I noted is that you cunstruct your primes as a Stream - which is a good thing - but don't take advantage of this in your isPrime function, which uses a primitive 2,3-wheel (1 and 5 mod 6) instead. I might be wrong, but try to replace it by

def isPrime(number: Int): Boolean = {
  val sq = math.sqrt(number + 0.5).toInt
  ! primes.takeWhile(_ <= sq).exists(p => number % p == 0)
}
Landei
http://pastebin.com/768E9fEn here is a compilable source. Stream was a bottleneck. When I replaced toval primes = generatePrimeNumbers(10000)it's take 1/10 of orginal time.
Evil Ipos
+2  A: 

You could first check what is slow. Your prime calculation, for instance, is very, very slow. For each number n, you try to divide n by each each number from 5 to sqrt(n), skipping multiples of 2 and 3. Not only you do not skip numbers you already know are not primes, but even if you fix this, the complexity of this algorithm is much worse than the traditional Sieve of Eratosthenes. See one Scala implementation for the Sieve here.

That is not to say that the rest of your code isn't suboptimal as well, but I'll leave that for others.

EDIT

Indeed, indexed access to Stream is terrible. Here's a rewrite that works with Stream, instead of converting everything to Array. Also, note the remark before the first if for a possible bug in your code.

  @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)
  }
Daniel
Prime generation isn't problem. Problem was a slow random access to Stream (List too) (in decomposeToPrimeNumbers). toArray + naive generation of prime fixed the probem. Now i can find solution in ~1000ms. Before change it's took a couple of minutes
Evil Ipos
@Evil See edit for alternate solution (to creating an `Array`). Plus a possible bug fix. Note, however, that you'll often need to find primes in Euler. A good prime generator is handy.
Daniel
Evil Ipos
@Evil There's a few changes in style I'd make, but I don't see anything wrong. Replacing the for loop with a while loop will make it faster, but this looks plenty fast already.
Daniel
@Daniel Thanks for help
Evil Ipos