7

Given a list of two lists, I am trying to obtain, without using for loops, a list of all element-wise products of the first list with the second. For example:

> a <- list(c(1,2), c(2,3), c(4,5))
> b <- list(c(1,3), c(3,4), c(6,2))
> c <- list(a, b)

The function should return a list with 9 entries, each of size two. For example,

> answer
[[1]]
[1] 1 6

[[2]]
[1] 3 8

[[3]]
[1] 6 4

[[4]]
[1] 2 9

[[5]]
[1] 6 12

etc...

Any suggestions would be much appreciated!

SAT
  • 169
  • 1
  • 2
  • 8
  • 2
    Welcome to SO! If a particular answer happens to solve your problem, it is very useful to the site as a whole, and to future readers, if you would click the little check mark next to it, marking it as the accepted answer. You are never under any obligation to do so, but if you _do_ get an answer that solves your problem, doing so would be much appreciated by the SO community. – joran May 11 '12 at 14:22
  • Hi, sorry for the late reply and of course I will give praise where it is due. Very nice answers indeed! – SAT May 13 '12 at 05:58

4 Answers4

10

A fast (but memory-intensive) way would be to use the mechanism of mapply in combination with argument recycling, something like this:

mapply(`*`,a,rep(b,each=length(a)))

Gives :

> mapply(`*`,a,rep(b,each=length(a)))
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
[1,]    1    2    4    3    6   12    6   12   24
[2,]    6    9   15    8   12   20    4    6   10

Or replace a with c[[1]] and b with c[[2]] to obtain the same. To get a list, set the argument SIMPLIFY = FALSE.

Joris Meys
  • 106,551
  • 31
  • 221
  • 263
  • 1
    I benched them both and it's not even close, your method is faster and takes one line of code. +1 (PS did you notice I spelled your name correctly) – Tyler Rinker May 11 '12 at 14:55
6

Have no idea if this is fast or memory intensive just that it works, Joris Meys's answer is more eloquent:

x <- expand.grid(1:length(a), 1:length(b))
x <- x[order(x$Var1), ]    #gives the order you asked for
FUN <- function(i)  diag(outer(a[[x[i, 1]]], b[[x[i, 2]]], "*"))
sapply(1:nrow(x), FUN)     #I like this out put
lapply(1:nrow(x), FUN)     #This one matches what you asked for

EDIT: Now that Brian introduced benchmarking (which I love (LINK)) I have to respond. I actually have a faster answer using what I call expand.grid2 that's a lighter weight version of the original that I stole from HERE. I was going to throw it up before but when I saw how fast Joris's is I figured why bother, both short and sweet but also fast. But now that Diggs has dug I figured I'd throw up here the expand.grid2 for educational purposes.

expand.grid2 <-function(seq1,seq2) {
    cbind(Var1 = rep.int(seq1, length(seq2)), 
    Var2 = rep.int(seq2, rep.int(length(seq1),length(seq2))))
}

x <- expand.grid2(1:length(a), 1:length(b))
x <- x[order(x[,'Var1']), ]    #gives the order you asked for
FUN <- function(i)  diag(outer(a[[x[i, 1]]], b[[x[i, 2]]], "*"))
lapply(1:nrow(x), FUN)

Here's the results (same labeling as Bryan's except TylerEG2 is using the expand.grid2):

Unit: microseconds
            expr      min       lq   median       uq      max
1   DiggsL(a, b) 5102.296 5307.816 5471.578 5887.516 70965.58
2   DiggsM(a, b)  384.912  428.769  443.466  461.428 36213.89
3    Joris(a, b)   91.446  105.210  123.172  130.171 16833.47
4 TylerEG2(a, b)  392.377  425.503  438.100  453.263 32208.94
5   TylerL(a, b) 1752.398 1808.852 1847.577 1975.880 49214.10
6   TylerM(a, b) 1827.515 1888.867 1925.959 2090.421 75766.01
7 Wojciech(a, b) 1719.740 1771.760 1807.686 1924.325 81666.12

And if I take the ordering step out I can squeak out even more but it still isn't close to Joris's answer.

