Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

zip up two list of different lengths in elisp

Tags:

elisp

I have two lists:

(setq x (list "a" "b" "c"))
(setq y (list "1" "2" "3" "4"))

How can I create a list of cons cells (("a" . "1") ("b" . "2") ("c" . "3") ("a" . "4")) with the shorter list recycled?

like image 878
RNA Avatar asked Jul 22 '13 22:07

RNA


Video Answer


3 Answers

Here's my take:

(require 'cl-lib)
(cl-mapcar #'list (setcdr (last x) x) y)

I'd add a check for which of them is larger, but that would spoil the brevity:).

like image 144
abo-abo Avatar answered Sep 23 '22 21:09

abo-abo


There is surely a simpler way to do it, but here's a version that turns the input sequences into infinite lists and zips them together:

(defun* cycle-iterator (xs &optional (idx 0) (len (length xs)))
  "Create an iterator that will cycle over the elements in XS.
Return a cons, where the car is the current value and the cdr is
a function to continue the iteration."
  (cons (nth (mod idx len) xs)
        (eval `(lambda () (cycle-iterator ',xs ,(1+ idx) ,len)))))

(defun cycle-take (xs n)
  "Take N elements from XS, cycling the elements if N exceeds the length of XS."
  (loop
   when (plusp n)
   ;; Creating the iterator returns the first value. Subsequent calls can then
   ;; be processed in a loop.
   with (value . iterator) = (cycle-iterator xs)
   with acc = (list value)
   repeat (1- n) do (destructuring-bind (val . next) (funcall iterator)
                      (setq iterator next)
                      (setq acc (cons val acc)))
   finally (return (nreverse acc))))

(defun cycling-zip (xs ys)
  "Zip XS and YS together, cycling elements to ensure the result
  is as long as the longest input list."
  (loop
   with limit = (max (length xs) (length ys))
   for x in (cycle-take xs limit)
   for y in (cycle-take ys limit)
   collect (cons x y)))


;; Usage:
(cycling-zip '("a" "b" "c") '("1" "2" "3" "4"))
; => (("a" . "1") ("b" . "2") ("c" . "3") ("a" . "4"))
like image 26
Chris Barrett Avatar answered Sep 24 '22 21:09

Chris Barrett


This answer requires dash list manipulation library. Before attacking your problem, it's good to find the length of the longest list. The first way I came up with is:

(require 'dash)
(require 'dash-functional)
(length (-max-by (-on '> 'length) (list x y))) ; 4

-on is a smart function from package dash-functional that accepts a comparator, a key on which to compare, and returns a function that compares on this key. Therefore (-max-by (-on '> 'length) xs) finds an element in xs, whose length is biggest. But this expression is too smart for its own sake, and dash-functional only works in Emacs 24 due to lexical scoping. Let's rewrite it, inspired by Python solution:

(-max (-map 'length (list x y))) ; 4

To take first n elements from an infinite cycled list, do (-take n (-cycle xs)). Therefore to create an alist, where elements from a smaller list are cycled, write:

(let ((len (-max (-map 'length (list x y)))))
  (flet ((cycle (xs) (-take len (-cycle xs))))
    (-zip (cycle x) (cycle y)))) ; (("a" . "1") ("b" . "2") ("c" . "3") ("a" . "4"))
like image 21
Mirzhan Irkegulov Avatar answered Sep 24 '22 21:09

Mirzhan Irkegulov