-
Notifications
You must be signed in to change notification settings - Fork 292
Expand file tree
/
Copy pathmodify-tree.R
More file actions
81 lines (78 loc) · 2.13 KB
/
modify-tree.R
File metadata and controls
81 lines (78 loc) · 2.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#' Recursively modify a list
#'
#' `modify_tree()` allows you to recursively modify a list, supplying functions
#' that either modify each leaf or each node (or both).
#'
#' @param x A list.
#' @param ... Reserved for future use. Must be empty
#' @param leaf A function applied to each leaf.
#' @param is_node A predicate function that determines whether an element is
#' a node (by returning `TRUE`) or a leaf (by returning `FALSE`). The
#' default value, `NULL`, treats simple lists as nodes and everything else
#' (including richer objects like data frames and linear models) as leaves,
#' using [vctrs::obj_is_list()]. To recurse into all objects built on lists
#' use [is.list()].
#' @param pre,post Functions applied to each node. `pre` is applied on the
#' way "down", i.e. before the leaves are transformed with `leaf`, while
#' `post` is applied on the way "up", i.e. after the leaves are transformed.
#' @family modify variants
#' @export
#' @examples
#' x <- list(list(a = 2:1, c = list(b1 = 2), b = list(c2 = 3, c1 = 4)))
#' x |> str()
#'
#' # Transform each leaf
#' x |> modify_tree(leaf = \(x) x + 100) |> str()
#'
#' # Recursively sort the nodes
#' sort_named <- function(x) {
#' nms <- names(x)
#' if (!is.null(nms)) {
#' x[order(nms)]
#' } else {
#' x
#' }
#' }
#' x |> modify_tree(post = sort_named) |> str()
modify_tree <- function(
x,
...,
leaf = identity,
is_node = NULL,
pre = identity,
post = identity
) {
check_dots_empty()
leaf <- rlang::as_function(leaf)
is_node <- as_is_node(is_node)
post <- rlang::as_function(post)
pre <- rlang::as_function(pre)
worker <- function(x) {
if (is_node(x)) {
out <- pre(x)
out <- modify(out, worker)
out <- post(out)
} else {
out <- leaf(x)
}
out
}
worker(x)
}
as_is_node <- function(
f,
error_call = caller_env(),
error_arg = caller_arg(f)
) {
if (is.null(f)) {
obj_is_list
} else {
is_node_f <- rlang::as_function(f, call = error_call, arg = error_arg)
as_predicate(
is_node_f,
.mapper = FALSE,
.purrr_error_call = error_call,
.purrr_error_arg = error_arg
)
}
}