-
Notifications
You must be signed in to change notification settings - Fork 292
Expand file tree
/
Copy pathlist-flatten.R
More file actions
90 lines (85 loc) · 2.74 KB
/
list-flatten.R
File metadata and controls
90 lines (85 loc) · 2.74 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
82
83
84
85
86
87
88
89
90
#' Flatten a list
#'
#' Flattening a list removes a single layer of internal hierarchy,
#' i.e. it inlines elements that are lists leaving non-lists alone.
#'
#' @param x A list.
#' @param name_spec If both inner and outer names are present, control
#' how they are combined. Should be a glue specification that uses
#' variables `inner` and `outer`.
#' @param name_repair One of `"minimal"`, `"unique"`, `"universal"`, or
#' `"check_unique"`. See [vctrs::vec_as_names()] for the meaning of these
#' options.
#' @inheritParams rlang::args_dots_empty
#' @inheritParams modify_tree
#' @return A list of the same type as `x`. The list might be shorter
#' if `x` contains empty lists, the same length if it contains lists
#' of length 1 or no sub-lists, or longer if it contains lists of
#' length > 1.
#' @export
#' @examples
#' x <- list(1, list(2, 3), list(4, list(5)))
#' x |> list_flatten() |> str()
#' x |> list_flatten() |> list_flatten() |> str()
#'
#' # Flat lists are left as is
#' list(1, 2, 3, 4, 5) |> list_flatten() |> str()
#'
#' # Empty lists will disappear
#' list(1, list(), 2, list(3)) |> list_flatten() |> str()
#'
#' # Another way to see this is that it reduces the depth of the list
#' x <- list(
#' list(),
#' list(list())
#' )
#' x |> pluck_depth()
#' x |> list_flatten() |> pluck_depth()
#'
#' # Use name_spec to control how inner and outer names are combined
#' x <- list(x = list(a = 1, b = 2), y = list(c = 1, d = 2))
#' x |> list_flatten() |> names()
#' x |> list_flatten(name_spec = "{outer}") |> names()
#' x |> list_flatten(name_spec = "{inner}") |> names()
#'
#' # Set `is_node = is.list` to also flatten richer objects built on lists like
#' # data frames and linear models
#' df <- data.frame(x = 1:3, y = 4:6)
#' x <- list(
#' a_string = "something",
#' a_list = list(1:3, "else"),
#' a_df = df
#' )
#' x |> list_flatten(is_node = is.list)
#'
#' # Note that objects that are already "flat" retain their classes
#' list_flatten(df, is_node = is.list)
list_flatten <- function(
x,
...,
is_node = NULL,
name_spec = "{outer}_{inner}",
name_repair = c("minimal", "unique", "check_unique", "universal")
) {
is_node <- as_is_node(is_node)
if (!is_node(x)) {
cli::cli_abort("{.arg x} must be a node.")
}
check_dots_empty()
check_string(name_spec)
# Take the proxy as we restore on exit
proxy <- vec_proxy(x)
# Unclass S3 lists to avoid their coercion methods. Wrap atoms in a
# list of size 1 so the elements can be concatenated in a single list.
proxy <- map_if(proxy, is_node, unclass, .else = list)
out <- list_unchop(
proxy,
ptype = list(),
name_spec = name_spec,
name_repair = name_repair,
error_arg = x,
error_call = current_env()
)
# Preserve input type
vec_restore(out, x)
}