【问题标题】:Lazily generating prime in Common Lisp在 Common Lisp 中懒惰地生成素数
【发布时间】:2017-09-29 19:56:09
【问题描述】:

我正在尝试以 Python 方式生成素数,即使用生成器。

python 代码或多或少如下

def divisor_in_list(n, d):
    """ Returns true if a divisor of n is in d, false otherwise"

def primes():
    yield 2
    primelist = [2]
    i = 3

    while True:
        while divisor_in_list(i, primelist):
            i += 2
        primelist.append(i)
        yield i

我对 Lisp 很陌生,所以我想知道惯用的等价物是什么。根据我到目前为止的研究,我有以下代码

(defun primes ()
  (let* ((p (list 2)) (n 3))
    (lambda ()
      (loop while (divisor-in-slist n p)
            do (incf n 2))
      (nconc p (list n))
      (+ n 0) ;; Not actually sure how to return N directly :(
      )
    )
  )

但是,这段代码有一个问题,即它输出的第一个值是 3。不幸的是,我无法弄清楚如何优雅地修改它以产生 2 作为第一个值。

我绝对可以将 lambda 中的 if 语句和一个额外的变量结合起来,以检查是否是第一次调用该方法,但这看起来很难看。有什么更好的方法?

【问题讨论】:

  • 仅供参考“惰性”关键字 => 一个惰性库:clazy
  • 丑陋在旁观者的眼中。我在 Python 解决方案中看到了很多:硬编码的初始产量 2,硬编码的 knowns 到 [2] 的初始化,从 3 迭代,最后是递增 2 的“作弊”。素数不会说“2 然后从 3 开始,每隔一个整数等跳过”。因此,Python 解决方案具有各种特定于我们所知道的素数的技巧。从“一个大于 1 的整数,其仅有的两个整数因子是 1 和它自己”再试一次。那么你只需要一个丑陋的技巧:如果 > 2 则跳过两个。
  • @kennytilton 你很搞笑,你知道吗?

标签: python closures lisp common-lisp generator


【解决方案1】:

Common Lisp 中没有直接等效于 yield 的内容。人们可能会使用某种函数式方法或使用某种提供惰性计算的库。

完成您的方法的一种方法是这样的,其中我们有一个变量 f 保存当前的延续。

(defun make-prime-generator (&aux (p (list 2)) (n 2) f)
  (labels ((f2 ()            ; a function for the first iteration
             (incf n)
             (setf f #'fn)   ; setting f to the next function
             2)
           (fn ()            ; a function for all other iterations
             (loop while (divisor-in-list n p)
                   do (incf n 2))
             (push n p)
             n))
    (setf f #'f2)            ; setting f to the first function
    (lambda ()               ; returning a closure
      (funcall f))))         ;   which calls the current f


CL-USER 28 > (let ((p (make-prime-generator)))
               (flet ((p () (funcall p)))
                 (loop repeat 10 do (print (p)))))

2 
3 
5 
7 
11 
13 
17 
19 
23 
29 
NIL

如果一个人有野心,可以隐藏在一个宏后面,该宏将定义所有代码部分并管理转换。

进一步探索

我们可以通过引入局部函数initexitstep 使状态变化更加明确。

(defun make-prime-generator (&aux (p (list 2)) (n 2) f)
  (flet ((init (function)
           (setf f function))
         (exit (result function)
           (setf f function)
           result)
         (step ()
           (funcall f)))
    (labels ((f2 ()
               (incf n)
               (exit 2 #'fn))
             (fn ()
               (loop while (divisor-in-list n p)
                     do (incf n 2))
               (push n p)
               (exit n #'fn)))
      (init #'f2)
      #'step)))

现在这将是另一个更高级的任务:编写一个宏gen-run,它允许我们删除样板并使代码更具声明性。可以这样使用:

(defmacro gen-run (f-descriptions &key start)
  (let ((§f    (gensym "F"))
        (§init (gensym "INIT"))
        (§exit (gensym "EXIT"))
        (§step (gensym "STEP")))
    `(let (,§f)
       (flet ((,§init (function)
                (setf ,§f function))
              (,§exit (result function)
                (setf ,§f function)
                result)
              (,§step ()
                (funcall ,§f)))
         (labels (,@(loop for fd in f-descriptions
                      collect (destructuring-bind (name -> next &body body)
                                  fd
                                (declare (ignore ->))
                                `(,name ()
                                    (,§exit ((lambda () ,@body))
                                            (function ,(if next next name)))))))
           (,§init (function ,start))
           (function ,§step))))))

(defun make-prime-generator (&aux (p (list 2)) (n 2))
  (gen-run ((f2 -> fn
              (incf n)
              2)
            (fn -> fn
              (loop while (divisor-in-list n p)
                    do (incf n 2))
              (push n p)
              n))
      :start f2))

【讨论】:

    猜你喜欢
    • 2011-12-31
    • 2010-09-26
    • 2020-09-18
    • 1970-01-01
    • 2019-04-19
    • 1970-01-01
    • 1970-01-01
    • 2021-11-15
    • 2016-02-17
    相关资源
    最近更新 更多