调整Common Lisp“ pushnew”以返回成功/失败

dav*_*ugh 1 macros common-lisp anonymous-function

Common Lisp宏pushnew返回作为参数给出的(可能已更新)位置列表。看来,如果您想知道给定的项目是否实际被推送,则需要比较放置前和放置后列表以查看其是否已更改。但这对于我的使用来说效率太低,因为它将涉及重复比较两个复杂结构对象的列表以得出等效结果。

另一种可行的替代方法是使用:test参数来pushnew记录是否已找到该项目,因为已扫描列表元素。如果找到该项目,则可以将T作为第二个整数值返回,否则返回NIL。下面的宏尝试执行此操作:

(defmacro pushnew+p (item place &key test (key #'identity) &aux old?)
  "Same as pushnew, but also returns whether item was pushed."
  `(values (pushnew ,item ,place
                    :test (lambda (item element)
                            (if ,test
                              (setf old? (funcall ,test item element))
                              (setf old? (eql item element))))
                    :key ,key)
           (not old?)))
Run Code Online (Sandbox Code Playgroud)

似乎工作正常,因为

(defparameter x '(1 2 3))

(pushnew+p 0 x) -> (0 1 2 3), T

(pushnew+p 2 x) -> (0 1 2 3), NIL

(defparameter y '((1) (2) (3)))

(pushnew+p '(0) y :test #'equal) -> ((0) (1) (2) (3)), T

(pushnew+p '(2) y :test #'equal) -> ((0) (1) (2) (3)), NIL
Run Code Online (Sandbox Code Playgroud)

但是,我不确定:test参数的工作方式,并且Hyperspec条目pushnew未明确说明。是否保证列表元素的测试会在测试返回T时立即停止(如需要)?(或者remove,测试应该继续进行到序列的末尾。)如果测试继续进行到末尾,那么return-from尽早终止的最佳方法是吗?

另一个问题是匿名lambda主体反复检查,test每个元素的给定参数。可以将其移出lambda吗?感谢您的任何建议。

sds*_*sds 6

我认为,更好的办法是检查是否有新的元素,其实,前置按规定pushnew

(unless (eq item (car place))
  (pushnew item place ...)
  (when (eq item (car place))
    ;; not found, prepended
    ))
Run Code Online (Sandbox Code Playgroud)

请注意,即使您传递:testpushnew,您仍然可以equnless和中使用when

PS。请注意@coredump评论中提到的特殊情况。

  • 小拐角处:如果ITEM为NIL并且PLACE为空,则结果不是(NIL)符合预期。例如,第一个测试可以是“(除非(和地点(eq项目(汽车地点)))...)”。 (2认同)