【问题标题】:why is inner loop collect not returning results?为什么内循环收集不返回结果?
【发布时间】:2021-08-21 08:02:39
【问题描述】:

我试图使用标准循环工具来收集结果,但它只返回 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))

为什么?我真的很困惑为什么第一个不起作用

【问题讨论】:

  • 是复制粘贴错字吗?第一个示例的let 缺少右括号:(let ((res (list)))。您的 Lisp 应该警告您有关格式错误的 let 绑定。​​
  • 对不起,这是我尝试过的东西的娱乐。在我尝试过的那个中,我没有错过那个右括号

标签: loops common-lisp


【解决方案1】:

正如 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)))

这里的外循环本质上是nconcs 和内循环的结果。这当然可以嵌套:

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

免责声明:我也写了那个宏。)

【讨论】:

  • 令人信服,不错!我还将指出在 Serapeum 中收集实用程序:github.com/ruricolist/serapeum/blob/master/…
  • @Ehvince:是的,我认为几代人已经重新发明了 collecting 这样的东西。我的来自 Interlisp-D 的 TCONCDOCOLLECT / ENDCOLLECT 对:我不确定是哪个,但我知道我是在 D 机器上写的。
【解决方案2】:

这是第一个 sn-p,修正了 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 大概使用了内部变量,而不是 your 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)

【讨论】:

    【解决方案3】:

    在您的第一个示例中,函数的返回值是外循环的返回值。它不收集任何值(内部循环收集),因此很可能只返回一个nil

    在您的第二个示例中,您的函数显式返回 res 的值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-05
      • 1970-01-01
      • 1970-01-01
      • 2011-08-18
      • 2021-09-24
      • 1970-01-01
      相关资源
      最近更新 更多