Lisp 删除+;函数中的SETF
我正在尝试编写一个函数,它将从列表中破坏性地删除Lisp 删除+;函数中的SETF,lisp,common-lisp,Lisp,Common Lisp,我正在尝试编写一个函数,它将从列表中破坏性地删除N元素并返回它们。我提出的代码(见下文)看起来不错,除了SETF没有按照我的预期工作 (defun pick (n from) "Deletes (destructively) n random items from FROM list and returns them" (loop with removed = nil for i below (min n (length from)) do (let ((to-d
N
元素并返回它们。我提出的代码(见下文)看起来不错,除了SETF
没有按照我的预期工作
(defun pick (n from)
"Deletes (destructively) n random items from FROM list and returns them"
(loop with removed = nil
for i below (min n (length from)) do
(let ((to-delete (alexandria:random-elt from)))
(setf from (delete to-delete from :count 1 :test #'equal)
removed (nconc removed (list to-delete))))
finally (return removed)))
在大多数情况下,这很好:
CL-USER> (defparameter foo (loop for i below 10 collect i))
CL-USER> (pick 3 foo)
(1 3 6)
CL-USER> foo
(0 2 4 5 7 8 9)
CL-USER> (pick 3 foo)
(8 7 0)
CL-USER> foo
(0 2 4 5 9)
如您所见,PICK
工作正常(在SBCL上),除非所拾取的元素恰好是列表中的第一个元素。在这种情况下,它不会被删除。这是因为唯一发生的重新分配是在DELETE
内部进行的。SETF
无法正常工作(即,如果改用REMOVE
,FOO
根本不会改变)
是否存在我不知道的任何作用域规则?
delete
不一定修改其序列参数。正如政府所说:
delete
、delete if
和delete if not
返回一个与序列类型相同的序列,该序列具有相同的元素,但以start
和end
为界且满足测试的子序列中的元素已被删除。序列可能被破坏并用于构建结果;然而,结果可能与序列相同,也可能不相同
例如,在SBCL中:
* (defvar f (loop for i below 10 collect i))
F
* (defvar g (delete 0 f :count 1 :test #'equal))
G
* g
(1 2 3 4 5 6 7 8 9)
* f
(0 1 2 3 4 5 6 7 8 9)
*
请注意,在函数setf
中,修改来自的局部变量,并且由于delete
在第一个元素的情况下不会修改原始列表,因此在函数末尾,变量foo
会保留旧值。修改任何结构都不需要delete,这是允许的。事实上,你不能总是进行破坏性的删除。如果要从(42)中删除42,则需要返回空列表(),即符号NIL,但无法将列表(42),即cons单元格(42.NIL)转换为不同类型的对象(符号NIL)。因此,您可能需要返回已更新的列表以及已删除的元素。您可以使用类似的方法执行此操作,它返回多个值:
(defun pick (n from)
(do ((elements '()))
((or (endp from) (zerop n))
(values elements from))
(let ((element (alexandria:random-elt from)))
(setf from (delete element from)
elements (list* element elements))
(decf n))))
在接收端,您需要使用类似于多值绑定或多值setq的东西:
(let ((from (list 1 2 3 4 5 6 7)))
(multiple-value-bind (removed from)
(pick 2 from)
(format t "removed: ~a, from: ~a" removed from)))
; removed: (7 4), from: (1 2 3 5 6)
(正确的)列表由cons单元格组成,每个单元格保存对下一个单元格的引用
牢房。因此,它实际上是一个引用链,并且您的变量有一个
对第一个单元格的引用。为了说明这一点,我将绑定重命名为outside
将您的函数设置为var
:
var ---> [a|]--->[b|]--->[c|nil]
将变量的值传递给函数时,参数
绑定到同一引用
var ---> [a|]--->[b|]--->[c|nil]
/
from --'
您可以更新链中的引用,例如消除b
:
var ---> [a|]--->[c|nil]
/
from --'
这会影响var
在外部看到的列表
如果更改第一个引用,例如删除a
,这只是
源于
的一个:
var ---> [a|]--->[b|]--->[c|nil]
/
from --'
这显然对var
看到的内容没有影响
您需要实际更新有问题的变量绑定。你能做到的
通过将其设置为函数返回的值。既然你已经退了
不同的值,这将是一个额外的返回值
(defun pick (n list)
(;; … separate picked and rest, then
(values picked rest)))
然后您可以这样使用,例如:
(let ((var (list 1 2 3)))
(multiple-value-bind (picked rest) (pick 2 var)
(setf var rest)
(do-something-with picked var)))
现在谈谈分离:除非名单太长,否则我会坚持
非破坏性行动。我也不会使用随机elt
,因为它需要
每次遍历O(m)个元素(m是列表的大小),
导致运行时间为O(n·m)
您可以通过确定当前选择的机会来获得O(m)总体运行时间
在列表上线性运行时的当前项。然后您收集
将项目放入已拾取或剩余列表中
(defun pick (n list)
(loop :for e :in list
:and l :downfrom (length list)
:when (or (zerop n)
(>= (random 1.0) (/ n l)))
:collect e :into rest
:else
:collect e :into picked
:and :do (decf n)
:finally (return (values picked rest))))
所以,我刚刚发现其中似乎有一个类似的问题。不过,我没有使用引用的数据。这里有什么我遗漏的吗?下面的(minn n(length from))
如果要更改列表的长度,将没有多大意义。您不能编写“从列表中删除(破坏性地)n个随机项并返回它们”的函数。如果要从(42)
中删除一个随机项,会发生什么情况。你不能把cons单元格变成一个空列表。我知道我在改变列表的长度。我只想确保更改的次数不超过列表本身的长度,因此下面的子句就是这样。如果我删除它,循环
将抛出一个错误。其实这并不重要,我只是从一个数百个项目的列表中删除了几十个项目。你可能会发现这些问题和答案很有帮助。这是一个非常简单的例子;如果您只需要从列表中删除第一个元素,最简单的方法就是返回列表的其余部分。如果您尝试以一种没有元素的方式删除,则删除不会具有破坏性。例如,如果您有(1)
,并且尝试删除1
,则无法返回同一cons单元格,必须返回nil
。无法将(1)
设为空。我知道我不能指望删除来更改列表的值。这就是我试图在函数中设置它的原因。问题是,SETF
似乎在函数之外没有任何效果。刚刚看到您的编辑。因此,
中的是我函数的局部变量。知道了!“通过在列表上线性运行时确定拾取当前项目的当前机会,可以获得O(m)总体运行时间。然后将项目收集到拾取列表或剩余列表中。”如果列表中包含重复的元素,这将不太正确。@JoshuaTaylor:我不理解指定删除随机选择的元素的所有重复项的要求(即使错误的尝试似乎暗示了这种效果)。你质疑这一点是对的,我或许应该要求澄清,但我敢打赌,删除重复项是实际的
(let ((var (list 1 2 3)))
(multiple-value-bind (picked rest) (pick 2 var)
(setf var rest)
(do-something-with picked var)))
(defun pick (n list)
(loop :for e :in list
:and l :downfrom (length list)
:when (or (zerop n)
(>= (random 1.0) (/ n l)))
:collect e :into rest
:else
:collect e :into picked
:and :do (decf n)
:finally (return (values picked rest))))