Учитывая список, как мне обрабатывать N элементов одновременно? Ruby имеет метод each_slice
для Enumerable
, который делает это; что было бы эквивалентно Lisp?
Обрабатывать n элементов из списка за раз в Lisp
Ответы (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))
(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 элемента за раз.
Если бы вы хотели разделить свой список на предикат (в отличие от подсписков фиксированной длины), я бы рекомендовал 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))))
Все ответы практичны и могут быть использованы, но я считаю, что ни один из них точно не повторяет поведение 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
(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
loop
изначально поддерживает это с(loop for ... on ... by ...)
. - person Joshua Taylor   schedule 19.06.2013