使用Scala查找素数。帮助我改进

时间:2012-03-14 23:16:11

标签: algorithm scala functional-programming primes

我写了这段代码,以便在scala中找到小于给定数字i的素数。

def findPrime(i : Int) : List[Int] = i match {
    case 2 => List(2)
    case _ => {
    val primeList = findPrime(i-1)
    if(isPrime(i, primeList)) i :: primeList else primeList
    }
}

def isPrime(num : Int, prePrimes : List[Int]) : Boolean = prePrimes.forall(num % _ != 0)    

但是,我对findPrime函数有所了解,特别是这部分:

case _ => {
    val primeList = findPrime(i-1)
    if(isPrime(i, primeList)) i :: primeList else primeList
}

不是功能风格。

我还在学习函数式编程。任何人都可以帮我改进这些代码,使其更具功能性。

非常感谢。

10 个答案:

答案 0 :(得分:18)

这是Sieve of Eratosthenes的功能实现,如Odersky's "Functional Programming Principles in Scala" Coursera课程中所示:

  // Sieving integral numbers
  def sieve(s: Stream[Int]): Stream[Int] = {
    s.head #:: sieve(s.tail.filter(_ % s.head != 0))
  }

  // All primes as a lazy sequence
  val primes = sieve(Stream.from(2))

  // Dumping the first five primes
  print(primes.take(5).toList) // List(2, 3, 5, 7, 11)

答案 1 :(得分:10)

这种风格对我来说很好看。尽管Eratosthenes筛选是一种非常有效的查找素数的方法,但您的方法也很有效,因为您只是针对已知素数进行分裂测试。但是你需要注意 - 你的递归函数不是尾递归的。尾递归函数不会修改递归调用的结果 - 在您的示例中,您将预先添加到递归调用的结果中。这意味着你将拥有一个长调用堆栈,因此findPrime不适用于大型i。这是一个尾递归解决方案。

def primesUnder(n: Int): List[Int] = {
  require(n >= 2)

  def rec(i: Int, primes: List[Int]): List[Int] = {
    if (i >= n) primes
    else if (prime(i, primes)) rec(i + 1, i :: primes)
    else rec(i + 1, primes)
  }

  rec(2, List()).reverse
}

def prime(num: Int, factors: List[Int]): Boolean = factors.forall(num % _ != 0)

这个解决方案并不漂亮 - 让您的解决方案适用于大型参数更具细节性。由于列表是向后构建的以利用快速前置,因此列表需要反转。作为替代方案,您可以使用ArrayVectorListBuffer来附加结果。但是,使用Array,您需要估计要为其分配多少内存。幸运的是,我们知道pi(n)大约等于n / ln(n),因此您可以选择合理的尺寸。 ArrayListBuffer也是一种可变数据类型,这再次成为您对功能风格的渴望。

更新:为了从Eratosthenes的筛选中获得良好的性能,我认为您需要将数据存储在本机数组中,这也违背了您对函数式编程风格的需求。可能会有一个创造性的功能实现!

更新:哎呀!错过了!如果您只将质数除以小于您正在测试的数字的平方根,这种方法也很有效!我错过了这个,不幸的是,调整我的解决方案并不容易,因为我正在向后存储素数。

更新:这是一个非常缺乏功能的解决方案,至少只能检查平方根。

rnative,您可以使用ArrayVectorListBuffer来附加结果。但是,使用Array,您需要估计要为其分配多少内存。幸运的是,我们知道pi(n)大约等于n / ln(n),因此您可以选择合理的尺寸。 ArrayListBuffer也是一种可变数据类型,这再次成为您对功能风格的渴望。

更新:为了从Eratosthenes的筛选中获得良好的性能,我认为您需要将数据存储在本机数组中,这也违背了您对函数式编程风格的需求。可能会有一个创造性的功能实现!

更新:哎呀!错过了!如果您只将质数除以小于您正在测试的数字的平方根,这种方法也很有效!我错过了这个,不幸的是,调整我的解决方案并不容易,因为我正在向后存储素数。

更新:这是一个非常缺乏功能的解决方案,至少只能检查平方根。

import scala.collection.mutable.ListBuffer

def primesUnder(n: Int): List[Int] = {
  require(n >= 2)

  val primes = ListBuffer(2)
  for (i <- 3 to n) {
    if (prime(i, primes.iterator)) {
      primes += i
    }
  }

  primes.toList
}

// factors must be in sorted order
def prime(num: Int, factors: Iterator[Int]): Boolean = 
  factors.takeWhile(_ <= math.sqrt(num).toInt) forall(num % _ != 0)

或者我可以在原始方法中使用Vector s。 Vector可能不是最佳解决方案,因为它们没有禁食O(1),即使它已经摊销了O(1)。

答案 2 :(得分:6)

正如schmmd所提到的,你希望它是尾递归的,你也希望它是懒惰的。幸运的是,有一个完美的数据结构:Stream

这是一个非常有效的主要计算器,实现为Stream,并进行了一些优化:

object Prime {
  def is(i: Long): Boolean =
    if (i == 2) true
    else if ((i & 1) == 0) false // efficient div by 2
    else prime(i)

