[英]Map Raggedly to Depth but Stop at Sentinel
I am developing an R package with an essential helper function " make()
".我正在开发一个 R package 和一个基本的助手 function “
make()
”。 This make()
function accepts a ragged list, then maps function foo()
onto the penultimate nodes ("twigs") in the list: those whose children are leaves.这个
make()
function 接受一个参差不齐的列表,然后将 function foo()
映射到列表中的倒数第二个节点(“树枝”)上:那些孩子是叶子的节点。 So far purrr::map_depth
(..., .depth = -2, .ragged = TRUE)
seems ideal.到目前为止
purrr::map_depth
(..., .depth = -2, .ragged = TRUE)
似乎很理想。
There is one catch: the traversal must stop at a node that is a done_box
— or some sentinel class of my own creation — regardless of its depth , even though that node is also (ie. inherits from) a list
of arbitrary depth.有一个问题:遍历必须在一个
done_box
节点处停止 - 或者我自己创建的一些哨兵 class -无论其深度如何,即使该节点也是(即继承自)任意深度的list
。 The function foo()
will then map the node conditionally, based on its class.然后 function
foo()
将根据其 class 有条件地对节点进行 map。
Unfortunately, the inflexibility of purrr::vec_depth()
induces an error when it encounters objective nodes:不幸的是,
purrr::vec_depth()
的不灵活性在遇到目标节点时会引发错误:
Error in `.f()`:
! `x` must be a vector
Run `rlang::last_error()` to see where the error occurred.
As such, I have tentatively written a variation .map_depth()
on the source code for map_depth()
, where .list_depth()
replaces vec_depth()
to handle objective nodes.因此,我暂时在
.map_depth()
的源代码上编写了一个变体 .map_depth map_depth()
,其中.list_depth()
替换vec_depth()
来处理目标节点。 See the Code section for the code.有关代码,请参阅代码部分。
I can then create a sentinel class my_sentinel
, which "boxes" (ie. wraps in a list
) a quosure
for the expression
that generates the node's value.然后,我可以创建一个哨兵
quosure
my_sentinel
,它为生成节点值的expression
“装箱”(即包装在一个list
)。 Since this my_sentinel
inherits from list
, then .list_depth()
will give its quosure
a depth of 1
and give the sentinel itself a depth of 2
.由于此
my_sentinel
继承自list
,因此.list_depth()
将为其quosure
提供1
的深度,并为哨兵本身提供2
的深度。
At a .depth
of -2
, the .map_depth()
function will thus target the my_sentinel
node itself.在
.depth
为-2
时, .map_depth()
function 将因此以my_sentinel
节点本身为目标。 By design, foo()
will identify its class as "my_sentinel"
, and it will evaluate the quosure
to "unbox" the value.按照设计,
foo()
会将其quosure
标识为"my_sentinel"
,并将评估“取消装箱”该值的条件。
Unfortunately, the environment
of the quosure
may change since the quosure
was first captured.不幸的是,自从首次捕获
quosure
quosure
environment
可能会发生变化。 So when .map_depth()
is finally called, it might "unbox" the wrong value: one that was wrongly updated.因此,当最终调用
.map_depth()
时,它可能会“取消装箱”错误的值:错误更新的值。
As such, the behavior could prove unstable for end users!因此,最终用户的行为可能会变得不稳定!
Is there a cleaner or canonical way to purrr::map_*()
a function foo()
to the "twigs" (ie. nodes at .depth = -2
) of a list
, while stopping at nodes that are sentinel objects like done_box
?是否有更清洁或规范的方法来
purrr::map_*()
将 function foo()
到list
的“树枝”(即.depth = -2
的节点),同时停在像done_box
这样的哨兵对象的节点?
I'm curious about purrr::map_if()
, with some predicate .p
that tests for both class and depth.我很好奇
purrr::map_if()
,有一些谓词 .p 可以测试.p
和深度。 However, I lack the expertise to confirm this as feasible, let alone canonical .但是,我缺乏确认这是否可行的专业知识,更不用说canonical了。
For my_sentinel
:对于
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))
)
}
For .map_depth()
:对于
.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")
}
}
Just mentioning rrapply()
in package rrapply
(an extended version of base rapply
), which may already provide the functionality you are looking for.只需在 package
rrapply
(基本rapply
的扩展版本rrapply()
中提到 rrapply(),它可能已经提供了您正在寻找的功能。
Using the following dummy list, since no data is provided in the question:使用以下虚拟列表,因为问题中没有提供数据:
## 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"))
)
In a first call, the my_sentinal
class is recursively propagated to each sublist.在第一次调用中,
my_sentinal
class 递归地传播到每个子列表。 In a second call, some function f
is applied to all lists with no sublists that do not inherit from the my_sentinal
class.在第二次调用中,一些 function
f
应用于所有没有继承自my_sentinal
class 的子列表的列表。 NB: this can probably be combined into a single call with some effort, but split into two separate calls the code is likely easier to follow and understand.注意:这可能可以通过一些努力组合成一个调用,但分成两个单独的调用,代码可能更容易理解和理解。
## 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"
Disclaimer: I am also the maintainer of the rrapply
-package.免责声明:我也是
rrapply
的维护者。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.