To iterate a list per group with a variable number of consecutive items in Emacs Lisp1, use a recursive function that calls butlast:
(defun jk/loop-per (list n) (jk/loop-per-iter list (- (length list) n))) (defun jk/loop-per-iter (list i) (debug (butlast list i)) (when (> i 0) (jk/loop-per-iter (cdr list) (- i 1)))) (jk/loop-per '(1 2 3 4 5) 3)
The jk/loop-per
function takes a list and a number of consecutive items; n.
Internally, it calls the iteration function jk/loop-per-iter
, which the list and the list’s length with n subtracted; i.
Within the iteration function, i is used in the butlast function, which returns all items of a list, except for the last i.
If i is more than zero, the iteration function is called again with the cdr of list and i subtracted by one. The iteration continues until i reaches zero.
Calling the jk/loop-per
function yields the following call sequence:
(jk/loop-per '(1 2 3 4 5) 3) (jk/loop-per-iter '(1 2 3 4 5) 2) ; => '(1 2 3) (jk/loop-per-iter '(2 3 4 5) 1) ; => '(2 3 4) (jk/loop-per-iter '(3 4 5) 0) ; => '(3 4 5)
The previous example produced every consecutive combination of three items from the list.
To iterate over a list producing each value together with its preceding and succeeding value (like when generating links to the previous and next chapters in a book), the list needs to be padded.
To do this, jk/loop-per-padded
prepends a nil to the list before running the iteration, and jk/loop-per-padded-iter
doesn’t stop the iteration until i reaches -1:
(defun jk/loop-per-padded (list n) (jk/loop-per-padded-iter (push nil list) (- (length list) n))) (defun jk/loop-per-padded-iter (list i) (debug (butlast list i)) (when (> i -1) (jk/loop-per-padded-iter (cdr list) (- i 1)))) (jk/loop-per-padded '(1 2 3 4 5) 3)
Calling jk/loop-per-padded
produces the following call sequence:
(jk/loop-per-padded '(1 2 3 4 5) 3) (jk/loop-per-padded-iter '(nil 1 2 3 4 5) 3) ; => '(nil 1 2) (jk/loop-per-padded-iter '(1 2 3 4 5) 2) ; => '(1 2 3) (jk/loop-per-padded-iter '(2 3 4 5) 1) ; => '(2 3 4) (jk/loop-per-padded-iter '(3 4 5) 0) ; => '(3 4 5) (jk/loop-per-padded-iter '(4 5) -1) ; => '(4 5 nil)
In Ruby, a function like this is named
↩︎Enumerable#each_cons
, as it iterates the block for each consecutive n elements.