Hung-Yi’s Journal

Front-end Developer, Emacs Adventurer, Home Cook

02 Apr 2021

Split a List Into Batches Using Emacs Lisp

Sometimes a list of things is too large and it needs to be split into batches before it can be processed effectively. A quick google didn't reveal any quick copy-pastable elisp (a.k.a. Emacs Lisp) functions that did this kind of batching, so I cobbled together what I found and came up with this cl-loop implementation, which is hopefully performant.

Feel free to copy paste it as is, since it should be general enough to use anywhere 💻

(require 'cl-lib)

(defun my/batch-list (input size)
  "Split INPUT list into a batches (i.e. sublists) of maximum SIZE."
  (when (< size 1)
    (error "SIZE of the batches must be at least 1"))
  (unless (seqp input)
    (error "INPUT must be a sequence or list"))
  (cl-loop with tail = input
           while tail
           collect (cl-loop for ptr on tail
                            for i upfrom 0
                            while (< i size)
                            collect (car ptr)
                            finally (setf tail ptr))))

Example Usage & Unit Tests

To use it, just call it with the input list and the maximum size of the batches that it should split into.

For example, splitting a list of (1 2 3 4 5 6 7 8 9) into batches of max size 3 🧙

(my/batch-list '(1 2 3 4 5 6 7 8 9) 3)
((1 2 3)
 (4 5 6)
 (7 8 9))

And a batch size of 4 to see what happens when the size doesn't evenly divide the length of the input list.

(my/batch-list '(1 2 3 4 5 6 7 8 9) 4)

Spoiler: the last batch is chopped short compared to the others 🪓

((1 2 3 4)
 (5 6 7 8)
 (9))

Let's try some other tricky inputs to check for sane behavior 😈 1

Like a size that is equal to the length of the input list ⚖️

(my/batch-list '(1 2 3 4 5 6 7 8 9) 9)
((1 2 3 4 5 6 7 8 9))

Or a size that exceeds the length of input 🌋

(my/batch-list '(1 2 3 4 5 6 7 8 9) 10)
((1 2 3 4 5 6 7 8 9))

Or a batch size of … just 1? 🤔

(my/batch-list '(1 2 3 4 5 6 7 8 9) 1)
((1)
 (2)
 (3)
 (4)
 (5)
 (6)
 (7)
 (8)
 (9))

1

This kind of messing around is actually called Boundary Testing or Boundary-Value Analysis and is a good way to ensure your code is robust. You'll also notice in the function that I also checked that the size is above 0, otherwise cl-loop would run forever.