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?
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:).
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"))
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"))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With