How to create all combinations from a nested list while preserving the structure using R?

前端 未结 4 1928
盖世英雄少女心
盖世英雄少女心 2021-01-04 07:10

Given a nested list, how to create all possible lists from its elements, while preserving the structure of the nested list?

Nested list:



        
4条回答
  •  天涯浪人
    2021-01-04 07:30

    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
    

提交回复
热议问题