3

I am aware I can use mapcar to join 2 lists into pairs, however all the map functions seem to quit at the shortest list.

This for example

(cl-mapcar #'concat '("1" "2" "3" "4") '("a" "b" "c"))

returns

("1a" "2b" "3c")

I basically want the same but with "4" included so it looks like this:

("1a" "2b" "3c" "4")
Basil
  • 12,019
  • 43
  • 69
Oly
  • 583
  • 1
  • 5
  • 15

4 Answers4

6

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%")
Basil
  • 12,019
  • 43
  • 69
  • Bonus points for generalising my generalisations to all sequences, i.e. including arrays. – Basil Apr 11 '18 at 01:29
  • cheers basil nice explanation and thanks to the link on why this is the case :) – Oly Apr 11 '18 at 06:16
2

Append the needed number of empty strings to short:

(let* ((long '("1" "2" "3" "4"))
       (short '("a" "b" "c"))

       (_short (append short
                       (make-list (- (list-length long)
                                     (list-length short))
                                  ""))))
  (mapcar* 'concat long _short))
choroba
  • 1,925
  • 10
  • 16
2

Or... you can just loop for yourself:

(let ((long '("1" "2" "3" "4"))
      (short '("a" "b" "c"))
      ret)
  (while (or long short)
    (setq ret (cons (concat
            (car-safe long)
            (car-safe short))
            ret)
      long (cdr long)
      short (cdr short)))
  (nreverse ret))
Tobias
  • 32,569
  • 1
  • 34
  • 75
1

Emacs 26 or newer version of seq.el provides seq-map-indexed

(seq-map-indexed
 (lambda (element index)
   (concat element (seq-elt '("a" "b" "c") index)))
 '("1" "2" "3" "4"))
;; => ("1a" "2b" "3c" "4")

This works because concat can work with nil

(concat "4" nil)
;; => "4"

You can also iter the lists by yourself

(let (result)
  (let ((index 0))
    (while (< index 4)
      (push (concat (nth index '("1" "2" "3" "4"))
                    (nth index '("a" "b" "c")))
            result)
      (setq index (+ 1 index))))
  (nreverse result))
;; => ("1a" "2b" "3c" "4")
xuchunyang
  • 14,302
  • 1
  • 18
  • 39