Putting together the great answers from Ben Nutzer and Joris Chau, we have a way to create all possible combinations from a nested list, regardless of whether some sublist components are of unequal length.
Put together as a function:
list.combine <- function(input) {
# Create list skeleton.
skeleton <- rapply(input, head, n = 1, how = "list")
# Create storage for the flattened list.
flattened = list()
# Flatten the list.
invisible(rapply(input, function(x) {
flattened <<- c(flattened, list(x))
}))
# Create all possible combinations from list elements.
combinations <- expand.grid(flattened, stringsAsFactors = FALSE)
# Create list for storing the output.
output <- apply(combinations, 1, relist, skeleton = skeleton)
return(output)
}
Note: If a character type exists in the sublist components, then everything will be coerced to a character. For example:
# Input list.
l <- list(
a = "string",
b = list(
c = 1:2,
d = 3
)
)
# Applying the function.
o <- list.combine(l)
# View the list:
str(o)
# List of 2
# $ :List of 2
# ..$ a: chr "string"
# ..$ b:List of 2
# .. ..$ c: chr "1"
# .. ..$ d: chr "3"
# $ :List of 2
# ..$ a: chr "string"
# ..$ b:List of 2
# .. ..$ c: chr "2"
# .. ..$ d: chr "3"
One--slow--way around this is to relist
within a loop which will maintain the data in a 1x1
dataframe. Accessing the dataframe as df[, 1]
will give a vector of length 1 of the original type as the element in the input list. For example:
Updated list.combine()
:
list.combine <- function(input) {
# Create list skeleton.
skeleton <- rapply(input, head, n = 1, how = "list")
# Create storage for the flattened list.
flattened = list()
# Flatten the list.
invisible(rapply(input, function(x) {
flattened <<- c(flattened, list(x))
}))
# Create all possible combinations from list elements.
combinations <- expand.grid(flattened, stringsAsFactors = FALSE)
# Create list for storing the output.
output <- list()
# Relist and preserve original data type.
for (i in 1:nrow(combinations)) {
output[[i]] <- retain.element.type(relist(flesh = combinations[i, ], skeleton = skeleton))
}
return(output)
}
Then the retain.element.type()
:
retain.element.type <- function(input.list) {
for (name in names(input.list)) {
# If the element is a list, recall the function.
if(inherits(input.list[[name]], "list")) {
input.list[[name]] <- Recall(input.list[[name]])
# Else, get the first element and preserve the type.
} else {
input.list[[name]] <- input.list[[name]][, 1]
}
}
return(input.list)
}
Example:
# Input list.
l <- list(
a = "string",
b = list(
c = 1:2,
d = 3
)
)
# Applying the updated function to preserve the data type.
o <- list.combine(l)
# View the list:
str(o)
# List of 2
# $ :List of 2
# ..$ a: chr "string"
# ..$ b:List of 2
# .. ..$ c: int 1
# .. ..$ d: num 3
# $ :List of 2
# ..$ a: chr "string"
# ..$ b:List of 2
# .. ..$ c: int 2
# .. ..$ d: num 3