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")
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%")
  • Bonus points for generalising my generalisations to all sequences, i.e. including arrays. – Basil Apr 11 '18 at 1:29
  • cheers basil nice explanation and thanks to the link on why this is the case :) – Oly Apr 11 '18 at 6: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))
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))
  • 1
    Not sure if you really need car-safe there, car works fine on nil. – npostavs Apr 11 '18 at 0:44
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")
  • 1
    This solution is O(n²) (or, if you expect lists of very different length, O(n*m)) though. – npostavs Apr 11 '18 at 8:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.