加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

Scala,Erastothenes:有一种直接用迭代替换流的方法吗?

发布时间:2020-12-16 09:03:14 所属栏目:安全 来源:网络整理
导读:我写了一个函数,使用流无限期地生成素数(维基百科: incremental sieve of Erastothenes).它返回一个流,但它也在内部合并素数倍流以标记即将到来的复合.如果我自己这样说,定义简洁,实用,优雅且易于理解: def primes(): Stream[Int] = { def merge(a: Stream
我写了一个函数,使用流无限期地生成素数(维基百科: incremental sieve of Erastothenes).它返回一个流,但它也在内部合并素数倍流以标记即将到来的复合.如果我自己这样说,定义简洁,实用,优雅且易于理解:

def primes(): Stream[Int] = {
  def merge(a: Stream[Int],b: Stream[Int]): Stream[Int] = {
    def next = a.head min b.head
    Stream.cons(next,merge(if (a.head == next) a.tail else a,if (b.head == next) b.tail else b))
  }
  def test(n: Int,compositeStream: Stream[Int]): Stream[Int] = {
    if (n == compositeStream.head) test(n+1,compositeStream.tail)
    else Stream.cons(n,test(n+1,merge(compositeStream,Stream.from(n*n,n))))
  }
  test(2,Stream.from(4,2))
}

但是,当我尝试生成第1000个素数时,我得到了“java.lang.OutOfMemoryError:GC开销超出限制”.

我有一个替代解决方案,它在素数上返回一个迭代器,并在内部使用元组的优先级队列(multiple,prime用于生成多个)来标记即将到来的复合.它运行良好,但它需要大约两倍的代码,我基本上不得不从头重新开始:

import scala.collection.mutable.PriorityQueue
def primes(): Iterator[Int] = {
  // Tuple (composite,prime) is used to generate a primes multiples
  object CompositeGeneratorOrdering extends Ordering[(Long,Int)] {
    def compare(a: (Long,Int),b: (Long,Int)) = b._1 compare a._1
  }
  var n = 2;
  val composites = PriorityQueue(((n*n).toLong,n))(CompositeGeneratorOrdering)
  def advance = {
    while (n == composites.head._1) { // n is composite
      while (n == composites.head._1) { // duplicate composites
        val (multiple,prime) = composites.dequeue
        composites.enqueue((multiple + prime,prime))
      }
      n += 1
    }
    assert(n < composites.head._1)
    val prime = n
    n += 1
    composites.enqueue((prime.toLong * prime.toLong,prime))
    prime
  }
  Iterator.continually(advance)
}

有没有一种直接的方法将带有流的代码转换为带迭代器的代码?或者有一种简单的方法可以让我的第一次尝试更有效率吗?

从流中考虑更容易;我宁愿那样开始,然后在必要时调整我的代码.

解决方法

在你的第一个代码中,你应该推迟合并,直到候选人中看到素数的平方.这将大大减少使用的流的数量,从根本上改善您的内存使用问题.为了获得第1000个素数,7919,我们只需要考虑不高于其平方根的素数,88.这只是它们的倍数的23个素数/流,而不是999(22,如果我们从一开始就忽略了均衡).对于第10,000个素数,它是9999个流的倍数和66个之间的区别.对于第100,000个,只需要189个.

诀窍是通过递归调用将正在消耗的素数与正在生成的素数分开:

def primes(): Stream[Int] = {
  def merge(a: Stream[Int],q: Int,compositeStream: Stream[Int],primesStream: Stream[Int]): Stream[Int] = {
    if (n == q) test(n+2,primesStream.tail.head*primesStream.tail.head,Stream.from(q,2*primesStream.head).tail),primesStream.tail)
    else if (n == compositeStream.head) test(n+2,q,compositeStream.tail,primesStream)
    else Stream.cons(n,test(n+2,compositeStream,primesStream))
  }
  Stream.cons(2,Stream.cons(3,Stream.cons(5,test(7,25,Stream.from(9,6),primes().tail.tail))))
}

作为额外的奖励,没有必要将素数的正方形存储为Longs.这也将更快,并且具有更好的算法复杂性(时间和空间),因为它避免了做大量多余的工作. Ideone testing显示它在约~n1.5..1.6 empirical orders of growth处运行,产生多达n = 80,000个质数.

这里仍然存在一个算法问题:这里创建的结构仍然是一个线性左倾结构(((mults_of_2 mults_of_3)mults_of_5)…),更频繁产生的流位于其内部(因此数字更多)渗透,上升的水平.右倾结构应该更好,mults_of_2(mults_of_3(mults_of_5 ……)).使它成为树应该带来时间复杂性的真正改善(通常将其推低到约~n1.2..1.25).相关讨论见this haskellwiki page.

Eratosthenes的“真实”必需筛子通常在~1.1左右(产生n个素数)和最佳试验分区筛子~1.40..1.45. Your original code runs at关于立方时间,或更糟.使用命令式可变阵列通常是最快的,按段(a.k.a.Eratosthenes的分段筛)工作.

在第二个代码的上下文中,this is how it is achieved in Python.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读