0

我想遍历一个列表,对元素执行一个操作,并根据一些标准,我想摆脱活动元素。但是,当使用下面的函数时,我最终会陷入无限循环。

 (defun foo (list action test)
   (do ((elt (car list) (car list)))
       ((null list))
     (funcall action elt)
     (when (funcall test elt)
       (delete elt list))))

(setq list '(1 2 3 4))
(foo list #'pprint #'oddp)
-> infinite loop

它指向自己是不可能的吗?最后,当然elt(car list)

这是一个正确的评估吗?我怎样才能有效地解决这个问题?

4

3 回答 3

3

循环是无限的,因为你没有迭代任何东西,你action重复应用,但如果它没有改变元素,pprint显然没有,那么如果test结果为负,那么它将保持不变并且列表不会即使删除在您尝试时有效,也是空的。

DELETE是一种破坏性功能。在 Common Lisp 中,破坏性操作被允许破坏它们的参数。您应该放弃对参数的任何引用并仅使用返回值。操作完成后,无法保证参数的状态。特别是,可能没有任何影响,因为实现也可以与非破坏性对应物相同,但通常序列的组成部分将以某种难以预测的方式重新组装。您还破坏了示例中的文字,该文字具有未定义的行为,应该避免。

通常最好将 Common Lisp 中的列表视为不可变且具有破坏性的操作,就像微优化一样,只有在您确定它们不会破坏任何东西时才应该使用它。对于这个问题,您可能希望使用 LOOP 将结果列表与 conditional 组装在一起来迭代列表COLLECT。请参阅PCL 的 LOOP 章节

于 2012-02-27T18:08:04.460 回答
1

实际上,您可以在迭代列表时更改列表的状态。您只需要使用rplacd除了delete, 并控制沿列表的前进,而不是在迭代子句中,而是在 do 主体内:

 (defun nfoo (lst action test)
   (do* ((list (cons 1 lst))
         (elt (cadr list) (cadr list)))
        ((null (cdr list))
         (if (funcall test (car lst)) (cdr lst) lst))
     (funcall action elt)
     (if (funcall test elt)
       (rplacd list (delete elt (cddr list)))
       (setf list (cdr list)))))  

copy-list如果你不希望它破坏参数列表,你应该调用它。

如果您想从列表中删除不是所有等于elt通过测试的元素,而是所有通过测试的元素,则delete需要将调用test作为:test参数传递给函数。

编辑:)甚至更简单直接,就像这个(非破坏性)版本:

(defun foo (list action test)
   (do* ((elt (car list) (car list)))
        ((null list))
     (funcall action elt)
     (if (funcall test elt)
       (setf list (delete elt list))
       (setf list (cdr list)))))
于 2012-02-27T20:29:52.970 回答
0

我对 lisp 有点陌生,所以也许我在你的问题中遗漏了一些东西。不过,我我明白你在问什么,我想知道你为什么不为此使用一些现有的结构......即remove-if-not(或者remove-if如果我有倒退的东西)和mapcar......

(mapcar #'pprint (remove-if-not #'oddp '(1 2 3 4))

上面的打印13(并返回(nil nil),但大概你可以忽略它......或者你可以做一个执行上述操作并以 结尾的 defun (values))。(如果您想要偶数,请更改remove-if-notremove-if。)

这让我觉得这可能是一种更明智的处理方式,除非你这样做是出于教学原因,或者我遗漏了一些东西……我承认其中任何一种都是很可能的。:)

PS Hyperspec 关于remove-if,remove-if-not等的信息。

于 2012-02-28T22:11:14.873 回答