2 August 2022

1到100亿以内的素数按顺序的IO输出

使用Sieve_of_Eratosthenes 计算素数

最终目标是 个人电脑(16G内存 CPU 10代I7)实现 1到100亿以内的素数按顺序的IO输出。[2022-08-02]

2022年8月2日

初步思路是先写出可以计算一定范围的素数的函数,然后对100亿进行分段生成素数,然后分区写出所有素数;

Int=>Int 计算 正整数 n以内素数的个数

根据Sieve of Eratosthenes - Wikipedia 的伪代码实现了我自己的初始版本

# scala
def countPrimes(n: Int): Int = {
      val container = collection.mutable.Seq.fill(n)(true)
      var i = 2
      while (i * i < n) {
        if (container(i)) {
          var j = i * i
          while (j < n) {
            container.update(j, false)
            j += i
          }
        }
        i += 1
      }
      (2 until n).count(container)
    }

对算法进行简单分析,将两次循环解耦得到第二个版本

    def countPrimesV1(n: Int): Int = {
      val container = collection.mutable.Seq.fill(n)(true)
      val seqI = 2 to Math.floor(Math.sqrt(n)).toInt
      val validSeqI =
        seqI.filter(i => {
          val lower = seqI.filter(j => i > j)
          !lower.exists(k => (i % k) == 0)
        })
      validSeqI.foreach(i => {
        var next = i * i
        while (next < n) {
          if (container(next)) {
            container.update(next, false)
          }
          next += i
        }
      })
      (2 until n).count(container)
    }

使用scala 比较惯用的集合函数api 写了一个版本,太慢了

	import java.util.Math
    def flatMapSet(n: Int): Int = {
      val seqI = (2 to Math.floor(Math.sqrt(n)).toInt)
      val container =
        seqI.filter(i => {
          val lower = seqI.filter(j => i > j)
          !lower.exists(k => (i % k) == 0)
        })
          .flatMap(i => {
            i * i to n by i
          })
          .toSet
      n - container.size - 1
    }

使用其它的数据结构复刻了一下第一个版本,效率都远远第一第一个版本

def seqPar(n: Int): Int = {
      val container = collection.mutable.Seq.fill(n)(true).par
      val seqI = 2 to Math.floor(Math.sqrt(n)).toInt
      val validSeqI =
        seqI.filter(i => {
          val lower = seqI.filter(j => i > j)
          !lower.exists(k => (i % k) == 0)
        })
      validSeqI.foreach(i => {
        var next = i * i
        while (next < n) {
          if (container(next)) {
            container.update(next, false)
          }
          next += i
        }
      })
      (2 until n).count(x => container(x))
    }

    def set(n: Int): Int = {
      val container = collection.mutable.Set.empty[Int]
      val seqI = 2 to Math.floor(Math.sqrt(n)).toInt
      val validSeqI =
        seqI.filter(i => {
          val lower = seqI.filter(j => i > j)
          !lower.exists(k => (i % k) == 0)
        })
      validSeqI.foreach(i => {
        var next = i * i
        while (next < n) {
          container.add(next)
          next += i
        }
      })
      n - container.size - 2
    }



    def map(n: Int): Int = {
      val container = collection.mutable.Map((2 to n).map(x => (x, true)): _*)
      var i = 2
      while (i * i < n) {
        if (container(i)) {
          var j = i * i
          while (j < n) {
            container.update(j, false)
            j += i
          }
        }
        i += 1
      }
      (2 until n).count(container)
    }

在写计算素数的函数个数中简单了解了一下 使用牛顿迭代法计算平方根的算法

下面是代码实现

  
/**
* v :需要计算平方根的数
* digit :表示具体精度
**/
def sqrt(v: Int, digit: Int) = {
    val min = Math.pow(10, -digit)

    @tailrec
    def sqrt_(r: Double): Double = {
      if (min > ((r + v / r) / 2 - r).abs) return r
      sqrt_((r + v / r) / 2)
    }

    sqrt_(v)
  }

2022年9月7日

今天看scala教程scala 高级教程 P17 13:00(链接)的时候发现了更简单的写法。 在视频中,老师首先自己手写了一套scala 风格的纯函数流处理实现,然后用自己实现的Stream 对象实现了Sieve of Eratosthenes 算法。 我在练习过程中把这一套手写实现复刻了一下。

下面是Stream 核心功能实现的代码。

import scala.annotation.{tailrec, varargs}

