Scala,Erastothenes:有一种直接用迭代替换流的方法吗?
我写了一个函数,使用流无限期地生成素数(维基百科:
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. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |