2012-02-27 101 views
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)

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

+0

@quartus的随机注释:如果它还包含预期的行为部分,我会很快给出这个问题upvote - 您期望打印什么(和/或其他副作用),以及您是做什么的希望被退回。 – lindes 2012-03-01 01:52:02

回答

1

实际上,您可以在迭代时更改列表的状态。你只得除了使用rplacddelete,并控制沿列表中未在迭代条款的进步,但做身体内部:

(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))))) 
+0

我发现你的例子有效,但它感觉像'(setf list(delete elt list))'和'(setf list(cdr list))'有效地做了同样的事情两次。 – quartus 2012-02-28 08:30:30

+0

如果意图修改列表,这会中断。变量'list'是一个本地函数绑定,任何'setf'只影响函数的内部。从调用代码中引用这个列表将仍然指向一些半随机的cons单元,因为原始列表将被'delete'销毁,并且函数不会返回新列表。 – Ramarren 2012-02-28 10:07:02

+0

@Ramarren *第一版本*将工作,如果这是意图,除了删除列表中的第一个元素,如果通过了测试。那么,“流行”电话可以做到这一点。我应该将第二个版本重新命名为“foo”。 – 2012-02-28 18:25:46

3

循环是无限的,因为你没有遍历任何东西,你应用action反复,但如果不发生变异的元素,如pprint显然不能,那么如果test结果为负,则其将保持所以即使删除按照您的尝试进行,清单也不会清空。

DELETE是一种破坏性的功能。在Common Lisp破坏性操作允许的情况下破坏他们的论点。您应该放弃对参数的任何引用并仅使用返回值。操作完成后,对参数状态不予保证。尤其是,可能没有任何效果,因为实现也可以与非破坏性副本相同地执行,但通常会以一些难以预测的方式重新组合该序列的组成部分。你也在销毁你的例子中的一个文字,它有未定义的行为,应该避免。

通常最好将Common Lisp中的列表视为不可改变的破坏性操作,只有在确定它们不会破坏任何内容时才能使用。对于这个问题,您可能需要使用LOOP将结果列表与条件COLLECT进行组合来遍历列表。请参阅LOOP chapter of PCL

+0

我想标记你的答案,因为它解释了我函数行为的_nature_。此外,为了简化我的代码片段以便发布它,我搞砸了一些,所以你的观点是正确的,无论如何我最终都会陷入无限循环。 – quartus 2012-02-28 07:40:10

+0

和,你真的只使用破坏性操作进行微优化吗?这对我很有趣。所以你会使用'(setq lst(delete elt lst))'而不是'(delete elt lst)'? – quartus 2012-02-28 07:42:36

+2

是的。在(删除elt lst)之后,您不能使用任何现有的对lst的引用,因为它处于不可预知的状态。只有返回值保证有用。 – Ramarren 2012-02-28 09:57:54

0

我是一个有点新的口齿不清,所以也许我失踪在你的问题中的东西。尽管如此,我认为我明白你在问什么,我想知道你为什么不使用一些现有的结构......即remove-if-not(或remove-if,如果我有东西倒退)和mapcar ......

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

上面打印13(并返回(nil nil),但想必你可以忽略...或者你可以做一个defun定义,做以上,并与(values)结束)。 (如果你想要平局,请将remove-if-not更改为remove-if。)

这可能是一种更明智的方式去解决问题,除非你是出于教学原因或我错过了某些东西......我承认这很有可能。 :)

P.S. Hyperspec info on remove-if, remove-if-not, etc.

+1

OP希望打印列表中通过测试的元素的首次出现,以及所有未通过测试的元素。你的方法不会打印任何通过测试的元素。巧合的是,你确实已经倒过来了'-if-not'。 :)感谢MIT CLHS链接,甚至不知道它存在! – 2012-02-29 07:59:40

+0

@WillNess:好吧......我想我会在这里留下这个答案,以防其他人有用吗?嗯,我忘记了偏好的行为。 确实在HyperSpec链接上。非常非常有用的东西 - 尽管大多数情况下我只是通过随机谷歌搜索得到它。 :) – lindes 2012-03-01 01:49:44

+0

好问题,通常我会使用这样的选项,但在特定情况下,我开发了一些更复杂的函数,实际上可以在一次迭代期间从列表中删除_several_元素,具体取决于测试。正如之前提到的,前面的例子很简单。 – quartus 2012-03-01 14:54:08

相关问题