The two built-in "zip-with" functions:
and the most prominent third-party one:
all share the limitation you describe:
all the map functions seem to quit at the shortest list
The philosophical question of why this is often the case has been raised before: https://softwareengineering.stackexchange.com/q/274983/271683.
If you want to use the aforementioned functions, you need to pad the shorter lists, as choroba points out.
One way to do this is to use the -pad
function added to Dash 2.7.0:
(let* ((xs '("1" "2" "3" "4"))
(ys '("a" "b" "c"))
(zs (-pad () xs ys)))
;; The following all return ("1a" "2b" "3c" "4")
(apply #'seq-mapn #'concat zs)
(apply #'cl-mapcar #'concat zs)
(apply #'-zip-with #'concat zs))
I'm not aware of such a built-in function, but it's pretty simple to define yourself, if you'd rather not use an external library. Here's a generalisation of choroba's solution:
(defun my-pad (pad &rest lists)
"Return LISTS such that they are all of equal length.
PAD is appended to shorter lists as many times as is required to
achieve this."
(let* ((lengths (mapcar #'length lists))
(maxlength (apply #'max 0 lengths)))
(mapcar (lambda (length)
(append (pop lists) (make-list (- maxlength length) pad)))
lengths)))
(my-pad 0) ; ⇒ ()
(my-pad 0 ()) ; ⇒ (())
(my-pad 0 () ()) ; ⇒ (() ())
(my-pad 0 () '(1 2 3) ()) ; ⇒ ((0 0 0) (1 2 3) (0 0 0))
As Tobias points out, you can also do the padding and zipping in a single step, possibly to achieve better performance. Here's a generalisation of Tobias' solution:
(defun my-zip-with-pad (fn pad &rest lists)
"Return result of convolving FN across LISTS.
The result is a list of applying FN to all elements of LISTS at
the corresponding index. Whenever this index is beyond the
bounds of a list, PAD is used as a substitute value."
(let (result)
(while (let (args loop)
(dolist (list lists)
;; Loop as long as at least one list is non-empty
(or loop (setq loop list))
(push (if list (car list) pad) args))
(and loop (push (apply fn (nreverse args)) result)))
(setq lists (mapcar #'cdr lists)))
(nreverse result)))
(my-zip-with-pad #'concat ())
;; ⇒ ()
(my-zip-with-pad #'concat () ())
;; ⇒ ()
(my-zip-with-pad #'concat () '("1"))
;; ⇒ ("1")
(my-zip-with-pad #'concat () '("1" "2" "3") '("a" "b") '("#" "$" "%"))
;; ⇒ ("1a#" "2b$" "3%")