[英]Map Raggedly to Depth but Stop at Sentinel
我正在开发一个 R package 和一个基本的助手 function “ make()
”。 这个make()
function 接受一个参差不齐的列表,然后将 function foo()
映射到列表中的倒数第二个节点(“树枝”)上:那些孩子是叶子的节点。 到目前为止purrr::map_depth
(..., .depth = -2, .ragged = TRUE)
似乎很理想。
有一个问题:遍历必须在一个done_box
节点处停止 - 或者我自己创建的一些哨兵 class -无论其深度如何,即使该节点也是(即继承自)任意深度的list
。 然后 function foo()
将根据其 class 有条件地对节点进行 map。
不幸的是, purrr::vec_depth()
的不灵活性在遇到目标节点时会引发错误:
Error in `.f()`:
! `x` must be a vector
Run `rlang::last_error()` to see where the error occurred.
因此,我暂时在.map_depth()
的源代码上编写了一个变体 .map_depth map_depth()
,其中.list_depth()
替换vec_depth()
来处理目标节点。 有关代码,请参阅代码部分。
然后,我可以创建一个哨兵quosure
my_sentinel
,它为生成节点值的expression
“装箱”(即包装在一个list
)。 由于此my_sentinel
继承自list
,因此.list_depth()
将为其quosure
提供1
的深度,并为哨兵本身提供2
的深度。
在.depth
为-2
时, .map_depth()
function 将因此以my_sentinel
节点本身为目标。 按照设计, foo()
会将其quosure
标识为"my_sentinel"
,并将评估“取消装箱”该值的条件。
不幸的是,自从首次捕获quosure
quosure
environment
可能会发生变化。 因此,当最终调用.map_depth()
时,它可能会“取消装箱”错误的值:错误更新的值。
因此,最终用户的行为可能会变得不稳定!
是否有更清洁或规范的方法来purrr::map_*()
将 function foo()
到list
的“树枝”(即.depth = -2
的节点),同时停在像done_box
这样的哨兵对象的节点?
我很好奇purrr::map_if()
,有一些谓词 .p 可以测试.p
和深度。 但是,我缺乏确认这是否可行的专业知识,更不用说canonical了。
对于my_sentinel
:
my_sentinel <- function(x) {
x_quo <- rlang::enquo0(x)
# No pun intended.
x_box <- list(x_quo)
structure(x_box,
class = c("my_sentinel", class(x_box))
)
}
对于.map_depth()
:
# A variation on 'purrr::map_depth()' that accommodates objective leaves.
.map_depth <- function(.x, .depth, .f, ..., .ragged = FALSE) {
if (!rlang::is_integerish(.depth, n = 1, finite = TRUE)) {
abort("`.depth` must be a single number")
}
if (.depth < 0) {
.depth <- .list_depth(.x) + .depth
}
.f <- purrr::as_mapper(.f, ...)
.map_depth_rec(.x, .depth, .f, ..., .ragged = .ragged, .atomic = FALSE)
}
# A variation on 'purrr:::map_depth_rec()' that accommodates objective leaves.
.map_depth_rec <- function(.x, .depth, .f, ..., .ragged, .atomic) {
if (.depth < 0) {
rlang::abort("Invalid depth")
}
# TODO: Must this be addressed too (unlikely)?
if (.atomic) {
if (!.ragged) {
rlang::abort("List not deep enough")
}
return(purrr::map(.x, .f, ...))
}
if (.depth == 0) {
return(.f(.x, ...))
}
if (.depth == 1) {
return(purrr::map(.x, .f, ...))
}
.atomic <- rlang::is_atomic(.x)
purrr::map(.x, function(x) {
.map_depth_rec(x, .depth - 1, .f, ..., .ragged = .ragged,
.atomic = .atomic)
})
}
# A variation on 'purrr::vec_depth()' that accommodates objective leaves.
.list_depth <- function(x) {
if (rlang::is_null(x)) {
0L
}
# TODO: Address this so a vector is treated as a leaf (or must users esc() for that?).
else if (rlang::is_atomic(x)) {
1L
}
else if (rlang::is_list(x)) {
depths <- purrr::map_int(x, .list_depth)
1L + max(depths, 0L)
}
# Accommodate objective leaves.
else if (is.object(x)) {
# TODO: Check if this should be 1L or (as I suspect) 0L!
1L
}
else {
rlang::abort("`x` must be a vector or an object")
}
}
只需在 package rrapply
(基本rapply
的扩展版本rrapply()
中提到 rrapply(),它可能已经提供了您正在寻找的功能。
使用以下虚拟列表,因为问题中没有提供数据:
## dummy data
l <- list(
list("initial_node"),
list(list("initial_node")),
structure(list("initial_node"), class = c("list", "my_sentinal")),
structure(list(list("initial_node")), class = c("list", "my_sentinal"))
)
在第一次调用中, my_sentinal
class 递归地传播到每个子列表。 在第二次调用中,一些 function f
应用于所有没有继承自my_sentinal
class 的子列表的列表。 注意:这可能可以通过一些努力组合成一个调用,但分成两个单独的调用,代码可能更容易理解和理解。
## propagate my_sentinal class to deeper sublists
l1 <- rrapply(
l,
classes ="list",
condition = \(x) inherits(x, "my_sentinal"),
f = \(x) {
if(is.list(x[[1]])) x[] <- lapply(x, structure, class = c("list", "my_sentinal"))
return(x)
},
how = "recurse"
)
## apply function to non-sentinal pen-ultimate nodes
l2 <- rrapply(
l1,
classes = "list",
condition = \(x) !inherits(x, "my_sentinal") && !is.list(x[[1]]),
f = \(x) lapply(x, \(xi) "processed_node")
)
str(l2)
#> List of 4
#> $ :List of 1
#> ..$ : chr "processed_node"
#> $ :List of 1
#> ..$ :List of 1
#> .. ..$ : chr "processed_node"
#> $ :List of 1
#> ..$ : chr "initial_node"
#> ..- attr(*, "class")= chr [1:2] "list" "my_sentinal"
#> $ :List of 1
#> ..$ :List of 1
#> .. ..$ : chr "initial_node"
#> .. ..- attr(*, "class")= chr [1:2] "list" "my_sentinal"
#> ..- attr(*, "class")= chr [1:2] "list" "my_sentinal"
免责声明:我也是rrapply
的维护者。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.