R:在深层嵌套列表中按名称查找 object
R: Find object by name in deeply nested list
问题
我认为这应该是一个常见问题,但我找不到解决方案:
让我们假设一个深度嵌套的列表,例如:
my_list <- list(
"first_node" = list(
"group_a" = list(
"E001" = 1:5,
"E002" = list(
"F001" = 6:10,
"F002" = 11:15
)
),
"group_b" = list(
"XY01" = list(
"Z1" = LETTERS[1:5],
"Z2" = LETTERS[6:10],
"Z3" = list(
"ZZ1" = LETTERS[1],
"ZZ2" = LETTERS[2],
"ZZ3" = LETTERS[3]
)
),
"YZ" = LETTERS[11:15]
),
"group_c" = list(
"QQQQ" = list(
"RRRR" = 200:300
)
)
),
"second_node" = list(
"group_d" = list(
"L1" = 99:101,
"L2" = 12
)
)
)
期望的输出
我想按名称检索元素,这些元素可能位于该列表中未知的深度级别。重要的是,我只想要那个特定元素,它是 children,而不是 parents.
例如,在 my_list
中搜索 "XY01"
应该得到:
XY01 = list(
"Z1" = LETTERS[1:5],
"Z2" = LETTERS[6:10],
"Z3" = list(
"ZZ1" = LETTERS[1],
"ZZ2" = LETTERS[2],
"ZZ3" = LETTERS[3]
)
)
> str(XY01)
List of 3
$ Z1: chr [1:5] "A" "B" "C" "D" ...
$ Z2: chr [1:5] "F" "G" "H" "I" ...
$ Z3:List of 3
..$ ZZ1: chr "A"
..$ ZZ2: chr "B"
..$ ZZ3: chr "C"
之前的尝试
最初我想使用 rapply()
来完成这项工作,但似乎我无法为当前迭代访问 names()
。我的第二次尝试是编写自定义递归函数:
recursive_extract <- function(haystack, needle){
lapply(names(haystack), function(x){
if (needle %in% names(haystack[[x]])) {
return(haystack[[needle]])
} else {
recursive_extract(haystack[[x]], needle)
}
}) %>% setNames(names(haystack))
}
...这似乎也有问题,因为 lapply()
将始终返回相同的 object,即使 NULL
是 returned,所以父结构跟进
我一直在研究 purrr
和 rlist
包的一个方便的功能,但似乎大多数都不支持递归(?)。
奖金挑战
提取所需元素后,理想情况下我想选择 child-levels 到 return 的数量。例如:
desired_func(haystack, needle, get_depth = 1)
对于前面的示例将导致:
XY01 = list(
"Z1" = LETTERS[1:5],
"Z2" = LETTERS[6:10]
)
> str(XY01)
List of 2
$ Z1: chr [1:5] "A" "B" "C" "D" ...
$ Z2: chr [1:5] "F" "G" "H" "I" ...
非常感谢帮助! :)
这是一个函数,如果找到 return 第一个匹配项
find_name <- function(haystack, needle) {
if (hasName(haystack, needle)) {
haystack[[needle]]
} else if (is.list(haystack)) {
for (obj in haystack) {
ret <- Recall(obj, needle)
if (!is.null(ret)) return(ret)
}
} else {
NULL
}
}
find_name(my_list, "XY01")
我们避免使用 lapply
,这样循环可以在发现时提前中断。
列表修剪确实是一个单独的问题。最好用不同的功能来攻击它。这应该有效
list_prune <- function(list, depth=1) {
if (!is.list(list)) return(list)
if (depth>1) {
lapply(list, list_prune, depth = depth-1)
} else {
Filter(function(x) !is.list(x), list)
}
}
那你可以
list_prune(find_name(my_list, "XY01"), 1)
或用管道
find_name(my_list, "XY01") %>% list_prune(1)
我们也可以在rrapply
-package(base-rapply
的扩展)中使用rrapply
。
首先,在嵌套列表中查找XY01
的位置:
library(rrapply)
(XY01_pos <- rrapply(my_list,
classes = "list",
condition = function(x, .xname) .xname == "XY01",
f = function(x, .xpos) .xpos,
how = "flatten")[[1]])
#> [1] 1 2 1
在这里,我们使用 .xname
和 .xpos
参数,它们计算出被评估的列表元素的名称和位置。 how = "flatten"
return 是修剪列表的扁平化版本,在本例中仅包含 XY01
.
的位置
其次,return嵌套列表的普通子集子列表:
str(my_list[[XY01_pos]])
#> List of 3
#> $ Z1: chr [1:5] "A" "B" "C" "D" ...
#> $ Z2: chr [1:5] "F" "G" "H" "I" ...
#> $ Z3:List of 3
#> ..$ ZZ1: chr "A"
#> ..$ ZZ2: chr "B"
#> ..$ ZZ3: chr "C"
对于奖金挑战,我们可以再次调用 rrapply
应用于子列表 my_list[[XY01]]
return 修剪后的列表仅包含深度小于或等于 a 的节点pre-specified 最大深度:
maxdepth <- 1
rrapply(my_list[[XY01_pos]], condition = function(x, .xpos) length(.xpos) <= maxdepth, how = "prune")
#> $Z1
#> [1] "A" "B" "C" "D" "E"
#>
#> $Z2
#> [1] "F" "G" "H" "I" "J"
这里length(.xpos)
求值到被求值列表元素的深度,所以我们return只有满足length(.xpos) <= maxdepth
.
的节点
注意:我们也可以直接 return XY01
的 children 通过设置 how = "prune"
:
str(rrapply(my_list, classes = "list", condition = function(x, .xname) .xname == "XY01", how = "prune"))
#> List of 1
#> $ first_node:List of 1
#> ..$ group_b:List of 1
#> .. ..$ XY01:List of 3
#> .. .. ..$ Z1: chr [1:5] "A" "B" "C" "D" ...
#> .. .. ..$ Z2: chr [1:5] "F" "G" "H" "I" ...
#> .. .. ..$ Z3:List of 3
#> .. .. .. ..$ ZZ1: chr "A"
#> .. .. .. ..$ ZZ2: chr "B"
#> .. .. .. ..$ ZZ3: chr "C"
但这将包含从根开始而不是从 XY01
节点开始的完整节点路径。
问题
我认为这应该是一个常见问题,但我找不到解决方案:
让我们假设一个深度嵌套的列表,例如:
my_list <- list(
"first_node" = list(
"group_a" = list(
"E001" = 1:5,
"E002" = list(
"F001" = 6:10,
"F002" = 11:15
)
),
"group_b" = list(
"XY01" = list(
"Z1" = LETTERS[1:5],
"Z2" = LETTERS[6:10],
"Z3" = list(
"ZZ1" = LETTERS[1],
"ZZ2" = LETTERS[2],
"ZZ3" = LETTERS[3]
)
),
"YZ" = LETTERS[11:15]
),
"group_c" = list(
"QQQQ" = list(
"RRRR" = 200:300
)
)
),
"second_node" = list(
"group_d" = list(
"L1" = 99:101,
"L2" = 12
)
)
)
期望的输出
我想按名称检索元素,这些元素可能位于该列表中未知的深度级别。重要的是,我只想要那个特定元素,它是 children,而不是 parents.
例如,在 my_list
中搜索 "XY01"
应该得到:
XY01 = list(
"Z1" = LETTERS[1:5],
"Z2" = LETTERS[6:10],
"Z3" = list(
"ZZ1" = LETTERS[1],
"ZZ2" = LETTERS[2],
"ZZ3" = LETTERS[3]
)
)
> str(XY01)
List of 3
$ Z1: chr [1:5] "A" "B" "C" "D" ...
$ Z2: chr [1:5] "F" "G" "H" "I" ...
$ Z3:List of 3
..$ ZZ1: chr "A"
..$ ZZ2: chr "B"
..$ ZZ3: chr "C"
之前的尝试
最初我想使用 rapply()
来完成这项工作,但似乎我无法为当前迭代访问 names()
。我的第二次尝试是编写自定义递归函数:
recursive_extract <- function(haystack, needle){
lapply(names(haystack), function(x){
if (needle %in% names(haystack[[x]])) {
return(haystack[[needle]])
} else {
recursive_extract(haystack[[x]], needle)
}
}) %>% setNames(names(haystack))
}
...这似乎也有问题,因为 lapply()
将始终返回相同的 object,即使 NULL
是 returned,所以父结构跟进
我一直在研究 purrr
和 rlist
包的一个方便的功能,但似乎大多数都不支持递归(?)。
奖金挑战
提取所需元素后,理想情况下我想选择 child-levels 到 return 的数量。例如:
desired_func(haystack, needle, get_depth = 1)
对于前面的示例将导致:
XY01 = list(
"Z1" = LETTERS[1:5],
"Z2" = LETTERS[6:10]
)
> str(XY01)
List of 2
$ Z1: chr [1:5] "A" "B" "C" "D" ...
$ Z2: chr [1:5] "F" "G" "H" "I" ...
非常感谢帮助! :)
这是一个函数,如果找到 return 第一个匹配项
find_name <- function(haystack, needle) {
if (hasName(haystack, needle)) {
haystack[[needle]]
} else if (is.list(haystack)) {
for (obj in haystack) {
ret <- Recall(obj, needle)
if (!is.null(ret)) return(ret)
}
} else {
NULL
}
}
find_name(my_list, "XY01")
我们避免使用 lapply
,这样循环可以在发现时提前中断。
列表修剪确实是一个单独的问题。最好用不同的功能来攻击它。这应该有效
list_prune <- function(list, depth=1) {
if (!is.list(list)) return(list)
if (depth>1) {
lapply(list, list_prune, depth = depth-1)
} else {
Filter(function(x) !is.list(x), list)
}
}
那你可以
list_prune(find_name(my_list, "XY01"), 1)
或用管道
find_name(my_list, "XY01") %>% list_prune(1)
我们也可以在rrapply
-package(base-rapply
的扩展)中使用rrapply
。
首先,在嵌套列表中查找XY01
的位置:
library(rrapply)
(XY01_pos <- rrapply(my_list,
classes = "list",
condition = function(x, .xname) .xname == "XY01",
f = function(x, .xpos) .xpos,
how = "flatten")[[1]])
#> [1] 1 2 1
在这里,我们使用 .xname
和 .xpos
参数,它们计算出被评估的列表元素的名称和位置。 how = "flatten"
return 是修剪列表的扁平化版本,在本例中仅包含 XY01
.
其次,return嵌套列表的普通子集子列表:
str(my_list[[XY01_pos]])
#> List of 3
#> $ Z1: chr [1:5] "A" "B" "C" "D" ...
#> $ Z2: chr [1:5] "F" "G" "H" "I" ...
#> $ Z3:List of 3
#> ..$ ZZ1: chr "A"
#> ..$ ZZ2: chr "B"
#> ..$ ZZ3: chr "C"
对于奖金挑战,我们可以再次调用 rrapply
应用于子列表 my_list[[XY01]]
return 修剪后的列表仅包含深度小于或等于 a 的节点pre-specified 最大深度:
maxdepth <- 1
rrapply(my_list[[XY01_pos]], condition = function(x, .xpos) length(.xpos) <= maxdepth, how = "prune")
#> $Z1
#> [1] "A" "B" "C" "D" "E"
#>
#> $Z2
#> [1] "F" "G" "H" "I" "J"
这里length(.xpos)
求值到被求值列表元素的深度,所以我们return只有满足length(.xpos) <= maxdepth
.
注意:我们也可以直接 return XY01
的 children 通过设置 how = "prune"
:
str(rrapply(my_list, classes = "list", condition = function(x, .xname) .xname == "XY01", how = "prune"))
#> List of 1
#> $ first_node:List of 1
#> ..$ group_b:List of 1
#> .. ..$ XY01:List of 3
#> .. .. ..$ Z1: chr [1:5] "A" "B" "C" "D" ...
#> .. .. ..$ Z2: chr [1:5] "F" "G" "H" "I" ...
#> .. .. ..$ Z3:List of 3
#> .. .. .. ..$ ZZ1: chr "A"
#> .. .. .. ..$ ZZ2: chr "B"
#> .. .. .. ..$ ZZ3: chr "C"
但这将包含从根开始而不是从 XY01
节点开始的完整节点路径。