enter image description here

Community
  • 1
  • 1
Tyler Rinker
  • 108,132
  • 65
  • 322
  • 519
2

Pulling ideas from the other answers together, I'll throw another one-liner in for fun:

do.call(mapply, c(FUN=`*`, as.list(expand.grid(b, a))))

which gives

     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
[1,]    1    3    6    2    6   12    4   12   24
[2,]    6    8    4    9   12    6   15   20   10

If you really need it in the format you gave, then you can use the plyr library to transform it into that:

library("plyr")
as.list(unname(alply(do.call(mapply, c(FUN=`*`, as.list(expand.grid(b, a)))), 2)))

which gives

[[1]]
[1] 1 6

[[2]]
[1] 3 8

[[3]]
[1] 6 4

[[4]]
[1] 2 9

[[5]]
[1]  6 12

[[6]]
[1] 12  6

[[7]]
[1]  4 15

[[8]]
[1] 12 20

[[9]]
[1] 24 10

Just for fun, benchmarking:

Joris <- function(a, b) {
    mapply(`*`,a,rep(b,each=length(a)))
}

TylerM <- function(a, b) {
    x <- expand.grid(1:length(a), 1:length(b))
    x <- x[order(x$Var1), ]    #gives the order you asked for
    FUN <- function(i)  diag(outer(a[[x[i, 1]]], b[[x[i, 2]]], "*"))
    sapply(1:nrow(x), FUN)
}

TylerL <- function(a, b) {
    x <- expand.grid(1:length(a), 1:length(b))
    x <- x[order(x$Var1), ]    #gives the order you asked for
    FUN <- function(i)  diag(outer(a[[x[i, 1]]], b[[x[i, 2]]], "*"))
    lapply(1:nrow(x), FUN)
}

Wojciech <- function(a, b) {
    # Matrix with indicies for elements to multiply
    G <- expand.grid(1:3,1:3)

    # Coversion of G to list
    L <- lapply(1:nrow(G),function(x,d=G) d[x,])

    lapply(L,function(i,x=a,y=b) x[[i[[2]]]]*y[[i[[1]]]])
}

DiggsM <- function(a, b) {
    do.call(mapply, c(FUN=`*`, as.list(expand.grid(b, a))))
}

DiggsL <- function(a, b) {
    as.list(unname(alply(t(do.call(mapply, c(FUN=`*`, as.list(expand.grid(b, a))))), 1)))
}

and the benchmarks

> library("rbenchmark")
> benchmark(Joris(b,a),
+           TylerM(a,b),
+           TylerL(a,b),
+           Wojciech(a,b),
+           DiggsM(a,b),
+           DiggsL(a,b),
+           order = "relative", 
+           replications = 1000,
+           columns = c("test", "elapsed", "relative"))
            test elapsed relative
1    Joris(b, a)    0.08    1.000
5   DiggsM(a, b)    0.26    3.250
4 Wojciech(a, b)    1.34   16.750
3   TylerL(a, b)    1.36   17.000
2   TylerM(a, b)    1.40   17.500
6   DiggsL(a, b)    3.49   43.625

and to show they are equivalent:

> identical(Joris(b,a), TylerM(a,b))
[1] TRUE
> identical(Joris(b,a), DiggsM(a,b))
[1] TRUE
> identical(TylerL(a,b), Wojciech(a,b))
[1] TRUE
> identical(TylerL(a,b), DiggsL(a,b))
[1] TRUE
Brian Diggs
  • 57,757
  • 13
  • 166
  • 188
1
# Your data
a <- list(c(1,2), c(2,3), c(4,5))
b <- list(c(1,3), c(3,4), c(6,2))

# Matrix with indicies for elements to multiply
G <- expand.grid(1:3,1:3)

# Coversion of G to list
L <- lapply(1:nrow(G),function(x,d=G) d[x,])

lapply(L,function(i,x=a,y=b) x[[i[[2]]]]*y[[i[[1]]]])
Wojciech Sobala
  • 7,431
  • 2
  • 21
  • 27