  def primes: Stream[Long] = 2 #:: prime3

  private val prime3: Stream[Long] = {
    @annotation.tailrec
    def nextPrime(i: Long): Long =
      if (prime(i)) i else nextPrime(i + 2) // tail

    def next(i: Long): Stream[Long] =
      i #:: next(nextPrime(i + 2))

    3 #:: next(5)

  }

    // assumes not even, check evenness before calling - perf note: must pass partially applied >= method
  def prime(i: Long): Boolean =
    prime3 takeWhile (math.sqrt(i).>= _) forall { i % _ != 0 }
}

Prime.is是主要检查谓词,Prime.primes返回所有素数的Streamprime3是计算Stream的位置,使用素数谓词来检查小于i的平方根的所有素数除数。

答案 3 :(得分:3)

  /**
   * @return Bitset p such that p(x) is true iff x is prime
   */
  def sieveOfEratosthenes(n: Int) = {
    val isPrime = mutable.BitSet(2 to n: _*)
    for (p <- 2 to Math.sqrt(n) if isPrime(p)) {
      isPrime --= p*p to n by p
    }
    isPrime.toImmutable
  }

答案 4 :(得分:1)

筛选方法是小数字列表(最多1000万-100万左右)的最佳选择。 见:Sieve of Eratosthenes

即使您想要找到更大的数字,也可以使用此方法生成的列表作为除数来测试最多n ^ 2的数字,其中n是列表的限制。

答案 5 :(得分:1)

@mfa has mentioned using a Sieve of Eratosthenes - SoE和@Luigi Plinge提到应该使用功能代码完成,所以@netzwerg has posted a non-SoE version;在这里,我发布了一个&#34;几乎&#34;使用完全不可变状态的SoE的功能版本,除了I posted as an answer to another question

的可变BitSet(可变性而非性能不可变)的内容
object SoE {
  def makeSoE_Primes(top: Int): Iterator[Int] = {
    val topndx = (top - 3) / 2
    val nonprms = new scala.collection.mutable.BitSet(topndx + 1)
    def cullp(i: Int) = {
      import scala.annotation.tailrec; val p = i + i + 3
      @tailrec def cull(c: Int): Unit = if (c <= topndx) { nonprms += c; cull(c + p) }
      cull((p * p - 3) >>> 1)
    }
    (0 to (Math.sqrt(top).toInt - 3) >>> 1).filterNot { nonprms }.foreach { cullp }
    Iterator.single(2) ++ (0 to topndx).filterNot { nonprms }.map { i: Int => i + i + 3 }
  }
}

答案 6 :(得分:1)

这个怎么样。

def getPrimeUnder(n: Int) = {
  require(n >= 2)
  val ol = 3 to n by 2 toList // oddList
  def pn(ol: List[Int], pl: List[Int]): List[Int] = ol match {
    case Nil => pl
    case _ if pl.exists(ol.head % _ == 0) => pn(ol.tail, pl)
    case _ => pn(ol.tail, ol.head :: pl)
  }
  pn(ol, List(2)).reverse
}

对我而言,在我的机器中,让所有素数低于10万,它的速度大约为2.5秒。

答案 7 :(得分:0)

标量fp方法

// returns the list of primes below `number`
def primes(number: Int): List[Int] = {
    number match {
        case a
        if (a <= 3) => (1 to a).toList
        case x => (1 to x - 1).filter(b => isPrime(b)).toList
    }
}

// checks if a number is prime
def isPrime(number: Int): Boolean = {
    number match {
        case 1 => true
        case x => Nil == {
            2 to math.sqrt(number).toInt filter(y => x % y == 0)
        }
    }
}

答案 8 :(得分:0)

def primeNumber(range: Int): Unit ={
    val primeNumbers: immutable.IndexedSeq[AnyVal] =
      for (number :Int <- 2 to range) yield {
        val isPrime = !Range(2, Math.sqrt(number).toInt).exists(x => number % x == 0)
        if(isPrime)  number
      }
    for(prime <- primeNumbers) println(prime)
  }

答案 9 :(得分:-1)

object Primes {
  private lazy val notDivisibleBy2: Stream[Long] = 3L #:: notDivisibleBy2.map(_ + 2)
  private lazy val notDivisibleBy2Or3: Stream[Long] = notDivisibleBy2
      .grouped(3)
      .map(_.slice(1, 3))
      .flatten
      .toStream
  private lazy val notDivisibleBy2Or3Or5: Stream[Long] = notDivisibleBy2Or3
      .grouped(10)
      .map { g =>
        g.slice(1, 7) ++ g.slice(8, 10)
      }
      .flatten
      .toStream

  lazy val primes: Stream[Long] = 2L #::
      notDivisibleBy2.head #::
      notDivisibleBy2Or3.head #::
      notDivisibleBy2Or3Or5.filter { i =>
        i < 49 || primes.takeWhile(_ <= Math.sqrt(i).toLong).forall(i % _ != 0)
      }

  def apply(n: Long): Stream[Long] = primes.takeWhile(_ <= n)

  def getPrimeUnder(n: Long): Long = Primes(n).last
}