abstract class Stream[+A] {
  def isEmpty: Boolean
  def head: A
  def tail: Stream[A]
  def #::[B >: A](e: B): Stream[B] // 在stream 的头部添加元素e
  def ++[B >: A](anotherStream: => Stream[B]): Stream[B] // 在stream 的尾部合并  anotherStream
  def foreach(f: A => Unit): Unit
  def map[B](f: A => B): Stream[B]
  def flatMap[B](f: A => Stream[B]): Stream[B]
  def filter(predicate: A => Boolean): Stream[A]
  // take first n element from stream
  def take(n: Int): Stream[A]
  def takeAsList(i: Int): List[A]
  @tailrec
  final def toList[B >: A](acc: List[B] = Nil): List[B] = {
    if (isEmpty)
      acc.reverse
    else
      tail.toList(head :: acc)
  }
}

object Stream {
  def from[A](start: A)(generator: A => A): Stream[A] = {
    new Cons[A](start, from(generator(start))(generator))
  }
}
object EmptyStream extends Stream[Nothing] {
  override def isEmpty: Boolean = true

  override def head: Nothing = throw new NoSuchElementException

  override def tail: Stream[Nothing] = throw new NoSuchElementException

  override def #::[B >: Nothing](e: B): Stream[B] = new Cons[B](e, this)

  override def ++[B >: Nothing](anotherStream: => Stream[B]): Stream[B] = anotherStream

  override def foreach(f: Nothing => Unit): Unit = ()

  override def map[B](f: Nothing => B): Stream[B] = this

  override def flatMap[B](f: Nothing => Stream[B]): Stream[B] = this

  override def filter(predicate: Nothing => Boolean): Stream[Nothing] = this

  override def take(i: Int): Stream[Nothing] = this

  override def takeAsList(i: Int): List[Nothing] = Nil
}

class Cons[+A](h: A, tl: => Stream[A]) extends Stream[A] {

  override def head: A = h

  override lazy val tail: Stream[A] = tl

  override def isEmpty: Boolean = false

  override def #::[B >: A](e: B): Stream[B] = new Cons[B](e, this)

  override def ++[B >: A](anotherStream: => Stream[B]): Stream[B] = new Cons[B](head, tail ++ anotherStream)

  override def foreach(f: A => Unit): Unit = {
    f(head)
    tail.foreach(f)
  }
  override def map[B](f: A => B): Stream[B] = new Cons[B](f(head), tail.map(f))

  override def flatMap[B](f: A => Stream[B]): Stream[B] = f(head) ++ tail.flatMap(f)

  override def filter(predicate: A => Boolean): Stream[A] = if (predicate(head)) {
    new Cons(head, tail.filter(predicate))
  } else {
    tail.filter(predicate)
  }

  override def take(i: Int): Stream[A] = {
    if (i <= 0) {
      EmptyStream
    } else if (i == 1) {
      new Cons(head, EmptyStream)
    } else {
      new Cons(head, tail.take(i - 1))
    }

  }

  override def takeAsList(i: Int): List[A] = take(i).toList()

}

个人理解整个函数式编程实现流处理流程的核心在于 使用递归和scala 中call-by-name 传名变量的使用。

以下是使用自己定义的Stream 实现的的Sieve of Eratosthenes 算法以及测试

  def eratosthenes(numbers: Stream[Int]): Stream[Int] = {
    if (numbers.isEmpty) numbers
    else new Cons[Int](numbers.head, eratosthenes(numbers.tail.filter(_ % numbers.head != 0)))
  }

  def test={
    val stream=Stream.from(2)(_ + 1).take(100) // 长度为 100的 从2 开始的流数据
    // 打印输出结果
    println(Eratosthenes(stream).toList().mkString(","))
    // 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101
  }

其实使用scala 的提供的数据结构直接实现就好

// "Prefer LazyList instead", since = "2.13.0"
// 流结构的
def Eratosthenes(numbers: LazyList[Int]): LazyList[Int] = {
  if (numbers.isEmpty) numbers
  else numbers.head+:Eratosthenes(numbers.tail.filter(_ % numbers.head != 0))
}

// 调用
 println(Eratosthenes(LazyList.from(2,1).take(100)).toSeq.mkString(","))

// 普通的seq 结构
  def Eratosthenes(numbers: Seq[Int]): Seq[Int] = {
    if (numbers.isEmpty) numbers
    else numbers.head+:Eratosthenes(numbers.tail.filter(_ % numbers.head != 0))
  }
// 调用
   println(Eratosthenes(2 to 101).mkString(","))