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:
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.
# 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]]]])
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
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
.