36

Let's say that I have two lists in R, not necessarily of equal length, like:

 a <- list('a.1','a.2', 'a.3')
 b <- list('b.1','b.2', 'b.3', 'b.4')

What is the best way to construct a list of interleaved elements where, once the element of the shorter list had been added, the remaining elements of the longer list are append at the end?, like:

interleaved <- list('a.1','b.1','a.2', 'b.2', 'a.3', 'b.3','b.4')

without using a loop. I know that mapply works for the case where both lists have equal length.

papirrin
  • 2,004
  • 22
  • 30

5 Answers5

43

Here's one way:

idx <- order(c(seq_along(a), seq_along(b)))
unlist(c(a,b))[idx]

# [1] "a.1" "b.1" "a.2" "b.2" "a.3" "b.3" "b.4"

As @James points out, since you need a list back, you should do:

(c(a,b))[idx]
Arun
  • 116,683
  • 26
  • 284
  • 387
  • I think with the edit, the only requirement now is that the shorter list must be a and the longer list must be b, but that's easy enough the check. – joran May 08 '13 at 15:05
  • I think I was just interpreting the OP's statements more strictly. "remaining elements of the longer list are append at the end" only really makes sense to me if you start the interleaving with the first element of the shorter list, and so on. – joran May 08 '13 at 15:11
  • Should the output not be a list? – James May 08 '13 at 15:27
  • 3
    @Arun Foregoing the `unlist` and just using `c(a,b)[idx]` is probably better since in general the items complex objects that get destroyed by `unlist`. – James May 08 '13 at 15:42
33

While investigating a similar question, I came across this beautiful solution by Gabor Grothendieck (i.e. @GGrothendieck?) for certain cases:

c(rbind(a,b))

This works equally well when a and b are both lists, or when a and b are both vectors. It's not a precise solution to OP's question, because when a and b have different lengths, it will recycle the elements of the shorter sequence, printing a warning. However, since this solution is simple and elegant, and provides an answer to a very similar question--a question of some people (like me) who find their way to this page as a result--it seemed worth adding as an answer.

Community
  • 1
  • 1
Mars
  • 8,689
  • 2
  • 42
  • 70
  • I was looking into forming XML out of data.frame as plain string without much dependencies. This is the most concise approach for opening/closing tags while processing a row (with apply)! I wrapped rbind (without c) into paste & collapse="". – mlt Aug 16 '16 at 20:40
  • This doesn't seem to work with dates, by some reason. It gets turned out as a list of integers. – wpkzz May 03 '17 at 21:10
5

Here's one option using the interleave function from ggplot2. I'm sure this can be improved upon, but it's a start:

require(ggplot2)
Interleave <- function(x,y){
    v <- list(x,y)
    lengths <- sapply(v,length)
    mn <- min(lengths)
    v <- v[order(lengths)]
    c(ggplot2:::interleave(v[[1]],v[[2]][seq_len(mn)]),v[[2]][(mn+1):length(v[[2]])])
}

Interleave(a,b)
Interleave(b,a)

In particular, this will do weird things if the lists are actually the same length. Perhaps someone will chime in with a better way to do the indexing for v[[2]] in the last line that avoids that degenerate case.

IRTFM
  • 258,963
  • 21
  • 364
  • 487
joran
  • 169,992
  • 32
  • 429
  • 468
5
interleave(a, b)

# unlist(interleave(a, b))
# [1] "a.1" "b.1" "a.2" "b.2" "a.3" "b.3" "b.4"


interleave <- function(a, b) { 

  shorter <- if (length(a) < length(b)) a else b
  longer  <- if (length(a) >= length(b)) a else b

  slen <- length(shorter)
  llen <- length(longer)


  index.short <- (1:slen) + llen
  names(index.short) <- (1:slen)

  lindex <- (1:llen) + slen
  names(lindex) <- 1:llen


  sindex <- 1:slen
  names(sindex) <- 1:slen

  index <- c(sindex, lindex)
  index <- index[order(names(index))]

  return(c(a, b)[index])

}
Ricardo Saporta
  • 54,400
  • 17
  • 144
  • 178
2
a <- list('a.1','a.2', 'a.3')
b <- list('b.1','b.2', 'b.3', 'b.4')

interleave <- function(a, b) {
  mlab <- min(length(a), length(b)) 
  seqmlab <- seq_len(mlab) 
  c(rbind(a[seqmlab], b[seqmlab]), a[-seqmlab], b[-seqmlab]) 
}

interleave(a, b) 

From http://r.789695.n4.nabble.com/Interleaving-elements-of-two-vectors-tp795123p1691409.html

This is a little bit faster than @Arun:

enter image description here

caiohamamura
  • 2,260
  • 21
  • 23