我有两个列表,其元素的名称部分重叠,我需要将其逐个元素合并/组合成一个列表:
> lst1 <- list(integers=c(1:7), letters=letters[1:5],
words=c("two", "strings"))
> lst2 <- list(letters=letters[1:10], booleans=c(TRUE, TRUE, FALSE, TRUE),
words=c("another", "two"), floats=c(1.2, 2.4, 3.8, 5.6))
> lst1
$integers
[1] 1 2 3 4 5 6 7
$letters
[1] "a" "b" "c" "d" "e"
$words
[1] "two" "strings"
> lst2
$letters
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
$booleans
[1] TRUE TRUE FALSE TRUE
$words
[1] "another" "two"
$floats
[1] 1.2 2.4 3.8 5.6
我尝试使用mapply,它基本上按索引组合两个列表(即:“[[”),而我需要按名称组合它们(即:“$”)。此外,由于列表的长度不同,因此应用回收规则(结果相当不可预测)。
> mapply(c, lst1, lst2)
$integers
[1] "1" "2" "3" "4" "5" "6" "7" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
$letters
[1] "a" "b" "c" "d" "e" "TRUE" "TRUE" "FALSE" "TRUE"
$words
[1] "two" "strings" "another" "two"
$<NA>
[1] 1.0 2.0 3.0 4.0 5.0 6.0 7.0 1.2 2.4 3.8 5.6
Warning message:
In mapply(c, lst1, lst2) :
longer argument not a multiple of length of shorter
正如你可能想象的那样,我正在寻找的是:
$integers
[1] 1 2 3 4 5 6 7
$letters
[1] "a" "b" "c" "d" "e" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
$words
[1] "two" "strings" "another" "two"
$booleans
[1] TRUE TRUE FALSE TRUE
$floats
[1] 1.2 2.4 3.8 5.6
有什么办法可以实现这一点吗? 谢谢!
你可以这样做:
keys <- unique(c(names(lst1), names(lst2)))
setNames(mapply(c, lst1[keys], lst2[keys]), keys)
泛化到任意数量的列表需要混合使用
do.call
和 lapply
:
l <- list(lst1, lst2, lst1)
keys <- unique(unlist(lapply(l, names)))
setNames(do.call(mapply, c(FUN=c, lapply(l, `[`, keys))), keys)
针对tidyverse
用户的
flodel答案更新:
list1 <- list(integers=c(1:7), letters=letters[1:5],
words=c("two", "strings"))
list2 <- list(letters=letters[1:10], booleans=c(TRUE, TRUE, FALSE, TRUE),
words=c("another", "two"), floats=c(1.2, 2.4, 3.8, 5.6))
input_list <- list(list1, list2, list1, list2)
我们希望为输出列表中的每个元素复制原始所需的输出两次。使用
map2
和 reduce
,我们可以比涉及 R
、do.call
和 mapply
的基本 lapply
解决方案更清晰地实现这一点。首先,我们使用 c()
声明一个通过命名元素组合两个列表的函数,然后通过 reduce
: 在输入列表上调用我们的函数
library(purrr)
cat_lists <- function(list1, list2) {
keys <- unique(c(names(list1), names(list2)))
map2(list1[keys], list2[keys], c) %>%
set_names(keys)
}
combined_output <- reduce(input_list, cat_lists)
这给了我们我们想要的:
> combined_output
#> $integers
#> [1] 1 2 3 4 5 6 7 1 2 3 4 5 6 7
#>
#> $letters
#> [1] "a" "b" "c" "d" "e" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "a" "b"
#> [18] "c" "d" "e" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
#>
#> $words
#> [1] "two" "strings" "another" "two" "two" "strings" "another"
#> [8] "two"
#>
#> $booleans
#> [1] TRUE TRUE FALSE TRUE TRUE TRUE FALSE TRUE
#>
#> $floats
#> [1] 1.2 2.4 3.8 5.6 1.2 2.4 3.8 5.6
我将添加我自己的基于
tapply
函数的解决方案。
lst1 <- list(integers=c(1:7), letters=letters[1:5],
words=c("two", "strings"))
lst2 <- list(letters=letters[1:10], booleans=c(TRUE, TRUE, FALSE, TRUE),
words=c("another", "two"), floats=c(1.2, 2.4, 3.8, 5.6))
binded <- c(lst1, lst2) # and for list of lists Reduce("c", list(lst1, lst2))
tapply(binded, names(binded), function(x) unlist(x, FALSE, FALSE)) # double false for better performance
我也用
grep
,不知道它是更好、最差还是相当!
l_tmp <- c(lst1, lst2, lst1)
keys = unique(names(l_tmp))
l = sapply(keys, function(name) {unlist(l_tmp[grep(name, names(l_tmp))])})
我认为这些答案都不能解决存在子列表的情况。为此,我们可以使用递归函数。
如果所有子列表的同级都被命名,那就很简单了:
merge_lists <- \(x, y) {
if (is.null(names(x)) | is.null(names(y))) return(unique(c(x, y)))
keys <- union(names(x), names(y))
sapply(keys, \(k) merge_lists(x[[k]], y[[k]]), simplify = F)
}
但是,如果子列表有未命名的同级,则该同级将被删除。
merge_lists <- \(x, y) {
if (is.null(names(x)) | is.null(names(y))) return(unique(c(x, y)))
nameless <- c(x[names(x) == ""], y[names(y) == ""])
nameless <- nameless[!(nameless %in% c(names(x), names(y)))]
unique_nodes_x <- x[setdiff(names(x), names(y))]
unique_nodes_y <- y[setdiff(names(y), names(x))]
common_keys <- intersect(names(x), names(y)) %>% .[. != ""]
common_nodes <- if (length(common_keys) == 0) NULL else {
sapply(common_keys, \(k) merge_lists(x[[k]], y[[k]]), simplify = F)
}
merged <- unlist(list(common_nodes, unique_nodes_x, unique_nodes_y, nameless), recursive = F)
browser()
return(merged)
}
为了演示,我向
LIST
和 lst1$words
添加了一个元素 lst2$words
:
lst1 <- list(
integers = c(1:7),
letters = letters[1:5],
words = list(
"two",
"strings",
"LIST" = list("a", "b")))
lst2 <- list(
letters = letters[1:10],
booleans = c(TRUE, TRUE, FALSE, TRUE),
words = list(
"another",
"two",
"LIST" = list("a", "c")),
floats=c(1.2, 2.4, 3.8, 5.6))
> merge_lists(lst1, lst2) %>% str()
# List of 5
# $ letters : chr [1:10] "a" "b" "c" "d" ...
# $ words :List of 5
# ..$ LIST:List of 3
# .. ..$ : chr "a"
# .. ..$ : chr "b"
# .. ..$ : chr "c"
# ..$ : chr "two"
# ..$ : chr "strings"
# ..$ : chr "another"
# ..$ : chr "two"
# $ integers: int [1:7] 1 2 3 4 5 6 7
# $ booleans: logi [1:4] TRUE TRUE FALSE TRUE
# $ floats : num [1:4] 1.2 2.4 3.8 5.6