根据项目的第三个元素查找并删除列表项

时间:2013-12-10 07:48:03

标签: lisp common-lisp

当项目的第三个元素与传递给函数的值匹配时,我需要查找并返回列表中的第一个项目。然后我需要将该项永久删除。

我已经写了这个函数来做这件事,我想知道是否有任何内置函数可以完成同样的事情而没有这个相当混乱的实现:

(defun find-remove-third (x) 
  (let ((item (first (member x *test-list* :key #'third)))) 
    (setf *test-list*  (remove item *test-list* :test #'equal)) 
    item))

操作:

CL-USER> *test-list*
((1 2 3) (2 3 4) (3 4 5) (4 4 4) (5 4 3) (6 5 4) (2 2 2))
CL-USER> (find-remove-third 4)
(2 3 4)
CL-USER> *test-list*
((1 2 3) (3 4 5) (4 4 4) (5 4 3) (6 5 4) (2 2 2))
CL-USER> (find-remove-third 4)
(4 4 4)
CL-USER> *test-list*
((1 2 3) (3 4 5) (5 4 3) (6 5 4) (2 2 2))

例如pop变异并从列表返回,虽然更有限,但我想知道是否有可能比我上面的函数更优雅,或者这个实现是否正常且惯用?

2 个答案:

答案 0 :(得分:3)

您的实施扫描列表两次,因此它不是最理想的。

我认为如果没有显式循环(或等效的递归),你不能写出你需要的东西:

(defun pop-from-list (object list &key (key #'identity) (test #'eql) kept)
  "Like `remove', but return the object removed as the second value."
  (let ((1st (car list)))
    (if (funcall test object 1st)
        (values (revappend kept (rest list))
                1st)
        (pop-from-list object (rest list) :key key :test test
                       :kept (cons 1st kept)))))

现在您可以像这样定义您的函数:

(defun find-remove-third (x)
  (multiple-value-bind (list object)
      (pop-from-list x *test-list* :key #'third)
    (setq *test-list* list)
    object))

答案 1 :(得分:2)

编辑 - 删除这个似乎不对,所以我会把它留下来,但正如@sds和@WillNess在评论中指出的那样,这有严重的问题。

这是一个破坏性版本,只扫描列表一次。它具有潜在的好处,您不必硬编码您正在操作的列表的名称。

CL-USER> (defun find&remove (list obj &key (key #'identity) (test #'eql))
           (loop with last = nil
                 for cons on list
                 when (funcall test obj (funcall key (first cons))) do
                    (progn (setf (rest last) (rest cons))
                           (return (first cons)))
                 do (setf last cons)))

CL-USER> (defvar test-list (list (list 1 2 3)
                                 (list 3 4 5)
                                 (list 5 6 7)
                                 (list 8 9 10)))

CL-USER> (find&remove test-list 5 :key #'third)
(3 4 5)
CL-USER> test-list
((1 2 3) (5 6 7) (8 9 10))
CL-USER> (find&remove test-list 7 :key #'third)
(5 6 7)
CL-USER> test-list
((1 2 3) (8 9 10))

关键是通过cons单元而不是项目(loop for ... on而不是loop for ... in)遍历列表,并保留指向我们已经查看过的列表部分的指针({{ 1}})。然后,当我们找到我们正在寻找的内容时,我们将我们已经看到的last连接到 next 缺点(所以现在列表省略了“命中”)和最后返回结果。