问题描述
我试图使用标准循环工具来收集结果,但它只返回 nil。为什么是这样?感觉这应该有效:
(defun coll-intersects (bounds mv)
(let ((res (list))
(loop for x from (first bounds) to (+ (first bounds) (third bounds)) do
(loop for y from (second bounds) to (+ (second bounds) (fourth bounds))
if (not (member (cl-byte (aref mapa x y)) mv))
collect (aref mapa x y) into res
))))
但是不,我必须这样做:
(defun coll-intersects (bounds mv)
(let ((res (list)))
(loop for x from (first bounds) to (+ (first bounds) (third bounds)) do
(loop for y from (second bounds) to (+ (second bounds) (fourth bounds))
do
(if (not (member (cl-byte (aref mapa x y)) mv))
(push (aref mapa x y) res))
))
res))
为什么?我真的很困惑为什么第一个不起作用
解决方法
正如 Ehvince 的回答所说,问题在于
(loop ...
collect ... into x
...)
绑定x
。这个构造的目的真的是让你可以收集多个列表:
(defun partition (l)
(loop for e in l
if (evenp e)
collect e into evens
else
collect e into odds
finally (return (values evens odds))))
例如。
如果您想从嵌套循环中收集单个列表并且您关心顺序,您可以使用以下技巧:
(defun sublist-evens (l)
(loop for s in l
nconcing
(loop for e in s
when (evenp e)
collect e)))
这里的外循环本质上是 nconc
将内循环的结果放在一起。这当然可以嵌套:
(loop ...
nconcing
(loop ...
nconcing
(loop ...
collect ...)))
会起作用。也有可能 loop
足够聪明,可以使用 nconc
/ nconcing
保留指向它正在构建的列表的尾指针,尽管您必须检查这一点。
但是,如果您想从某个深层嵌套循环(或任何其他搜索过程)中按顺序构建一些列表,我发现使用 collecting macro 几乎总是更令人愉快(免责声明:我写了这个)。使用这样的宏,上面的 sublist-evens
函数看起来像这样:
(defun sublist-evens (l)
(collecting
(dolist (s l)
(dolist (e s)
(when (evenp e) (collect e))))))
和
> (sublist-evens '((1 2 3) (4 5 6)))
(2 4 6)
而且你可以做得更好:
(defun tree-partition (tree)
(with-collectors (evens odds)
(labels ((search (it)
(typecase it
(list
(dolist (e it)
(search e)))
(integer
(if (evenp it)
(evens it)
(odds it)))
(t
(warn "unexpected ~A" (type-of it))))))
(search tree))))
现在
> (tree-partition '(((1 2 3) (4)) 5))
(2 4)
(1 3 5)
(对于 hack 值,您可以使用 another macro 更简洁地表达上述内容:
(defun tree-partition (tree)
(with-collectors (evens odds)
(iterate search ((it tree))
(typecase it
(list
(dolist (e it)
(search e)))
(integer
(if (evenp it)
(evens it)
(odds it)))
(t
(warn "unexpected ~A" (type-of it)))))))
免责声明:我也写了那个宏。)
,这是第一个片段,更正了 let
括号,简化为可重现:
(defun coll-intersects (bounds mv)
(let ((res (list))) ;; <-- third closing paren
(loop for x from (first bounds) to (+ (first bounds) (third bounds)) do
(loop for y from (second bounds) to (+ (second bounds) (fourth bounds))
if (evenp y)
collect y into res
))))
现在,当我将其输入到 REPL 中时,SBCL 会警告我未使用的 res
:
; caught STYLE-WARNING:
; The variable RES is defined but never used.
这是一个很大的提示。
我看到的问题:
- 您将
do
用于外循环,而不是收集,并且您不返回res
,因此函数始终返回 nil。 -
collect … into
大概使用内部变量,而不是你的res
:S 此外,循环没有说明如何处理它。我添加了finally (return res)
并得到了结果。您也可以像在第二个示例中一样使用push
。不过好像没必要用into
,用collect y
就好了。 - 通常不需要使用外部
let
声明中间变量。
这是一个返回(哑)结果的更简单的函数:
(defun coll-intersects (bounds)
(loop for x from (first bounds) to (+ (first bounds) (third bounds)) collect
(loop for y from (second bounds) to (+ (second bounds) (fourth bounds))
if (evenp y)
collect y)))
(coll-intersects '(1 2 3 4))
((2 4 6) (2 4 6) (2 4 6) (2 4 6))
如果您使用 nconcing
而不是第一个 collect
,您将得到一个平面列表(如@tfb 所指出的)。
或:
(defun coll-intersects (bounds)
(let ((res (list)))
(loop for x from (first bounds) to (+ (first bounds) (third bounds)) do
(loop for y from (second bounds) to (+ (second bounds) (fourth bounds))
if (evenp y)
do (push y res)
))
res))
(coll-intersects '(1 2 3 4))
(6 4 2 6 4 2 6 4 2 6 4 2)
,
在您的第一个示例中,函数的返回值是外部循环的返回值。它不收集任何值(内循环会),因此很可能只返回一个 nil
。
在您的第二个示例中,您的函数显式返回 res
的值。