【问题标题】:Common Lisp: is delete-if the same as setf + remove-if?Common Lisp:delete-if 是否与 setf + remove-if 相同?
【发布时间】:2012-07-26 00:43:15
【问题描述】:

以下代码生成从 1 到 n 的素数:

(defun prime-list(n)
  (let ((a)(b)(x (floor (sqrt n))))
    (loop for i from (floor n 6) downto 1 do
          (push (1+ (* 6 i)) a)
          (push (1- (* 6 i)) a))
    (loop while (<= (car a) x) do
          (push (car a) b)
          (setf a (remove-if #'(lambda(m)(or (= 0 (mod m (car a))) (> m n))) a)))
    (append '(2 3) (reverse b) a)))

在我看来这部分

(setf a (remove-if #'XXX a)) 

可以替换为

(delete-if #'XXX a)

我希望这会使其更快。但是,当我进行更改时,函数现在进入无限循环并且永远不会返回。为什么?

【问题讨论】:

  • ERROR1: 应该是 while (&lt;= (car a) x) ERROR2: 因为任何素数的形式都是 6i+-1,所以任何素数的平方模 6 为 1。如果n == p^2-1 对于一些素数p,它遵循n == 6i 对于一些i。 Q 中的代码会将6i+1 == p^2 包含在列表中;但将通过x = sqrt(n) &lt; p 进行测试,因此将在输出中包含p^2。因此,当使用n=p^2-1 调用任何素数时,以上将产生p^2 作为其输出中的最后一个elt。 (我不编辑这个,因为 Q 不是关于代码的有效性)。
  • 另外,这是一个试验除法筛,它的效率远低于 Eratosthenes 的筛(它以相等的增量计数以找到倍数,而不是通过师)。
  • @WillNess TURE。我正在玩这个并且没有考虑效率。后来我使用了bit-array,你可以在这里找到代码:rosettacode.org/wiki/Sieve_of_Eratosthenes#Common_Lisp(第2个,欢迎评论。)
  • 明白。我只是为了方便读者而注意到这一点。 (error1 现已修复;error2 仍然存在)。
  • @WillNess 已更改。但这可能不是修复它的最佳方法。 ;)

标签: lisp common-lisp primes


【解决方案1】:

如cmets中所说,需要设置变量。

DELETE-IF 主要是REMOVE-IF 的破坏性版本。 REMOVE-IF 返回一个新的 consed 序列,其中不包含已删除的元素。 DELETE-IF 可能会返回一个重复使用的序列。

如果你有一个绑定到列表的变量,你仍然需要设置结果。上面的函数返回结果,但它们没有为结果设置变量。在列表的情况下,DELETE-IF 操作的结果可以是空列表,并且不可能有副作用,即可以为其设置变量 - 当它指向非空列表时。

【讨论】:

  • 此外,当第一个元素需要删除时,返回的值总是与原始序列不同(没有明智的方法可以手术删除列表的第一个缺点,同时仍然有一个指针那个缺点,所以返回第一个未删除的缺点)。
【解决方案2】:

我没有太多的 CL 经验,但我在 Scheme 中做了很多工作。

在第二个版本(sans setf a)中,remove-if 表达式被求值,但它实际上并没有改变 a。 loop 是 CL 中的一个宏,它只计算表达式,但不像递归函数那样使用这些表达式的结果。

所以在第一个版本中,由于 setf 的原因,每次循环运行时 a 的值都会更改,但在第二个版本中,a 的值始终保持不变。因此 (car a) 永远不会改变并且循环永远不会终止。

我们可以比较两个循环语句上的宏扩展结果:

没有 setf:

(MACROLET ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-ERROR)))
 (BLOCK NIL
  (LET NIL
   (MACROLET ((LOOP-FINISH NIL '(GO SYSTEM::END-LOOP)))
    (TAGBODY SYSTEM::BEGIN-LOOP
     (PROGN (UNLESS (< (CAR A) X) (LOOP-FINISH))
      (PROGN (PUSH (CAR A) B) (REMOVE-IF #'(LAMBDA (M) (= 0 (MOD M (CAR A)))) A)))
     (GO SYSTEM::BEGIN-LOOP) SYSTEM::END-LOOP
     (MACROLET
      ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-WARN) '(GO SYSTEM::END-LOOP))))))))) ;

使用 setf:

(MACROLET ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-ERROR)))
 (BLOCK NIL
  (LET NIL
   (MACROLET ((LOOP-FINISH NIL '(GO SYSTEM::END-LOOP)))
    (TAGBODY SYSTEM::BEGIN-LOOP
     (PROGN (UNLESS (< (CAR A) X) (LOOP-FINISH))
      (PROGN (PUSH (CAR A) B)
       (SETF A (REMOVE-IF #'(LAMBDA (M) (= 0 (MOD M (CAR A))))) A)))
     (GO SYSTEM::BEGIN-LOOP) SYSTEM::END-LOOP
     (MACROLET
      ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-WARN) '(GO SYSTEM::END-LOOP))))))))) ;

您可以看到,在第一个循环中,remove-if 表达式被求值,但它的结果未被使用。

【讨论】:

  • 糟糕,我想我问错了问题,我的意思是“(setf a (remove-if #'XXX a)) 可以替换为(delete-if #'XXX a)
【解决方案3】:

克里斯是正确的。

您可以使用delete-if 代替remove-if 来加快速度

【讨论】:

  • 对不起,我是想问delete-if(我在标题里写了delete-if),发现delete-if不起作用。我正在使用 Lispworks,当我尝试 delete-if 时,发生了一些非常奇怪的事情:即使第一个条目应该被删除, delete if 也不会删除第一个条目。见link
  • 你还需要使用setf:(setf foo (delete-if #'some-pred foo))
  • 你还需要(setq a (delete-if ...)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-25
  • 1970-01-01
  • 2015-07-08
  • 2022-01-23
  • 2020-04-15
  • 1970-01-01
相关资源
最近更新 更多