Iterate lists per group in Emacs Lisp

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)

  1. In Ruby, a function like this is named Enumerable#each_cons, as it iterates the block for each consecutive n elements.

    ↩︎