Process n items from a list at a time in Lisp

Given a list, how do I handle N items at a time? Ruby uses each_slice Enumerable method, which does this; What would be the equivalent of Lisp?

+4
source share
3 answers

The general Lisp loop can be used very nicely for this, as in the following two examples. The first example loop for (xyz) in the list. However, the default step is cdr ( rest ), so if the list is (1 2 3 4 5) , you will get (1 2 3) , (2 3 4) , etc. For (xyz) .

 CL-USER> (loop for (xyz) on '(1 2 3 4 5 6 7 8 9 10 11 12) do (print (list zyx))) (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 

If you do not need overlap between iterations, specify that the step function is something that moves further down the list. For example, if you pull three elements at a time, use cdddr :

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

Implementing a section using this method

Another answer implements a copy of each_slice using a helper function. However, note that partition (in this sense) is simply each_slice with an identity function. This suggests that we should be able to implement it using the above idiom. Anonymous function

 (lambda (list) (nthcdr n list)) 

- required step function. Since we do not know how many elements cells have before runtime, we cannot bind each element, as we did above, using (xyz) . We need to match each tail of the list when we leave and retrieve the elements of n subsequences. This is where the partition based loop is executed.

 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)) 
+4
source
 (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))) 

Then you can:

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

and then just mapcar over the list to process its 2 or 3 elements at a time.

+2
source

If you want to split your list into a predicate (as opposed to fixed-length nsplit-list ), I would recommend nsplit-list .

For fixed-length subscriptions, you can use loop :

 (defun by-N (list n fun) (loop for tail on list by (lambda (l) (nthcdr nl)) 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) 

Note that this is not very efficient (it scans the list more than necessary and selects a fresh sublist to go to fun ).

The effective version is more complicated:

 (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)))) 
+2
source

Source: https://habr.com/ru/post/1487116/


All Articles