Обрабатывать n элементов из списка за раз в Lisp

Учитывая список, как мне обрабатывать N элементов одновременно? Ruby имеет метод each_slice для Enumerable, который делает это; что было бы эквивалентно Lisp?


person rebnoob    schedule 19.06.2013    source источник
comment
В CL нет ничего встроенного для этого, но вы можете легко создать его, используя SUBSEQ в цикле.   -  person Barmar    schedule 19.06.2013
comment
Использование subseq в цикле, вероятно, приведет к выделению большого количества промежуточной памяти. Макрос loop изначально поддерживает это с (loop for ... on ... by ...).   -  person Joshua Taylor    schedule 19.06.2013
comment
Спасибо за ответы всем!   -  person rebnoob    schedule 20.06.2013


Ответы (4)


Для этого очень хорошо можно использовать loop Common Lisp, как в следующие два примера. Первый пример зацикливается на (x y z) в списке. Однако шаг по умолчанию — cdr (rest), поэтому, если список равен (1 2 3 4 5), вы получите (1 2 3), (2 3 4) и т. д. для (x y z).

CL-USER> (loop for (x y z) on '(1 2 3 4 5 6 7 8 9 10 11 12)
              do (print (list z y x)))

(3 2 1) 
(4 3 2) 
(5 4 3) 
(6 5 4) 
(7 6 5) 
(8 7 6) 
(9 8 7) 
(10 9 8) 
(11 10 9) 
(12 11 10) 
(NIL 12 11) 
(NIL NIL 12) 
NIL

Если вы не хотите перекрытия между итерациями, задайте пошаговую функцию, чтобы она перемещалась дальше вниз по списку. Например, если вы извлекаете три элемента одновременно, используйте cdddr:

CL-USER> (loop for (x y z) on '(1 2 3 4 5 6 7 8 9 10 11 12) by 'cdddr
              do (print (list z y x)))
(3 2 1) 
(6 5 4) 
(9 8 7) 
(12 11 10) 
NIL

Реализация раздела с помощью этой техники

Другой ответ реализовал аналог each_slice с помощью вспомогательной функции. Однако обратите внимание, что partition (в этом смысле) — это просто each_slice с функцией идентификации. Это говорит о том, что мы должны быть в состоянии реализовать это, используя приведенную выше идиому. Анонимная функция

(lambda (list)
  (nthcdr n list))

— нужная нам ступенчатая функция. Поскольку мы не знаем, сколько элементов содержится в ячейках до момента выполнения, мы не можем связать каждый элемент, как мы сделали выше с (x y z). Нам нужно сопоставить каждый хвост списка, когда мы делаем шаг вниз и извлекаем элементы подпоследовательности n. Вот реализация partition на основе loop.

CL-USER> (defun partition (list cell-size)
           (loop for cell on list by #'(lambda (list)
                                         (nthcdr cell-size list))
              collecting (subseq cell 0 cell-size)))
PARTITION

CL-USER> (partition '(1 2 3 4 5 6) 2)
((1 2) (3 4) (5 6))
person Joshua Taylor    schedule 19.06.2013
comment
Спасибо Джошуа! Использовал ваш второй фрагмент кода; нужны 2 elts, поэтому используется cddr. Любимый Лисп! - person rebnoob; 20.06.2013
comment
@rebnoob Рад это слышать! В Common Lisp есть много функций, упакованных в стандарте, поэтому было бы неплохо просмотреть оглавление и подпункты подразделов, не для того, чтобы узнать все подробно, а чтобы иметь представление, что там есть, и знать, где искать, когда что-то захочется. Например, мне пришлось проверить синтаксис (loop ... on ... by ...), чтобы убедиться, что он правильный, но у меня была идея, где искать. - person Joshua Taylor; 20.06.2013

(defun partition-helper (lst acc x)
  (if (< (length lst) x)
    acc
    (partition-helper (subseq lst x) (cons (subseq lst 0 x) acc) x)))

(defun partition (lst x)
  (reverse (partition-helper lst '() x)))

Тогда ты можешь:

[25]> (PARTITION '(1 2 3 4 5 6) 2)
((1 2) (3 4) (5 6))

or:

[26]> (PARTITION '(1 2 3 4 5 6) 3)
((1 2 3) (4 5 6))

а затем просто mapcar по списку, чтобы обработать 2 или 3 элемента за раз.

person DJG    schedule 19.06.2013

Если бы вы хотели разделить свой список на предикат (в отличие от подсписков фиксированной длины), я бы рекомендовал nsplit-list.

Для подсписков фиксированной длины вы можете использовать loop:

(defun by-N (list n fun) 
  (loop for tail on list by (lambda (l) (nthcdr n l)) 
    do (funcall fun (subseq tail 0 (min (length tail) n)))))
(by-n (loop for i from 0 to 20 collect i) 5 #'print)

(0 1 2 3 4) 
(5 6 7 8 9) 
(10 11 12 13 14) 
(15 16 17 18 19) 
(20) 

Обратите внимание, что это не очень эффективно (он просматривает список больше, чем необходимо, и выделяет новый подсписок для передачи в fun).

Эффективная версия сложнее:

(defun batch-map (list batch-size function)
  "Call FUNCTION on sublists of LIST of size BATCH-SIZE.
Returns the list of return values of FUNCTION."
  (do ((tail list (cdr end)) end ret (bs1 (1- batch-size)))
      ((endp tail) (nreverse ret))
    (setq end (nthcdr bs1 tail))
    (if (consp end)
        (let ((next (cdr end)))
          (setf (cdr end) nil)
          (unwind-protect (push (funcall function tail) ret)
            (setf (cdr end) next)))
        (push (funcall function tail) ret))))
person sds    schedule 19.06.2013

Все ответы практичны и могут быть использованы, но я считаю, что ни один из них точно не повторяет поведение Ruby:

> 1.upto(7).each_slice(3) { |x, y, z| p [x, y, z] }
[1, 2, 3]
[4, 5, 6]
[7, nil, nil]

Я считаю, что для эмуляции Ruby правильный код похож на:

CL-USER> (defun each-slice (n list thunk)
  (apply thunk (loop for i below n collect (nth i list)))

  (if (> (length list) n)
      (each-slice n (subseq list n) thunk)))

Генерирует аналогичный ответ при вызове:

CL-USER> (each-slice 3 '(1 2 3 4 5 6 7) (lambda (x y z) (print (list x y z))))

(1 2 3) 
(4 5 6) 
(7 NIL NIL) 
NIL
person Everton J. Carpes    schedule 15.04.2020
comment
подход принятого ответа уже делает это: (loop for (x y z) on '(1 2 3 4) by 'cdddr do (print (list x y z))) печатает (1 2 3) (4 NIL NIL) и возвращает NIL. - person Will Ness; 15.04.2020
comment
Да, вы правы, он работает, добавляя дополнительные значения NIL, но требует, чтобы вы настроили параметр by, используя cd*dr или nthcdr внутри дополнительной лямбды. Есть также другое поведение из each_slice Ruby, которое версия цикла эмулирует лучше... в Ruby, если вы вызываете each_slice без заданного блока, он возвращает экземпляр Enumerator, который можно связать в цепочку, и это можно лучше эмулировать, используя цикл и собирая результат. - person Everton J. Carpes; 16.04.2020