почему сбор внутреннего цикла не возвращает результатов?

#loops #common-lisp

Вопрос:

Я пытался использовать стандартные средства цикла для получения результата, но он просто возвращает ноль. Почему это так? такое чувство, что это должно сработать:

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

почему? я был действительно сбит с толку, почему первый не работал

Комментарии:

1. Это опечатка с копипастом? В let первом примере отсутствует закрывающая скобка: (let ((res (list))) . Ваш шепелявый должен предупредить вас о неправильной привязке let.

2. извините, это было воссоздание того, что я пробовал. В том, что я попробовал, я не пропустил эту закрывающую скобу

Ответ №1:

Как говорится в ответе Эвинса, проблема в том, что

 (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 , хотя вам придется это проверить.

Однако в тех случаях, когда вы хотите построить какой-либо список по порядку из какого-либо глубоко вложенного цикла (или любого другого процесса поиска) Я нахожу, что почти всегда приятнее использовать для этого макрос сбора (отказ от ответственности: я написал это). С таким макросом вышеуказанная 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)
 

(И для значения взлома вы можете использовать другой макрос, чтобы выразить вышесказанное более кратко:

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

Отказ от ответственности: я тоже написал этот макрос.)

Комментарии:

1. согласна, милая! Я также укажу на сбор коммунальных услуг в Серапеуме: github.com/ruricolist/serapeum/blob/master/…

2. @Ehvince: да, я думаю, что поколения изобретали такие вещи, как collecting . Мой пришел от Interlisp-D TCONC или, возможно DOCOLLECT , / ENDCOLLECT пары: я не уверен, какой именно, но я знаю, что написал оригинал на D-машине.

Ответ №2:

Вот первый фрагмент, с 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 , поэтому функции всегда возвращают ноль.
  • 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)
 

Ответ №3:

В вашем первом примере возвращаемое значение функции является возвращаемым значением из внешнего цикла. Он не собирает никаких значений (это делает внутренний цикл) и, таким образом, скорее всего, просто возвращает a nil .

Во втором примере ваша функция явно возвращает значение res .