Найти простые числа с помощью Scala. Помоги мне улучшить - PullRequest
5 голосов
/ 15 марта 2012

Я написал этот код, чтобы найти простые числа, меньшие заданного числа i в scala.

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 ]

19 голосов
/ 02 ноября 2012

Вот функциональная реализация Сита Эратосфена , представленного в Курс Одерского "Принципы функционального программирования в 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)
10 голосов
/ 15 марта 2012

Стиль выглядит хорошо для меня.Хотя сито Эратосфена является очень эффективным способом поиска простых чисел, ваш подход также работает хорошо, поскольку вы проверяете только деление на известные простые числа.Однако вам нужно остерегаться - ваша рекурсивная функция не является хвостовой.Хвостовая рекурсивная функция не изменяет результат рекурсивного вызова - в вашем примере вы добавляете результат к рекурсивному вызову.Это означает, что у вас будет длинный стек вызовов, поэтому 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)

Это решение не красивее - это больше подробности, чтобы заставить ваше решение работать для больших аргументов.Поскольку список строится в обратном направлении, чтобы воспользоваться преимуществами быстрых предопределений, список необходимо перевернуть.В качестве альтернативы вы можете использовать Array, Vector или ListBuffer для добавления результатов.Однако с Array вам нужно будет оценить, сколько памяти выделить для него.К счастью, мы знаем, что pi(n) примерно равно n / ln(n), поэтому вы можете выбрать разумный размер.Array и ListBuffer также являются изменяемыми типами данных, что вновь соответствует вашему стремлению к функциональному стилю.

Обновление: чтобы добиться хорошей производительности из решета Эратосфена, я думаю, вам нужно будет хранить данныев нативном массиве, что также идет вразрез с вашим стремлением к стилю в функциональном программировании.Хотя может быть творческая функциональная реализация!

Обновление: упс!Пропустил его!Этот подход также хорошо работает , если вы делите только на простые числа меньше квадратного корня числа, которое вы тестируете !Я пропустил это, и, к сожалению, нелегко настроить мое решение, чтобы сделать это, потому что я храню простые числа в обратном направлении.

Обновление: вот очень нефункциональное решение, которое по крайней мере проверяет только до квадратного корня.

rnative, вы можете использовать Array, Vector или ListBuffer для добавления результатов.Однако с Array вам необходимо оценить, сколько памяти выделить для него.К счастью, мы знаем, что pi(n) примерно равно n / ln(n), поэтому вы можете выбрать разумный размер.Array и ListBuffer также являются изменяемыми типами данных, что снова соответствует вашему стремлению к функциональному стилю.

Обновление: чтобы добиться хорошей производительности из решета Эратосфена, я думаю, вам нужно будет хранить данныев нативном массиве, что также идет вразрез с вашим стремлением к стилю в функциональном программировании.Хотя может быть творческая функциональная реализация!

Обновление: упс!Пропустил его!Этот подход тоже хорошо работает , если вы делите только на простые числа меньше квадратного корня числа, которое вы тестируете !Я пропустил это, и, к сожалению, нелегко настроить мое решение, чтобы сделать это, потому что я храню простые числа в обратном направлении.

Обновление: вот очень нефункциональное решение, которое по крайней мере проверяет только до квадратного корня.

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 s, вероятно, не лучшее решение, потому что у них нет ускоренного O (1), хотя это амортизированное O (1).

6 голосов
/ 15 марта 2012

Как упоминает 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 возвращает Stream всех простых чисел. prime3 - это место, где вычисляется поток с использованием простейшего предиката для проверки всех простых делителей, меньших квадратного корня из i.

3 голосов
/ 27 апреля 2014
  /**
   * @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
  }
1 голос
/ 03 марта 2016

Как насчет этого?

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
}

Это довольно быстро для меня, в моем Mac, получить все простые под 100k, это займет около 2,5 сек.

1 голос
/ 07 декабря 2014

@ mfa упомянул об использовании сита эратосфена - SoE , а @Luigi Plinge упомянул, что это должно быть сделано с использованием функционального кода, поэтому @ netzwerg опубликовал не-SoE версию ; здесь я публикую «почти» функциональную версию SoE, использующую полностью неизменяемое состояние, за исключением содержимого изменяемого 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 }
  }
}
1 голос
/ 15 марта 2012

Метод сито - ваш лучший выбор для небольших списков чисел (до 10-100 миллионов или около того). см .: Сито Эратосфена

Даже если вы хотите найти гораздо большие числа, вы можете использовать список, сгенерированный этим методом, в качестве делителей для проверки чисел до n ^ 2, где n - предел вашего списка.

0 голосов
/ 03 апреля 2018
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)
  }
0 голосов
/ 27 февраля 2017

скалярный 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)
        }
    }
}
0 голосов
/ 29 ноября 2016
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
}
...