forked from tidyverse/purrr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcoercion.R
107 lines (94 loc) · 2.93 KB
/
coercion.R
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#' Coerce a list to a vector
#'
#' `as_vector()` collapses a list of vectors into one vector. It
#' checks that the type of each vector is consistent with
#' `.type`. If the list can not be simplified, it throws an error.
#' `simplify` will simplify a vector if possible; `simplify_all`
#' will apply `simplify` to every element of a list.
#'
#' `.type` can be a vector mold specifying both the type and the
#' length of the vectors to be concatenated, such as `numeric(1)`
#' or `integer(4)`. Alternatively, it can be a string describing
#' the type, one of: "logical", "integer", "double", "complex",
#' "character" or "raw".
#'
#' @param .x A list of vectors
#' @param .type A vector mold or a string describing the type of the
#' input vectors. The latter can be any of the types returned by
#' [typeof()], or "numeric" as a shorthand for either
#' "double" or "integer".
#' @export
#' @examples
#' # Supply the type either with a string:
#' as.list(letters) %>% as_vector("character")
#'
#' # Or with a vector mold:
#' as.list(letters) %>% as_vector(character(1))
#'
#' # Vector molds are more flexible because they also specify the
#' # length of the concatenated vectors:
#' list(1:2, 3:4, 5:6) %>% as_vector(integer(2))
#'
#' # Note that unlike vapply(), as_vector() never adds dimension
#' # attributes. So when you specify a vector mold of size > 1, you
#' # always get a vector and not a matrix
as_vector <- function(.x, .type = NULL) {
if (can_simplify(.x, .type)) {
unlist(.x)
} else {
stop("Cannot coerce .x to a vector", call. = FALSE)
}
}
#' @export
#' @rdname as_vector
simplify <- function(.x, .type = NULL) {
if (can_simplify(.x, .type)) {
unlist(.x)
} else {
.x
}
}
#' @export
#' @rdname as_vector
simplify_all <- function(.x, .type = NULL) {
map(.x, simplify, .type = .type)
}
# Simplify a list of atomic vectors of the same type to a vector
#
# simplify_list(list(1, 2, 3))
can_simplify <- function(x, type = NULL) {
is_atomic <- vapply(x, is.atomic, logical(1))
if (!all(is_atomic)) return(FALSE)
mode <- unique(vapply(x, typeof, character(1)))
if (length(mode) > 1 &&
!all(c("double", "integer") %in% mode)) {
return(FALSE)
}
# This can be coerced safely. If type is supplied, perform
# additional check
is.null(type) || can_coerce(x, type)
}
can_coerce <- function(x, type) {
actual <- typeof(x[[1]])
if (is_mold(type)) {
lengths <- unique(map_int(x, length))
if (length(lengths) > 1 || !(lengths == length(type))) {
return(FALSE)
} else {
type <- typeof(type)
}
}
if (actual == "integer" && type %in% c("integer", "double", "numeric")) {
return(TRUE)
}
if (actual %in% c("integer", "double") && type == "numeric") {
return(TRUE)
}
actual == type
}
# is a mold? As opposed to a string
is_mold <- function(type) {
modes <- c("numeric", "logical", "integer", "double", "complex",
"character", "raw")
length(type) > 1 || (!type %in% modes)
}