【问题标题】:Filter a range without using an intermediate list在不使用中间列表的情况下过滤范围
【发布时间】:2016-01-17 16:02:30
【问题描述】:

我写了一个函数is-prime,它验证给定的数字是否是素数,并相应地返回tnil

(is-prime 2) ; => T
(is-prime 3) ; => T
(is-prime 4) ; => NIL

到目前为止,一切都很好。现在我想生成minmax 之间的素数列表,所以我想提出一个函数,将这两个值作为参数并返回所有素数的列表:

(defun get-primes (min max)
  ...)

现在这是我目前卡住的地方。当然,我可以做的是创建一个列表,其中包含从minmax 的所有数字范围,并在其上运行remove-if-not

无论如何,这意味着,我首先必须创建一个潜在的巨大列表,其中包含很多我无论如何都会丢弃的数字。所以我想反过来做,建立一个列表,只包含minmax 之间的数字,根据is-prime 谓词实际上是素数。

我怎样才能以功能的方式做到这一点,即不使用loop?我目前的方法(使用loop)如下所示:

(defun get-primes (min max)
  (loop
    for guess from min to max
    when (is-prime guess)
    collect guess))

也许这是一个完全愚蠢的问题,但我想我没有只见树木不见森林。

有什么提示吗?

【问题讨论】:

  • 我想你可以递归地遍历每个数字并检查它。类似(非常粗糙,很久没有在 lisp 中做过任何事情)(defun primes (start end res) (cond (< start end) (primes (+ start 1) end (if (is-prime start) (cons start res) res) (eq start end) res))
  • 您是否期望 get-primes 以非常大的 min 值被调用?如果不是,我会想象使用Sieve of Eratosthenes 将比你的is-prime 更有效,并且使用它会使问题变得毫无意义(因为无论如何你都会使用一组真/假位)。跨度>
  • @jkiiski 哈哈,感谢您指出这一点:-)!实际上,Eratosthenes 的筛子正是我来自的地方,由于max 值很大,我遇到了内存问题,总是不得不从2 开始很烦人确保即使对于非常大的min 值,也会消除正确的值。所以基本上,是的,你是对的,但我已经是这样了:-)
  • min 值很大时,为了更小的内存占用,使用两个数组 - 一个从 2 到 (sqrt max)core 和一个 "offset" 段 从minmaxsieve them at the same time

标签: functional-programming lisp common-lisp primes


【解决方案1】:

Common Lisp 不支持 函数式编程 方法。该语言基于更实用的底层机器视图:没有 TCO堆栈 是有限的,各种资源是有限的(允许的参数数量等)。 ),变异是可能的。对于任何 Haskell 爱好者来说,这听起来都不是很有动力。但是 Common Lisp 是为了编写 Lisp 应用程序而开发的,而不是为了推进 FP 的研究和开发。 Common Lisp 中的求值(与 Lisp 中的往常一样)是strict 而不是lazy。默认数据结构也不是 lazy。虽然有 lazy 库。 Common Lisp 也没有提供像 continuationscoroutines 这样的标准特性——这在这种情况下可能很有用。

默认的 Common Lisp 方法是非功能性的,并使用某种 迭代 构造:DOLOOPITERATE 库。就像你的例子一样。普通 Lisp 用户发现它非常好。有些人,像我一样,认为 Iterate 是有史以来发明的最好的循环结构。 ;-) 优点是创建高效的代码相对容易,即使宏有很大的实现。

有多种不同的方法:

  • 延迟流。阅读 SICP 会有所帮助,请参阅 streams 上的部分。也可以在 Common Lisp 中轻松完成。请注意,Common Lisp 使用单词 stream 表示 I/O 流 - 这是不同的。
  • 使用生成器函数 next-prime。从初始范围生成此函数并调用它,直到获得您感兴趣的所有素数。

这是一个简单的生成器函数示例,从某个起始值生成偶数:

(defun make-next-even-fn (start)
  (let ((current (- start
                    (if (evenp start) 2 1))))
    (lambda ()
      (incf current 2))))


CL-USER 14 > (let ((next-even-fn (make-next-even-fn 13))) 
               (flet ((get-next-even ()
                        (funcall next-even-fn)))
                 (print (get-next-even))
                 (print (get-next-even))
                 (print (get-next-even))
                 (print (get-next-even))
                 (list (get-next-even)
                       (get-next-even)
                       (get-next-even))))

14 
16 
18 
20 
(22 24 26)

定义next-prime 生成器留作练习。 ;-)

  • 使用某种更复杂的惰性数据结构。对于 Common Lisp,有 Series,这是一个早期的迭代提议 - 也发表在 CLtL2:Series。 Edi Weitz(汉堡的数学教授)的新书Common Lisp Recipes 有一个用于计算素数的系列示例。见第 7.15 章。您可以下载本书的源代码并在那里找到示例。
  • Series 的更简单变体,例如pipes

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-18
    • 1970-01-01
    • 2018-09-18
    • 1970-01-01
    • 1970-01-01
    • 2018-03-18
    • 1970-01-01
    相关资源
    最近更新 更多