rlist, another R package to support working with lists. Similar goals but somewhat different philosophy.
However, the goal of purrr is not to try and simulate a purer functional programming language in R; we don’t want to implement a second-class version of Haskell in R. The goal is to give you similar expressiveness to an FP language, while allowing you to write code that looks and works like R:
Instead of point free (tacit) style, we use the pipe, %>%, to write code that can be read from left to right.
Instead of currying, we use ... to pass in extra arguments.
Anonymous functions are verbose in R, so we provide two convenient shorthands. For unary functions, ~ .x + 1 is equivalent to function(.x) .x + 1. For chains of transformations functions, . %>% f() %>% g() is equivalent to function(.) . %>% f() %>% g() (this shortcut is provided by magrittr).
R is weakly typed, so we need map variants that describe the output type (like map_int(), map_dbl(), etc) because don’t the return type of .f.
R has named arguments, so instead of providing different functions for minor variations (e.g. detect() and detectLast()) we use a named argument, .right. Type-stable functions are easy to reason about so additional arguments will never change the type of the output.
purrr/inst/doc/other-langs.Rmd 0000644 0001762 0000144 00000004060 13124217327 016037 0 ustar ligges users ---
title: "Functional programming in other languages"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Functional programming in other languages}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
purrr draws inspiration from many related tools:
* List operations defined in the Haskell [prelude][haskell]
* Scala's [list methods][scala].
* Functional programming librarys for javascript:
[underscore.js](http://underscorejs.org),
[lodash](https://lodash.com) and
[lazy.js](http://danieltao.com/lazy.js/).
* [rlist](http://renkun.me/rlist/), another R package to support working
with lists. Similar goals but somewhat different philosophy.
However, the goal of purrr is not to try and simulate a purer functional programming language in R; we don't want to implement a second-class version of Haskell in R. The goal is to give you similar expressiveness to an FP language, while allowing you to write code that looks and works like R:
* Instead of point free (tacit) style, we use the pipe, `%>%`, to write code
that can be read from left to right.
* Instead of currying, we use `...` to pass in extra arguments.
* Anonymous functions are verbose in R, so we provide two convenient shorthands.
For unary functions, `~ .x + 1` is equivalent to `function(.x) .x + 1`.
For chains of transformations functions, `. %>% f() %>% g()` is
equivalent to `function(.) . %>% f() %>% g()` (this shortcut is provided
by magrittr).
* R is weakly typed, so we need `map` variants that describe the output type
(like `map_int()`, `map_dbl()`, etc) because don't the return type of `.f`.
* R has named arguments, so instead of providing different functions for
minor variations (e.g. `detect()` and `detectLast()`) we use a named
argument, `.right`. Type-stable functions are easy to reason about so
additional arguments will never change the type of the output.
[scala]:http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.List
[haskell]:http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#g:11
purrr/tests/ 0000755 0001762 0000144 00000000000 13171650762 012576 5 ustar ligges users purrr/tests/testthat.R 0000644 0001762 0000144 00000000066 12465413273 014562 0 ustar ligges users library(testthat)
library(purrr)
test_check("purrr")
purrr/tests/testthat/ 0000755 0001762 0000144 00000000000 13171733547 014441 5 ustar ligges users purrr/tests/testthat/test-utils.R 0000644 0001762 0000144 00000001456 13124217327 016676 0 ustar ligges users context("utils")
test_that("%@% is an infix attribute accessor", {
expect_identical(mtcars %@% "names", attr(mtcars, "names"))
})
test_that("rbernoulli is a special case of rbinom", {
set.seed(1)
x <- rbernoulli(10)
set.seed(1)
y <- ifelse(rbinom(10, 1, 0.5) == 1, TRUE, FALSE)
expect_equal(x, y)
})
test_that("rdunif works", {
expect_length(rdunif(100, 10), 100)
})
test_that("rdunif fails if a and b are not unit length numbers", {
expect_error(rdunif(1000, 1, "a"))
expect_error(rdunif(1000, 1, c(0.5, 0.2)))
expect_error(rdunif(1000, FALSE, 2))
expect_error(rdunif(1000, c(2, 3), 2))
})
test_that("has_names returns vector of logicals", {
expect_equal(has_names(letters %>% set_names()), rep_along(letters, TRUE))
expect_equal(has_names(letters), rep_along(letters, FALSE))
})
purrr/tests/testthat/test-pluck.R 0000644 0001762 0000144 00000007764 13137651733 016673 0 ustar ligges users context("pluck")
test_that("contents must be a vector", {
expect_error(pluck(quote(x), list(1)), "Don't know how to pluck")
})
# pluck vector --------------------------------------------------------------
test_that("can pluck by position", {
x <- list("a", 1, c(TRUE, FALSE))
# double
expect_identical(pluck(x, list(1)), x[[1]])
expect_identical(pluck(x, list(2)), x[[2]])
expect_identical(pluck(x, list(3)), x[[3]])
# integer
expect_identical(pluck(x, list(1L)), x[[1]])
expect_identical(pluck(x, list(2L)), x[[2]])
expect_identical(pluck(x, list(3L)), x[[3]])
})
test_that("can pluck by name", {
x <- list(a = "a", b = 1, c = c(TRUE, FALSE))
expect_identical(pluck(x, list("a")), x[["a"]])
expect_identical(pluck(x, list("b")), x[["b"]])
expect_identical(pluck(x, list("c")), x[["c"]])
})
test_that("can pluck from atomic vectors", {
expect_identical(pluck(TRUE, list(1)), TRUE)
expect_identical(pluck(1L, list(1)), 1L)
expect_identical(pluck(1, list(1)), 1)
expect_identical(pluck("a", list(1)), "a")
})
test_that("can pluck by name and position", {
x <- list(a = list(list(b = 1)))
expect_equal(pluck(x, list("a", 1, "b")), 1)
})
test_that("require length 1 vectors", {
expect_error(pluck(1, list(letters)), "must have length 1")
expect_error(pluck(1, list(TRUE)), "must be a character or numeric")
})
test_that("special indexes never match", {
x <- list(a = 1, b = 2, c = 3)
expect_null(pluck(x, list(NA_character_)))
expect_null(pluck(x, list("")))
expect_null(pluck(x, list(NA_integer_)))
expect_null(pluck(x, list(NA_real_)))
expect_null(pluck(x, list(NaN)))
expect_null(pluck(x, list(Inf)))
expect_null(pluck(x, list(-Inf)))
})
test_that("special values return NULL", {
# unnamed input
expect_null(pluck(list(1, 2), list("a")))
# zero length input
expect_null(pluck(integer(), list(1)))
# past end
expect_null(pluck(1:4, list(10)))
expect_null(pluck(1:4, list(10L)))
})
test_that("handles weird names", {
x <- list(1, 2, 3, 4, 5)
names(x) <- c("a", "a", NA, "", "b")
expect_equal(pluck(x, list("a")), 1)
expect_equal(pluck(x, list("b")), 5)
expect_null(pluck(x, list("")))
expect_null(pluck(x, list(NA_character_)))
})
# closures ----------------------------------------------------------------
test_that("can pluck attributes", {
x <- structure(
list(
structure(
list(),
x = 1
)
),
y = 2
)
expect_equal(pluck(x, list(attr_getter("y"))), 2)
expect_equal(pluck(x, list(1, attr_getter("x"))), 1)
})
test_that("attr_getter() evaluates eagerly", {
getters <- list_len(2)
attrs <- c("foo", "bar")
for (i in seq_along(attrs)) {
getters[[i]] <- attr_getter(attrs[[i]])
}
x <- set_attrs(list(), foo = "foo", bar = "bar")
expect_identical(getters[[1]](x), "foo")
})
test_that("delegate error handling to Rf_eval()", {
expect_error(pluck(letters, list(function() NULL)), "unused argument")
expect_error(pluck(letters, list(function(x, y) y)), "missing, with no default")
})
# environments ------------------------------------------------------------
test_that("pluck errors with invalid indices", {
expect_error(pluck(environment(), list(1)), "not a string")
expect_error(pluck(environment(), list(letters)), "not a string")
})
test_that("pluck returns missing with missing index", {
expect_equal(pluck(environment(), list(NA_character_)), NULL)
})
test_that("plucks by name", {
env <- new.env(parent = emptyenv())
env$x <- 10
expect_equal(pluck(env, list("x")), 10)
})
# S4 ----------------------------------------------------------------------
newA <- methods::setClass("A", list(a = "numeric", b = "numeric"))
A <- newA(a = 1, b = 10)
test_that("pluck errors with invalid indices", {
expect_error(pluck(A, list(1)), "not a string")
expect_error(pluck(A, list(letters)), "not a string")
})
test_that("pluck returns missing with missing index", {
expect_equal(pluck(A, list(NA_character_)), NULL)
})
test_that("plucks by name", {
expect_equal(pluck(A, list("a")), 1)
})
purrr/tests/testthat/test-splice.R 0000644 0001762 0000144 00000001011 13124217327 017000 0 ustar ligges users context("splice")
test_that("predicate controls which elements get spliced", {
x <- list(1, 2, list(3, 4))
expect_equal(splice_if(x, ~ FALSE), x)
expect_equal(splice_if(x, is.list), list(1, 2, 3, 4))
})
test_that("splice() produces correctly named lists", {
inputs <- list(arg1 = "a", arg2 = "b")
out1 <- splice(inputs, arg3 = c("c1", "c2"))
expect_named(out1, c("arg1", "arg2", "arg3"))
out2 <- splice(inputs, arg = list(arg3 = 1, arg4 = 2))
expect_named(out2, c("arg1", "arg2", "arg3", "arg4"))
})
purrr/tests/testthat/test-output.R 0000644 0001762 0000144 00000001773 13137651733 017107 0 ustar ligges users context("output")
test_that("safely has NULL error when successful", {
out <- safely(log10)(10)
expect_equal(out, list(result = 1, error = NULL))
})
test_that("safely has NULL result on failure", {
out <- safely(log10)("a")
expect_equal(out$result, NULL)
expect_equal(out$error$message,
"non-numeric argument to mathematical function")
})
test_that("quietly captures output", {
f <- function() {
cat(1)
message(2, appendLF = FALSE)
warning(3)
4
}
expect_output(quietly(f)(), NA)
expect_message(quietly(f)(), NA)
expect_warning(quietly(f)(), NA)
out <- quietly(f)()
expect_equal(out, list(
result = 4,
output = "1",
warnings = "3",
messages = "2"
))
})
test_that("possibly returns default value on failure", {
expect_identical(possibly(log, NA_real_)("a"), NA_real_)
})
test_that("auto_browse() not intended for primitive functions", {
expect_error(auto_browse(log)(NULL), "primitive functions")
expect_error(auto_browse(identity)(NULL), NA)
})
purrr/tests/testthat/test-as-mapper.R 0000644 0001762 0000144 00000004233 13137651733 017426 0 ustar ligges users context("as_mapper")
# formulas ----------------------------------------------------------------
test_that("can refer to first argument in three ways", {
expect_equal(map_dbl(1, ~ . + 1), 2)
expect_equal(map_dbl(1, ~ .x + 1), 2)
expect_equal(map_dbl(1, ~ ..1 + 1), 2)
})
test_that("can refer to second arg in two ways", {
expect_equal(map2_dbl(1, 2, ~ .x + .y + 1), 4)
expect_equal(map2_dbl(1, 2, ~ ..1 + ..2 + 1), 4)
})
# vectors --------------------------------------------------------------
# test_that(".null generates warning", {
# expect_warning(map(1, 2, .null = NA), "`.null` is deprecated")
# })
test_that(".default replaces absent values", {
x <- list(
list(a = 1, b = 2, c = 3),
list(a = 1, c = 2),
NULL
)
expect_equal(map_dbl(x, 3, .default = NA), c(3, NA, NA))
expect_equal(map_dbl(x, "b", .default = NA), c(2, NA, NA))
})
test_that(".default replaces elements with length 0", {
x <- list(
list(a = 1),
list(a = NULL),
list(a = numeric())
)
expect_equal(map_dbl(x, "a", .default = NA), c(1, NA, NA))
})
test_that("Additional arguments are ignored", {
expect_equal(as_mapper(function() NULL, foo = "bar", foobar), function() NULL)
})
test_that("can supply length > 1 vectors", {
expect_identical(as_mapper(1:2)(list(list("a", "b"))), "b")
expect_identical(as_mapper(c("a", "b"))(list(a = list("a", b = "b"))), "b")
})
# primitive functions --------------------------------------------------
test_that("primitive functions are wrapped", {
expect_identical(as_mapper(`-`)(.y = 10, .x = 5), -5)
expect_identical(as_mapper(`c`)(1, 3, 5), c(1, 3, 5))
})
test_that("syntactic primitives are wrapped", {
expect_identical(as_mapper(`[[`)(mtcars, "cyl"), mtcars$cyl)
expect_identical(as_mapper(`$`)(mtcars, cyl), mtcars$cyl)
})
# lists ------------------------------------------------------------------
test_that("lists are wrapped", {
mapper_list <- as_mapper(list("mpg", 5))(mtcars)
base_list <- mtcars[["mpg"]][[5]]
expect_identical(mapper_list, base_list)
})
test_that("raw and complex types aren't supported for indexing", {
expect_error(as_mapper(1)(raw(2)))
expect_error(as_mapper(1)(complex(2)))
})
purrr/tests/testthat/test-head-tail.R 0000644 0001762 0000144 00000000640 13124217327 017360 0 ustar ligges users context("head-tail")
y <- 1:100
test_that("head_while works", {
expect_length(head_while(y, function(x) x <= 15), 15)
})
test_that("tail_while works", {
expect_length(tail_while(y, function(x) x >= 86), 15)
})
test_that("original vector returned if predicate satisfied by all elements", {
expect_identical(head_while(y, function(x) x <= 100), y)
expect_identical(tail_while(y, function(x) x >= 0), y)
})
purrr/tests/testthat/test-list-modify-update.R 0000644 0001762 0000144 00000004322 13137651733 021260 0 ustar ligges users context("list_modify")
# list_modify -------------------------------------------------------------
test_that("named lists have values replaced by name", {
expect_equal(list_modify(list(a = 1), b = 2), list(a = 1, b = 2))
expect_equal(list_modify(list(a = 1), a = 2), list(a = 2))
expect_equal(list_modify(list(a = 1, b = 2), b = NULL), list(a = 1))
})
test_that("unnamed lists are replaced by position", {
expect_equal(list_modify(list(3), 1, 2), list(1, 2))
expect_equal(list_modify(list(1, 2, 3), 4), list(4, 2, 3))
expect_equal(list_modify(list(1, 2, 3), NULL, NULL), list(3))
})
test_that("error if one named and the other is not", {
expect_error(
list_modify(list(a = 1), 2),
"must be either both named or both unnamed"
)
})
test_that("lists are replaced recursively", {
expect_equal(
list_modify(
list(a = list(x = 1)),
a = list(x = 2)
),
list(a = list(x = 2))
)
expect_equal(
list_modify(
list(a = list(x = 1)),
a = list(y = 2)
),
list(a = list(x = 1, y = 2))
)
})
# list_merge --------------------------------------------------------------
test_that("list_merge concatenates values from two lists", {
l1 <- list(x = 1:10, y = 4, z = list(a = 1, b = 2))
l2 <- list(x = 11, z = list(a = 2:5, c = 3))
l <- list_merge(l1, !!! l2)
expect_equal(l$x, c(l1$x, l2$x))
expect_equal(l$y, c(l1$y, l2$y))
expect_equal(l$z$a, c(l1$z$a, l2$z$a))
expect_equal(l$z$b, c(l1$z$b, l2$z$b))
expect_equal(l$z$c, c(l1$z$c, l2$z$c))
})
test_that("list_merge concatenates without needing names", {
l1 <- list(1:10, 4, list(1, 2))
l2 <- list(11, 5, list(2:5, 3))
expect_length(list_merge(l1, !!! l2), 3)
})
test_that("list_merge returns the non-empty list", {
expect_equal(list_merge(list(3)), list(3))
expect_equal(list_merge(list(), 2), set_names(list(2), ""))
})
# update_list ------------------------------------------------------------
test_that("can modify element called x", {
expect_equal(update_list(list(), x = 1), list(x = 1))
})
test_that("quosures and formulas are evaluated", {
expect_identical(update_list(list(x = 1), y = quo(x + 1)), list(x = 1, y = 2))
expect_identical(update_list(list(x = 1), y = ~x + 1), list(x = 1, y = 2))
})
purrr/tests/testthat/test-along.R 0000644 0001762 0000144 00000000367 13124217327 016636 0 ustar ligges users context("along")
test_that("list_along works", {
x <- 1:5
expect_identical(list_along(x), vector("list", 5))
})
test_that("rep_along works", {
expect_equal(
rep_along(c("c", "b", "a"), 1:3),
rep_along(c("d", "f", "e"), 1:3)
)
})
purrr/tests/testthat/test-predicates.R 0000644 0001762 0000144 00000000715 13124217327 017656 0 ustar ligges users context("predicates")
test_that("predicate-based functionals work with logical vectors", {
expect_equal(keep(as.list(1:3), c(TRUE, FALSE, TRUE)), list(1, 3))
expect_equal(discard(as.list(1:3), c(TRUE, FALSE, TRUE)), list(2))
expect_equal(
modify_if(as.list(1:3), c(TRUE, FALSE, TRUE), as.character),
list("1", 2, "3")
)
expect_equal(
lmap_if(as.list(1:3), c(TRUE, FALSE, TRUE), ~list(as.character(.x[[1]]))),
list("1", 2, "3")
)
})
purrr/tests/testthat/test-cross.R 0000644 0001762 0000144 00000001301 13124217327 016654 0 ustar ligges users context("cross")
test_that("long format corresponds to expand.grid output", {
x <- list(a = 1:3, b = 4:9)
out1 <- cross_df(x)
out2 <- expand.grid(x, KEEP.OUT.ATTRS = FALSE) %>% dplyr::as_data_frame()
expect_equal(out1, out2)
})
test_that("filtering works", {
filter <- function(x, y) x >= y
out <- cross2(1:3, 1:3, .filter = filter)
expect_equal(out, list(list(1, 2), list(1, 3), list(2, 3)))
})
test_that("filtering fails when filter function doesn't return a logical", {
filter <- function(x, y, z) x + y + z
expect_error(cross3(1:3, 1:3, 1:3, .filter = filter))
})
test_that("works with empty input", {
expect_equal(cross(list()), list())
expect_equal(cross(NULL), NULL)
})
purrr/tests/testthat/test-composition.R 0000644 0001762 0000144 00000001467 13124217327 020103 0 ustar ligges users context("composition")
test_that("lift_dl and lift_ld are inverses of each other", {
expect_identical(
sum %>%
lift_dl(.unnamed = TRUE) %>%
invoke(list(3, NA, 4, na.rm = TRUE)),
sum %>%
lift_dl() %>%
lift_ld() %>%
invoke(3, NA, 4, na.rm = TRUE)
)
})
test_that("lift_dv is from ... to c(...)", {
expect_equal(lift_dv(range, .unnamed = TRUE)(1:10), c(1, 10))
})
test_that("lift_vd is from c(...) to ...", {
expect_equal(lift_vd(mean)(1, 2), 1.5)
})
test_that("lift_vl is from c(...) to list(...)", {
expect_equal(lift_vl(mean)(list(1, 2)), 1.5)
})
test_that("lift_lv is from list(...) to c(...)", {
glue <- function(l) {
if (!is.list(l)) stop("not a list")
l %>% invoke(paste, .)
}
expect_identical(lift_lv(glue)(letters), paste(letters, collapse = " "))
})
purrr/tests/testthat/test-compose.R 0000644 0001762 0000144 00000000420 13124217327 017171 0 ustar ligges users context("compose")
test_that("composed functions are applied right to left", {
expect_identical(!is.null(4), compose(`!`, is.null)(4))
set.seed(1)
x <- sample(1:4, 100, replace = TRUE)
expect_identical(unname(sort(table(x))), compose(unname, sort, table)(x))
})
purrr/tests/testthat/test-partial.R 0000644 0001762 0000144 00000002057 13124217327 017170 0 ustar ligges users context("partial")
test_that("dots are correctly placed in the signature", {
dots_last_actual <- call("runif", n = call("rpois", 1, 5), quote(...))
dots_last_alleged <- partial(runif, n = rpois(1, 5)) %>% body()
expect_identical(dots_last_actual, dots_last_alleged)
# Also tests that argument names are not eaten when .dots_first = TRUE
dots_first_actual <- call("runif", quote(...), n = call("rpois", 1, 5))
dots_first_alleged <- partial(runif, n = rpois(1, 5), .first = FALSE) %>%
body()
expect_identical(dots_first_actual, dots_first_alleged)
})
test_that("partial() works with no partialised arguments", {
actual <- call("runif", quote(...))
alleged1 <- partial(runif, .first = TRUE) %>% body()
alleged2 <- partial(runif, .first = FALSE) %>% body()
expect_identical(actual, alleged1)
expect_identical(actual, alleged2)
})
test_that("lazy evaluation means arguments aren't repeatedly evaluated", {
f <- partial(runif, n = rpois(1, 5), .lazy = FALSE)
.n <- 100
v <- map_int(rerun(.n, f()), length)
expect_true(table(v) == .n)
})
purrr/tests/testthat/test-map_n.R 0000644 0001762 0000144 00000003657 13124217327 016635 0 ustar ligges users context("pmap")
test_that("input must be a list of vectors", {
expect_error(pmap(environment(), identity), "not a list")
expect_error(pmap(list(environment()), identity), "not a vector")
})
test_that("elements must be same length", {
expect_error(pmap(list(1:2, 1:3), identity), "has length 2")
})
test_that("handles any length 0 input", {
expect_equal(pmap(list(list(), list(), list()), ~ 1), list())
expect_equal(pmap(list(NULL, NULL, NULL), ~ 1), list())
expect_equal(pmap(list(list(), list(), 1:10), ~ 1), list())
expect_equal(pmap(list(NULL, NULL, 1:10), ~ 1), list())
})
test_that("length 1 elemetns are recycled", {
out <- pmap(list(1:2, 1), c)
expect_equal(out, list(c(1, 1), c(2, 1)))
})
test_that(".f called with named arguments", {
out <- pmap(list(x = 1, 2, y = 3), list)[[1]]
expect_equal(names(out), c("x", "", "y"))
})
test_that("names are preserved", {
out <- pmap(list(c(x = 1, y = 2), 3:4), list)
expect_equal(names(out), c("x", "y"))
})
test_that("... are passed on", {
out <- pmap(list(x = 1:2), list, n = 1)
expect_equal(out, list(
list(x = 1, n = 1),
list(x = 2, n = 1)
))
})
test_that("outputs are suffixes have correct type", {
x <- 1:3
expect_is(pmap_lgl(list(x), is.numeric), "logical")
expect_is(pmap_int(list(x), length), "integer")
expect_is(pmap_dbl(list(x), mean), "numeric")
expect_is(pmap_chr(list(x), paste), "character")
expect_is(pmap_dfr(list(x), as.data.frame), "data.frame")
expect_is(pmap_dfc(list(x), as.data.frame), "data.frame")
})
test_that("pmap on data frames performs rowwise operations", {
mtcars2 <- mtcars[c("mpg", "cyl")]
expect_length(pmap(mtcars2, paste), nrow(mtcars))
expect_is(pmap_lgl(mtcars2, function(mpg, cyl) mpg > cyl), "logical")
expect_is(pmap_int(mtcars2, function(mpg, cyl) as.integer(cyl)), "integer")
expect_is(pmap_dbl(mtcars2, function(mpg, cyl) mpg + cyl), "numeric")
expect_is(pmap_chr(mtcars2, paste), "character")
})
purrr/tests/testthat/test-invoke.R 0000644 0001762 0000144 00000003000 13124217327 017014 0 ustar ligges users context("invoke")
# invoke ------------------------------------------------------------------
test_that("invoke() evaluates expressions in the right environment", {
x <- letters
f <- toupper
expect_equal(invoke("f", quote(x)), toupper(letters))
})
test_that("invoke() follows promises to find the evaluation env", {
x <- letters
f <- toupper
f1 <- function(y) {
f2 <- function(z) purrr::invoke(z, quote(x))
f2(y)
}
expect_equal(f1("f"), toupper(letters))
})
# invoke_map --------------------------------------------------------------
test_that("invoke_map() works with bare function", {
data <- list(1:2, 3:4)
expected <- list("1 2", "3 4")
expect_equal(invoke_map(paste, data), expected)
expect_equal(invoke_map("paste", data), expected)
expect_equal(invoke_map_chr(paste, data), unlist(expected))
expect_identical(invoke_map_dbl(`+`, data), c(3, 7))
expect_identical(invoke_map_int(`+`, data), c(3L, 7L))
expect_identical(invoke_map_lgl(`&&`, data), c(TRUE, TRUE))
ops <- set_names(c(`+`, `-`), c("a", "b"))
expect_identical(invoke_map_dfr(ops, data), invoke_map_dfc(ops, data))
})
test_that("invoke_map() evaluates expressions in the right environment", {
shadowed_object <- letters
shadowed_fun <- toupper
expect_equal(
invoke_map("shadowed_fun", list(quote(shadowed_object))),
list(toupper(letters))
)
})
test_that("invoke_maps doesn't rely on c() returning list", {
day <- as.Date("2016-09-01")
expect_equal(invoke_map(identity, list(day)), list(day))
})
purrr/tests/testthat/test-recycle_args.R 0000644 0001762 0000144 00000001234 13124217327 020172 0 ustar ligges users context("recycle_args")
test_that("rejects uneven lengths", {
args <- list(1, c(1:2), NULL)
expect_error(purrr:::recycle_args(args), "lengths == 1L \\| lengths == n")
})
test_that("recycles single values and preserves longer ones", {
args <- list(1, 1:12, month.name, "a")
recycled <- purrr:::recycle_args(args)
expect_equal(recycled[[1]], rep(1, 12))
expect_equal(recycled[[2]], 1:12)
expect_equal(recycled[[3]], month.name)
expect_equal(recycled[[4]], rep("a", 12))
})
test_that("will not recycle non-vectors", {
args <- list(1:12, identity)
expect_error(
purrr:::recycle_args(args),
"replicate an object of type 'closure'"
)
})
purrr/tests/testthat/test-reduce.R 0000644 0001762 0000144 00000003455 13124217327 017006 0 ustar ligges users context("reduce")
test_that("empty input returns init or error", {
expect_error(reduce(list()), "no `.init` supplied")
expect_equal(reduce(list(), `+`, .init = 0), 0)
})
test_that("first/value value used as first value", {
x <- c(1, 1)
expect_equal(reduce(x, `+`), 2)
expect_equal(reduce(x, `+`, .init = 1), 3)
expect_equal(reduce_right(x, `+`), 2)
expect_equal(reduce_right(x, `+`, .init = 1), 3)
})
test_that("length 1 argument reduced with init", {
expect_equal(reduce(1, `+`, .init = 1), 2)
expect_equal(reduce_right(1, `+`, .init = 1), 2)
})
test_that("reduce_right equivalent to reversing input", {
x <- list(c(2, 1), c(4, 3), c(6, 5))
expect_equal(reduce_right(x, c), c(6, 5, 4, 3, 2, 1))
expect_equal(reduce_right(x, c, .init = 7), c(7, 6, 5, 4, 3, 2, 1))
})
# accumulate --------------------------------------------------------------
test_that("accumulate passes arguments to function", {
tt <- c("a", "b", "c")
expect_equal(accumulate(tt, paste, sep = "."), c("a", "a.b", "a.b.c"))
expect_equal(accumulate_right(tt, paste, sep = "."), c("c.b.a", "c.b", "c"))
})
# reduce2 -----------------------------------------------------------------
test_that("basic application works", {
paste2 <- function(x, y, sep) paste(x, y, sep = sep)
x <- c("a", "b", "c")
expect_equal(reduce2(x, c("-", "."), paste2), "a-b.c")
expect_equal(reduce2(x, c(".", "-", "."), paste2, .init = "x"), "x.a-b.c")
})
test_that("reduce2_right works if lengths match", {
x <- list(c(0, 1), c(2, 3), c(4, 5))
y <- list(c(6, 7), c(8, 9))
expect_equal(reduce2_right(x, paste, y), c("4 2 8 0 6", "5 3 9 1 7"))
expect_error(reduce2_right(y, paste, x))
})
test_that("reduce returns original input if it was length one", {
x <- list(c(0, 1), c(2, 3), c(4, 5))
expect_equal(reduce(x[1], paste), x[[1]])
})
purrr/tests/testthat/test-coerce.R 0000644 0001762 0000144 00000004444 13124217327 016776 0 ustar ligges users context("coerce")
test_that("missing values converted to new type", {
expect_equal(coerce_int(NA), NA_integer_)
expect_equal(coerce_dbl(NA), NA_real_)
expect_equal(coerce_chr(NA), NA_character_)
expect_equal(coerce_dbl(NA_integer_), NA_real_)
expect_equal(coerce_chr(NA_integer_), NA_character_)
expect_equal(coerce_chr(NA_real_), NA_character_)
})
test_that("can't coerce downwards", {
expect_error(coerce_chr(list(1)), "Can't coerce")
expect_error(coerce_dbl(list(1)), "Can't coerce")
expect_error(coerce_int(list(1)), "Can't coerce")
expect_error(coerce_lgl(list(1)), "Can't coerce")
expect_error(coerce_dbl("a"), "Can't coerce")
expect_error(coerce_int("a"), "Can't coerce")
expect_error(coerce_lgl("a"), "Can't coerce")
expect_error(coerce_int(1), "Can't coerce")
expect_error(coerce_lgl(1), "Can't coerce")
expect_error(coerce_lgl(1L), "Can't coerce")
})
test_that("coercing to same type returns input", {
expect_equal(coerce_lgl(c(TRUE, FALSE)), c(TRUE, FALSE))
expect_equal(coerce_dbl(c(1, 2)), c(1, 2))
expect_equal(coerce_int(c(1L, 2L)), c(1L, 2L))
expect_equal(coerce_chr(c("a", "b")), c("a", "b"))
})
test_that("types automatically coerced upwards", {
expect_identical(coerce_int(c(FALSE, TRUE)), c(0L, 1L))
expect_identical(coerce_dbl(c(FALSE, TRUE)), c(0, 1))
expect_identical(coerce_dbl(c(1L, 2L)), c(1, 2))
expect_identical(coerce_chr(c(FALSE, TRUE)), c("FALSE", "TRUE"))
expect_identical(coerce_chr(c(1L, 2L)), c("1", "2"))
expect_identical(coerce_chr(c(1.5, 2.5)), c("1.500000", "2.500000"))
})
test_that("coercing to character values correctly", {
expect_equal(coerce_chr(c(FALSE, TRUE)), c("FALSE", "TRUE"))
expect_equal(coerce_chr(c(1L, 2L)), c("1", "2"))
expect_equal(coerce_chr(c(1.5, 2.5)), c("1.500000", "2.500000"))
expect_equal(coerce_chr(c("a", "b")), c("a", "b"))
x <- c(NA, NaN, Inf, -Inf)
expect_equal(coerce(x, "character"), as.character(x))
})
test_that("can't coerce to expressions", {
expect_error(coerce(list(1), "expression"))
})
test_that("as_vector can be type-specifc", {
expect_identical(as_vector(as.list(letters), "character"), letters)
})
test_that("as_vector cannot coerce lists with zero-length elements", {
x <- list(a = 1, b = c(list(), 3))
expect_error(as_vector(x))
expect_identical(x, simplify(x))
})
purrr/tests/testthat/test-arrays.R 0000644 0001762 0000144 00000000764 13124217327 017040 0 ustar ligges users context("arrays")
x <- array(1:12, c(2, 2, 3))
test_that("array_branch creates a flat list when no margin specified", {
expect_length(array_branch(x), 12)
})
test_that("array_branch wraps array in list when margin has length 0", {
expect_identical(array_branch(x, numeric(0)), list(x))
})
test_that("length depends on whether list is flattened or not", {
m1 <- c(3, 1)
m2 <- 3
expect_length(array_branch(x, m1), prod(dim(x)[m1]))
expect_length(array_tree(x, m1), prod(dim(x)[m2]))
})
purrr/tests/testthat/test-when.R 0000644 0001762 0000144 00000002013 13124217327 016465 0 ustar ligges users context("when")
test_that("when chooses the correct action", {
x <-
1:5 %>%
when(
sum(.) <= 50 ~ sum(.),
sum(.) <= 100 ~ sum(.) / 2,
~ 0
)
expect_equal(x, 15)
y <-
1:10 %>%
when(
sum(.) <= 50 ~ sum(.),
sum(.) <= 100 ~ sum(.) / 2,
~ 0
)
expect_equal(y, sum(1:10) / 2)
z <-
1:100 %>%
when(
sum(.) <= 50 ~ sum(.),
sum(.) <= 100 ~ sum(.) / 2,
~ 0
)
expect_equal(z, 0)
})
test_that("named arguments work with when", {
x <-
1:10 %>%
when(
sum(.) <= x ~ sum(.) * x,
sum(.) <= 2 * x ~ sum(.) * x / 2,
~ 0,
x = 60
)
expect_equal(x, sum(1:10) * 60)
})
test_that("default values work without a formula", {
x <-
iris %>%
subset(Sepal.Length > 10) %>%
when(
nrow(.) > 0 ~ .,
head(iris, 10)
)
expect_equivalent(x, head(iris, 10))
})
test_that("error when named arguments have no matching conditions", {
expect_error(1:5 %>% when(a = sum(.) < 5 ~ 3))
})
purrr/tests/testthat/test-imap.R 0000644 0001762 0000144 00000001261 13124217327 016456 0 ustar ligges users context("imap")
x <- 1:3 %>% set_names()
test_that("imap is special case of map2", {
expect_identical(imap(x, paste), map2(x, names(x), paste))
})
test_that("imap always returns a list", {
expect_is(imap(x, paste), "list")
})
test_that("atomic vector imap works", {
expect_true(all(imap_lgl(x, `==`)))
expect_length(imap_chr(x, paste), 3)
expect_equal(imap_int(x, ~ .x + as.integer(.y)), x * 2)
expect_equal(imap_dbl(x, ~ .x + as.numeric(.y)), x * 2)
})
test_that("data frame imap works", {
expect_identical(imap_dfc(x, paste), imap_dfr(x, paste))
})
test_that("iwalk returns invisibly", {
expect_output(iwalk(mtcars, ~ cat(.y, ": ", median(.x), "\n", sep = "")))
})
purrr/tests/testthat/test-find-position.R 0000644 0001762 0000144 00000001727 13137651733 020330 0 ustar ligges users context("find-position")
y <- 4:10
test_that("detect functions work", {
is_odd <- function(x) x %% 2 == 1
expect_equal(detect(y, is_odd), 5)
expect_equal(detect_index(y, is_odd), 2)
expect_equal(detect(y, is_odd, .right = TRUE), 9)
expect_equal(detect_index(y, is_odd, .right = TRUE), 6)
})
test_that("detect returns NULL when match not found", {
expect_null(detect(y, function(x) x > 11))
})
test_that("detect_index returns 0 when match not found", {
expect_equal(detect_index(y, function(x) x > 11), 0)
})
test_that("has_element checks whether a list contains an object", {
expect_true(has_element(list(1, 2), 1))
expect_false(has_element(list(1, 2), 3))
})
test_that("detect functions still work with `.p`", {
is_even <- function(x) x %% 2 == 0
expect_warning(regex = "renamed to `.f`",
expect_identical(detect(1:3, .p = is_even), 2L)
)
expect_warning(regex = "renamed to `.f`",
expect_identical(detect_index(1:3, .p = is_even), 2L)
)
})
purrr/tests/testthat/test-depth.R 0000644 0001762 0000144 00000001257 13137651733 016650 0 ustar ligges users context("depth")
test_that("depth of NULL is 0", {
expect_equal(vec_depth(NULL), 0L)
})
test_that("depth of atomic vector is 1", {
expect_equal(vec_depth(1:10), 1)
expect_equal(vec_depth(letters), 1)
expect_equal(vec_depth(c(TRUE, FALSE)), 1)
})
test_that("depth of empty list is 1", {
expect_equal(vec_depth(list()), 1)
})
test_that("depth of nested is depth of deepest element + 1", {
x <- list(
NULL,
list(),
list(list())
)
depths <- map_int(x, vec_depth)
expect_equal(depths, c(0, 1, 2))
expect_equal(vec_depth(x), max(depths) + 1)
})
test_that("depth throws an error if input is not a vector", {
expect_error(vec_depth(as.formula(y ~ x)))
})
purrr/tests/testthat/test-prepend.R 0000644 0001762 0000144 00000000405 13124217327 017164 0 ustar ligges users context("prepend")
test_that("prepend is clearer version of merging with c()", {
x <- 1:3
expect_identical(
x %>% prepend(4),
x %>% c(4, .)
)
expect_identical(
x %>% prepend(4, before = 3),
x %>% {
c(.[1:2], 4, .[3])
}
)
})
purrr/tests/testthat/test-lmap.R 0000644 0001762 0000144 00000000351 13124217327 016460 0 ustar ligges users context("lmap")
test_that("lmap output is list if input is list", {
expect_is(lmap(as.list(mtcars), as.list), "list")
})
test_that("lmap output is tibble if input is data frame", {
expect_is(lmap(mtcars, as.list), "tbl_df")
})
purrr/tests/testthat/test-simplify.R 0000644 0001762 0000144 00000001735 12602231320 017357 0 ustar ligges users context("simplify")
test_that("can_simplify() understands vector molds", {
x <- as.list(1:3)
x2 <- c(x, list(1:3))
expect_true(can_simplify(x, integer(1)))
expect_false(can_simplify(x, character(1)))
expect_false(can_simplify(x2, integer(1)))
x3 <- list(1:2, 3:4, 5:6)
expect_true(can_simplify(x3, integer(2)))
expect_false(can_simplify(x, integer(2)))
})
test_that("can_simplify() understands types as strings", {
x <- as.list(1:3)
expect_true(can_simplify(x, "integer"))
expect_false(can_simplify(x, "character"))
})
test_that("integer is coercible to double", {
x <- list(1L, 2L)
expect_true(can_simplify(x, "numeric"))
expect_true(can_simplify(x, numeric(1)))
expect_true(can_simplify(x, "double"))
expect_true(can_simplify(x, double(1)))
})
test_that("numeric is an alias for double", {
expect_true(can_simplify(list(1, 2), "numeric"))
})
test_that("double is not coercible to integer", {
expect_false(can_simplify(list(1, 2), "integer"))
})
purrr/tests/testthat/test-rerun.R 0000644 0001762 0000144 00000000732 13124217327 016665 0 ustar ligges users context("rerun")
test_that("single unnamed arg doesn't get extra list", {
expect_equal(rerun(2, 1), list(1, 1))
})
test_that("single named arg gets extra list", {
expect_equal(rerun(2, a = 1), list(list(a = 1), list(a = 1)))
})
test_that("every run is different", {
x <- rerun(2, runif(1))
expect_true(x[[1]] != x[[2]])
})
test_that("rerun uses scope of expression", {
f <- function(n) {
rerun(1, x = seq_len(n))
}
expect_equal(f(10)[[1]]$x, 1:10)
})
purrr/tests/testthat/test-transpose.R 0000644 0001762 0000144 00000005634 13102346452 017554 0 ustar ligges users context("transpose")
test_that("input must be a list", {
expect_error(transpose(1:3), "is not a list")
})
test_that("elements of input must be vectors", {
expect_error(transpose(list(environment())), "is not a vector")
expect_error(transpose(list(list(), environment())), "is not a vector")
})
test_that("empty list returns empty list", {
expect_equal(transpose(list()), list())
})
test_that("transpose switches order of first & second idnex", {
x <- list(list(1, 3), list(2, 4))
expect_equal(transpose(x), list(list(1, 2), list(3, 4)))
})
test_that("inside names become outside names", {
x <- list(list(x = 1), list(x = 2))
expect_equal(transpose(x), list(x = list(1, 2)))
})
test_that("outside names become inside names", {
x <- list(x = list(1, 3), y = list(2, 4))
expect_equal(transpose(x), list(list(x = 1, y = 2), list(x = 3, y = 4)))
})
test_that("warns if element too short", {
x <- list(list(1, 2), list(1))
expect_warning(out <- transpose(x), "Element 2 has length 1")
expect_equal(out, list(list(1, 1), list(2, NULL)))
})
test_that("warns if element too long", {
x <- list(list(1, 2), list(1, 2, 3))
expect_warning(out <- transpose(x), "Element 2 has length 3")
expect_equal(out, list(list(1, 1), list(2, 2)))
})
test_that("can transpose list of lists of atomic vectors", {
x <- list(list(TRUE, 1L, 1, "1"))
expect_equal(transpose(x), list(list(TRUE), list(1L), list(1), list("1")))
})
test_that("can transpose lists of atomic vectors", {
expect_equal(transpose(list(TRUE, FALSE)), list(list(TRUE, FALSE)))
expect_equal(transpose(list(1L, 2L)), list(list(1L, 2L)))
expect_equal(transpose(list(1, 2)), list(list(1, 2)))
expect_equal(transpose(list("a", "b")), list(list("a", "b")))
})
test_that("can't transpose expressions", {
expect_error(transpose(list(expression(a))), "Unsupported type")
})
# Named based matching ----------------------------------------------------
test_that("can override default names", {
x <- list(
list(x = 1),
list(y = 2, x = 1)
)
tx <- transpose(x, c("x", "y"))
expect_equal(tx, list(
x = list(1, 1),
y = list(NULL, 2)
))
})
test_that("if present, names are used", {
x <- list(
list(x = 1, y = 2),
list(y = 2, x = 1)
)
tx <- transpose(x)
expect_equal(tx$x, list(1, 1))
expect_equal(tx$y, list(2, 2))
})
test_that("if missing elements, filled with NULL", {
x <- list(
list(x = 1, y = 2),
list(x = 1)
)
tx <- transpose(x)
expect_equal(tx$y, list(2, NULL))
})
# Position based matching -------------------------------------------------
test_that("warning if too short", {
x <- list(
list(1, 2),
list(1)
)
expect_warning(tx <- transpose(x), "has length 1 not 2")
expect_equal(tx, list(list(1, 1), list(2, NULL)))
})
test_that("warning if too long", {
x <- list(
list(1),
list(1, 2)
)
expect_warning(tx <- transpose(x), "has length 2 not 1")
expect_equal(tx, list(list(1, 1)))
})
purrr/tests/testthat/test-every-some.R 0000644 0001762 0000144 00000000707 13124217327 017627 0 ustar ligges users context("every-some")
test_that("return NA if present", {
expect_equal(some(1:10, ~ NA), NA)
expect_equal(every(1:10, ~ NA), NA)
})
test_that("every returns TRUE if all elements are TRUE", {
x <- list(0, 1, TRUE)
expect_false(every(x, isTRUE))
expect_true(every(x[3], isTRUE))
})
test_that("some returns FALSE if all elements are FALSE", {
x <- list(1, 0, FALSE)
expect_false(some(x, isTRUE))
expect_true(some(x[1], negate(isTRUE)))
})
purrr/tests/testthat/test-map2.R 0000644 0001762 0000144 00000002340 13124217327 016366 0 ustar ligges users context("map2")
test_that("map2 inputs must be same length", {
expect_error(map2(1:3, 2:3, function(...) NULL), "different lengths")
})
test_that("map2 can't simplify if elements longer than length 1", {
expect_error(
map2_int(1:4, 5:8, range),
"Result 1 is not a length 1 atomic vector"
)
})
test_that("fails on non-vectors", {
expect_error(map2(environment(), "a", identity), "not a vector")
expect_error(map2("a", environment(), identity), "not a vector")
})
test_that("map2 vectorised inputs of length 1", {
expect_equal(map2(1:2, 1, `+`), list(2, 3))
expect_equal(map2(1, 1:2, `+`), list(2, 3))
})
test_that("any 0 length input gives 0 length output", {
expect_equal(map2(list(), list(), ~ 1), list())
expect_equal(map2(1:10, list(), ~ 1), list())
expect_equal(map2(list(), 1:10, ~ 1), list())
expect_equal(map2(NULL, NULL, ~ 1), list())
expect_equal(map2(1:10, NULL, ~ 1), list())
expect_equal(map2(NULL, 1:10, ~ 1), list())
})
test_that("map2 takes only names from x", {
x1 <- 1:3
x2 <- set_names(x1)
expect_equal(names(map2(x1, x2, `+`)), NULL)
expect_equal(names(map2(x2, x1, `+`)), names(x2))
})
test_that("map2 always returns a list", {
expect_is(map2(mtcars, 0, ~mtcars), "list")
})
purrr/tests/testthat/test-negate.R 0000644 0001762 0000144 00000000442 13102346452 016771 0 ustar ligges users context("negate")
test_that("negate works with both functions and vectors", {
true <- function(...) TRUE
expect_equal(negate(true)(), FALSE)
expect_equal(negate("x")(list(x = TRUE)), FALSE)
expect_equal(negate(is.null)(TRUE), TRUE)
expect_equal(negate(is.null)(NULL), FALSE)
})
purrr/tests/testthat/test-modify.R 0000644 0001762 0000144 00000003326 13124217327 017023 0 ustar ligges users context("modify")
test_that("modify returns same type as input", {
df1 <- data.frame(x = 1:3, y = 4:6)
expect_equal(modify(df1, length), data.frame(x = rep(3, 3), y = rep(3, 3)))
})
test_that("modify_if/modify_at return same type as input", {
df1 <- data.frame(x = "a", y = 2, stringsAsFactors = FALSE)
exp <- data.frame(x = "A", y = 2, stringsAsFactors = FALSE)
df2a <- modify_if(df1, is.character, toupper)
expect_equal(df2a, exp)
df2b <- modify_at(df1, "x", toupper)
expect_equal(df2b, exp)
})
test_that("modify_at requires a named object", {
df1 <- data.frame(x = "a", y = 2, stringsAsFactors = FALSE)
expect_error(modify_at(unname(df1), "x", toupper))
})
test_that("modify_at operates on character and numeric indexing", {
df1 <- data.frame(x = "a", y = 2, stringsAsFactors = FALSE)
expect_error(modify_at(df1, TRUE, toupper))
})
# modify_depth ------------------------------------------------------------
test_that("modify_depth modifies values at specified depth", {
x1 <- list(list(list(1)))
expect_equal(modify_depth(x1, 0, length), list(1))
expect_equal(modify_depth(x1, 1, length), list(1))
expect_equal(modify_depth(x1, 2, length), list(list(1)))
expect_equal(modify_depth(x1, 3, length), list(list(list(1))))
expect_equal(modify_depth(x1, -1, length), list(list(list(1))))
expect_error(modify_depth(x1, 4, length), "List not deep enough")
expect_error(modify_depth(x1, -5, length), "Invalid `depth`")
})
test_that(".ragged = TRUE operates on leaves", {
x1 <- list(
list(1),
list(list(2))
)
x2 <- list(
list(2),
list(list(3))
)
expect_equal(modify_depth(x1, 3, ~ . + 1, .ragged = TRUE), x2)
expect_equal(modify_depth(x1, -1, ~ . + 1, .ragged = TRUE), x2)
})
purrr/tests/testthat/test-flatten.R 0000644 0001762 0000144 00000004353 13124217327 017172 0 ustar ligges users context("flatten")
test_that("input must be a list", {
expect_error(flatten(1), "must be a list")
})
test_that("contents of list must be supported types", {
expect_error(flatten(list(quote(a))), "not a vector")
expect_error(flatten(list(expression(a))), "Unsupported type")
})
test_that("each second level element becomes first level element", {
expect_equal(flatten(list(1:2)), list(1, 2))
expect_equal(flatten(list(1, 2)), list(1, 2))
})
test_that("can flatten all atomic vectors", {
expect_equal(flatten(list(F)), list(F))
expect_equal(flatten(list(1L)), list(1L))
expect_equal(flatten(list(1)), list(1))
expect_equal(flatten(list("a")), list("a"))
})
test_that("NULLs are silently dropped", {
expect_equal(flatten(list(NULL, NULL)), list())
expect_equal(flatten(list(NULL, 1)), list(1))
expect_equal(flatten(list(1, NULL)), list(1))
})
test_that("names are preserved", {
expect_equal(flatten(list(list(x = 1), list(y = 1))), list(x = 1, y = 1))
expect_equal(flatten(list(list(a = 1, b = 2), 3)), list(a = 1, b = 2, 3))
})
test_that("names of 'scalar' elements are preserved", {
out <- flatten(list(a = list(1), b = list(2)))
expect_equal(out, list(a = 1, b = 2))
out <- flatten(list(a = list(1), b = 2:3))
expect_equal(out, list(a = 1, 2, 3))
out <- flatten(list(list(a = 1, b = 2), c = 3))
expect_equal(out, list(a = 1, b = 2, c = 3))
})
test_that("child names beat parent names", {
out <- flatten(list(a = list(x = 1), b = list(y = 2)))
expect_equal(out, list(x = 1, y = 2))
})
# atomic flatten ----------------------------------------------------------
test_that("must be a list", {
expect_error(flatten_lgl(1), "must be a list")
})
test_that("can flatten all atomic vectors", {
expect_equal(flatten_lgl(list(F)), F)
expect_equal(flatten_int(list(1L)), 1L)
expect_equal(flatten_dbl(list(1)), 1)
expect_equal(flatten_chr(list("a")), "a")
})
test_that("preserves inner names", {
expect_equal(
flatten_dbl(list(c(a = 1), c(b = 2))),
c(a = 1, b = 2)
)
})
# data frame flatten ------------------------------------------------------
test_that("can flatten to a data frame with named lists", {
expect_is(flatten_dfr(list(c(a = 1), c(b = 2))), "data.frame")
expect_error(flatten_dfc(list(1)))
})
purrr/tests/testthat/test-map.R 0000644 0001762 0000144 00000004210 13171452210 016274 0 ustar ligges users context("map")
test_that("preserves names", {
out <- map(list(x = 1, y = 2), identity)
expect_equal(names(out), c("x", "y"))
})
test_that("creates simple call", {
out <- map(1, function(x) sys.call())[[1]]
expect_identical(out, quote(.f(.x[[i]], ...)))
})
test_that("fails on non-vectors", {
expect_error(map(environment(), identity), "not a vector")
expect_error(map(quote(a), identity), "not a vector")
})
test_that("0 length input gives 0 length output", {
out1 <- map(list(), identity)
expect_equal(out1, list())
out2 <- map(NULL, identity)
expect_equal(out2, list())
})
test_that("map() always returns a list", {
expect_is(map(mtcars, mean), "list")
})
test_that("types automatically coerced upwards", {
expect_identical(map_int(c(FALSE, TRUE), identity), c(0L, 1L))
expect_identical(map_dbl(c(FALSE, TRUE), identity), c(0, 1))
expect_identical(map_dbl(c(1L, 2L), identity), c(1, 2))
expect_identical(map_chr(c(FALSE, TRUE), identity), c("FALSE", "TRUE"))
expect_identical(map_chr(c(1L, 2L), identity), c("1", "2"))
expect_identical(map_chr(c(1.5, 2.5), identity), c("1.500000", "2.500000"))
})
test_that("logical and integer NA become correct double NA", {
expect_identical(
map_dbl(list(NA, NA_integer_), identity),
c(NA_real_, NA_real_)
)
})
test_that("map forces arguments in same way as base R", {
f_map <- map(1:2, function(i) function(x) x + i)
f_base <- lapply(1:2, function(i) function(x) x + i)
expect_equal(f_map[[1]](0), f_base[[1]](0))
expect_equal(f_map[[2]](0), f_base[[2]](0))
})
test_that("row and column binding work", {
mtcar_mod <- mtcars %>%
split(.$cyl) %>%
map(~ lm(mpg ~ wt, data = .x))
f_coef <- function(x) as.data.frame(t(as.matrix(coef(x))))
expect_length(mtcar_mod %>% map_dfr(f_coef), 2)
expect_length(mtcar_mod %>% map_dfc(f_coef), 6)
})
test_that("walk is used for side-effects", {
expect_output(walk(1:3, str))
})
test_that("map_if() and map_at() always return a list", {
df <- tibble::tibble(x = 1, y = "a")
expect_identical(map_if(df, is.character, ~"out"), list(x = 1, y = "out"))
expect_identical(map_at(df, 1, ~"out"), list(x = "out", y = "a"))
})
purrr/src/ 0000755 0001762 0000144 00000000000 13171650762 012223 5 ustar ligges users purrr/src/coerce.c 0000644 0001762 0000144 00000005021 13171650762 013625 0 ustar ligges users #define R_NO_REMAP
#include
#include
#include
double logical_to_real(int x) {
return (x == NA_LOGICAL) ? NA_REAL : x;
}
double integer_to_real(int x) {
return (x == NA_INTEGER) ? NA_REAL : x;
}
SEXP logical_to_char(int x) {
if (x == NA_LOGICAL)
return NA_STRING;
return Rf_mkChar(x ? "TRUE" : "FALSE");
}
SEXP integer_to_char(int x) {
if (x == NA_INTEGER)
return NA_STRING;
char buf[100];
snprintf(buf, 100, "%d", x);
return Rf_mkChar(buf);
}
SEXP double_to_char(double x) {
if (!R_finite(x)) {
if (R_IsNA(x)) {
return NA_STRING;
} else if (R_IsNaN(x)) {
return Rf_mkChar("NaN");
} else if (x > 0) {
return Rf_mkChar("Inf");
} else {
return Rf_mkChar("-Inf");
}
}
char buf[100];
snprintf(buf, 100, "%f", x);
return Rf_mkChar(buf);
}
void cant_coerce(SEXP from, SEXP to, int i) {
Rf_errorcall(R_NilValue, "Can't coerce element %i from a %s to a %s",
i + 1, Rf_type2char(TYPEOF(from)), Rf_type2char(TYPEOF(to)));
}
void set_vector_value(SEXP to, int i, SEXP from, int j) {
switch(TYPEOF(to)) {
case LGLSXP:
switch(TYPEOF(from)) {
case LGLSXP: LOGICAL(to)[i] = LOGICAL(from)[j]; break;
default: cant_coerce(from, to, i);
}
break;
case INTSXP:
switch(TYPEOF(from)) {
case LGLSXP: INTEGER(to)[i] = LOGICAL(from)[j]; break;
case INTSXP: INTEGER(to)[i] = INTEGER(from)[j]; break;
default: cant_coerce(from, to, i);
}
break;
case REALSXP:
switch(TYPEOF(from)) {
case LGLSXP: REAL(to)[i] = logical_to_real(LOGICAL(from)[j]); break;
case INTSXP: REAL(to)[i] = integer_to_real(INTEGER(from)[j]); break;
case REALSXP: REAL(to)[i] = REAL(from)[j]; break;
default: cant_coerce(from, to, i);
}
break;
case STRSXP:
switch(TYPEOF(from)) {
case LGLSXP: SET_STRING_ELT(to, i, logical_to_char(LOGICAL(from)[j])); break;
case INTSXP: SET_STRING_ELT(to, i, integer_to_char(INTEGER(from)[j])); break;
case REALSXP: SET_STRING_ELT(to, i, double_to_char(REAL(from)[j])); break;
case STRSXP: SET_STRING_ELT(to, i, STRING_ELT(from, j)); break;
default: cant_coerce(from, to, i);
}
break;
case VECSXP:
SET_VECTOR_ELT(to, i, from);
break;
default: cant_coerce(from, to, i);
}
}
SEXP coerce_impl(SEXP x, SEXP type_) {
int n = Rf_length(x);
SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
SEXP out = PROTECT(Rf_allocVector(type, n));
for (int i = 0; i < n; ++i) {
set_vector_value(out, i, x, i);
}
UNPROTECT(1);
return out;
}
purrr/src/backports.h 0000644 0001762 0000144 00000000245 13171650762 014365 0 ustar ligges users #ifndef BACKPORTS_H
#define BACKPORTS_H
#include
#if defined(R_VERSION) && R_VERSION < R_Version(3, 2, 0)
SEXP Rf_installChar(SEXP x);
#endif
#endif
purrr/src/transpose.c 0000644 0001762 0000144 00000005530 13171650762 014410 0 ustar ligges users #define R_NO_REMAP
#include
#include
SEXP transpose_impl(SEXP x, SEXP names_template) {
if (TYPEOF(x) != VECSXP)
Rf_errorcall(R_NilValue, "`.l` is not a list (%s)", Rf_type2char(TYPEOF(x)));
int n = Rf_length(x);
if (n == 0) {
return Rf_allocVector(VECSXP, 0);
}
int has_template = !Rf_isNull(names_template);
SEXP x1 = VECTOR_ELT(x, 0);
if (!Rf_isVector(x1))
Rf_errorcall(R_NilValue, "Element 1 is not a vector (%s)", Rf_type2char(TYPEOF(x1)));
int m = has_template ? Rf_length(names_template) : Rf_length(x1);
// Create space for output
SEXP out = PROTECT(Rf_allocVector(VECSXP, m));
SEXP names1 = PROTECT(Rf_getAttrib(x, R_NamesSymbol));
for (int j = 0; j < m; ++j) {
SEXP xj = PROTECT(Rf_allocVector(VECSXP, n));
if (!Rf_isNull(names1)) {
Rf_setAttrib(xj, R_NamesSymbol, names1);
}
SET_VECTOR_ELT(out, j, xj);
UNPROTECT(1);
}
SEXP names2 = has_template ? names_template : Rf_getAttrib(x1, R_NamesSymbol);
if (!Rf_isNull(names2)) {
Rf_setAttrib(out, R_NamesSymbol, names2);
}
// Fill output
for (int i = 0; i < n; ++i) {
SEXP xi = VECTOR_ELT(x, i);
if (!Rf_isVector(xi))
Rf_errorcall(R_NilValue, "Element %i is not a vector (%s)", i + 1, Rf_type2char(TYPEOF(x1)));
// find mapping between names and index. Use -1 to indicate not found
SEXP names_i = Rf_getAttrib(xi, R_NamesSymbol);
SEXP index;
if (!Rf_isNull(names2) && !Rf_isNull(names_i)) {
index = PROTECT(Rf_match(names_i, names2, 0));
// Rf_match returns 1-based index; convert to 0-based for C
for (int i = 0; i < m; ++i) {
INTEGER(index)[i] = INTEGER(index)[i] - 1;
}
} else {
index = PROTECT(Rf_allocVector(INTSXP, m));
int mi = Rf_length(xi);
if (m != mi) {
Rf_warningcall(R_NilValue, "Element %i has length %i not %i", i + 1, mi, m);
}
for (int i = 0; i < m; ++i) {
INTEGER(index)[i] = (i < mi) ? i : -1;
}
}
int* pIndex = INTEGER(index);
for (int j = 0; j < m; ++j) {
int pos = pIndex[j];
if (pos == -1)
continue;
switch(TYPEOF(xi)) {
case LGLSXP:
SET_VECTOR_ELT(VECTOR_ELT(out, j), i, Rf_ScalarLogical(LOGICAL(xi)[pos]));
break;
case INTSXP:
SET_VECTOR_ELT(VECTOR_ELT(out, j), i, Rf_ScalarInteger(INTEGER(xi)[pos]));
break;
case REALSXP:
SET_VECTOR_ELT(VECTOR_ELT(out, j), i, Rf_ScalarReal(REAL(xi)[pos]));
break;
case STRSXP:
SET_VECTOR_ELT(VECTOR_ELT(out, j), i, Rf_ScalarString(STRING_ELT(xi, pos)));
break;
case VECSXP:
SET_VECTOR_ELT(VECTOR_ELT(out, j), i, VECTOR_ELT(xi, pos));
break;
default:
Rf_errorcall(R_NilValue, "Unsupported type %s", Rf_type2char(TYPEOF(xi)));
}
}
UNPROTECT(1);
}
UNPROTECT(2);
return out;
}
purrr/src/backports.c 0000644 0001762 0000144 00000000317 13171650762 014360 0 ustar ligges users #define R_NO_REMAP
#include
#include
#include
#if defined(R_VERSION) && R_VERSION < R_Version(3, 2, 0)
SEXP Rf_installChar(SEXP x) {
return Rf_install(CHAR(x));
}
#endif
purrr/src/flatten.c 0000644 0001762 0000144 00000007061 13171650762 014030 0 ustar ligges users #define R_NO_REMAP
#include
#include
#include "coerce.h"
const char* objtype(SEXP x) {
return Rf_type2char(TYPEOF(x));
}
SEXP flatten_impl(SEXP x) {
if (TYPEOF(x) != VECSXP)
Rf_errorcall(R_NilValue, "`.x` must be a list (%s)", objtype(x));
int m = Rf_length(x);
// Determine output size and check type
int n = 0;
int has_names = 0;
SEXP x_names = PROTECT(Rf_getAttrib(x, R_NamesSymbol));
for (int j = 0; j < m; ++j) {
SEXP x_j = VECTOR_ELT(x, j);
if (!Rf_isVector(x_j) && !Rf_isNull(x_j))
Rf_errorcall(R_NilValue, "Element %i is not a vector (%s)", j + 1, objtype(x_j));
n += Rf_length(x_j);
if (!has_names) {
if (!Rf_isNull(Rf_getAttrib(x_j, R_NamesSymbol))) {
// Sub-element is named
has_names = 1;
} else if (Rf_length(x_j) == 1 && !Rf_isNull(x_names)) {
// Element is a "scalar" and has name in parent
SEXP name = STRING_ELT(x_names, j);
if (name != NA_STRING && strcmp(CHAR(name), "") != 0)
has_names = 1;
}
}
}
SEXP out = PROTECT(Rf_allocVector(VECSXP, n));
SEXP names = PROTECT(Rf_allocVector(STRSXP, n));
if (has_names)
Rf_setAttrib(out, R_NamesSymbol, names);
int i = 0;
for (int j = 0; j < m; ++j) {
SEXP x_j = VECTOR_ELT(x, j);
int n_j = Rf_length(x_j);
SEXP names_j = PROTECT(Rf_getAttrib(x_j, R_NamesSymbol));
int has_names_j = !Rf_isNull(names_j);
for (int k = 0; k < n_j; ++k, ++i) {
switch(TYPEOF(x_j)) {
case LGLSXP: SET_VECTOR_ELT(out, i, Rf_ScalarLogical(LOGICAL(x_j)[k])); break;
case INTSXP: SET_VECTOR_ELT(out, i, Rf_ScalarInteger(INTEGER(x_j)[k])); break;
case REALSXP: SET_VECTOR_ELT(out, i, Rf_ScalarReal(REAL(x_j)[k])); break;
case STRSXP: SET_VECTOR_ELT(out, i, Rf_ScalarString(STRING_ELT(x_j, k))); break;
case VECSXP: SET_VECTOR_ELT(out, i, VECTOR_ELT(x_j, k)); break;
default:
Rf_errorcall(R_NilValue, "Unsupported type at element %i (%s)", j + 1, objtype(x_j));
}
if (has_names) {
if (has_names_j) {
SET_STRING_ELT(names, i, has_names_j ? STRING_ELT(names_j, k) : Rf_mkChar(""));
} else if (n_j == 1) {
SET_STRING_ELT(names, i, !Rf_isNull(x_names) ? STRING_ELT(x_names, j) : Rf_mkChar(""));
}
}
if (i % 1000 == 0)
R_CheckUserInterrupt();
}
UNPROTECT(1);
}
UNPROTECT(3);
return out;
}
SEXP vflatten_impl(SEXP x, SEXP type_) {
if (TYPEOF(x) != VECSXP)
Rf_errorcall(R_NilValue, "`.x` must be a list (%s)", objtype(x));
int m = Rf_length(x);
SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
// Determine output size and type
int n = 0;
int has_names = 0;
for (int j = 0; j < m; ++j) {
SEXP x_j = VECTOR_ELT(x, j);
n += Rf_length(x_j);
if (!has_names && !Rf_isNull(Rf_getAttrib(x_j, R_NamesSymbol))) {
has_names = 1;
}
}
SEXP out = PROTECT(Rf_allocVector(type, n));
SEXP names = PROTECT(Rf_allocVector(STRSXP, n));
if (has_names)
Rf_setAttrib(out, R_NamesSymbol, names);
UNPROTECT(1);
int i = 0;
for (int j = 0; j < m; ++j) {
SEXP x_j = VECTOR_ELT(x, j);
int n_j = Rf_length(x_j);
SEXP names_j = PROTECT(Rf_getAttrib(x_j, R_NamesSymbol));
int has_names_j = !Rf_isNull(names_j);
for (int k = 0; k < n_j; ++k, ++i) {
set_vector_value(out, i, x_j, k);
if (has_names)
SET_STRING_ELT(names, i, has_names_j ? STRING_ELT(names_j, k) : Rf_mkChar(""));
if (i % 1000 == 0)
R_CheckUserInterrupt();
}
UNPROTECT(1);
}
UNPROTECT(1);
return out;
}
purrr/src/map.h 0000644 0001762 0000144 00000000273 13171650762 013153 0 ustar ligges users #ifndef MAP_H
#define MAP_H
extern "C" {
SEXP map_impl(SEXP env, SEXP x_name_, SEXP f_name_, SEXP type_);
SEXP pmap_impl(SEXP env, SEXP l_name_, SEXP f_name_, SEXP type_);
}
#endif
purrr/src/map.c 0000644 0001762 0000144 00000013575 13171650762 013157 0 ustar ligges users #define R_NO_REMAP
#include
#include
#include
#include "coerce.h"
void copy_names(SEXP from, SEXP to) {
if (Rf_length(from) != Rf_length(to))
return;
SEXP names = Rf_getAttrib(from, R_NamesSymbol);
if (Rf_isNull(names))
return;
Rf_setAttrib(to, R_NamesSymbol, names);
}
// call must involve i
SEXP call_loop(SEXP env, SEXP call, int n, SEXPTYPE type, int force_args) {
// Create variable "i" and map to scalar integer
SEXP i_val = PROTECT(Rf_ScalarInteger(1));
SEXP i = Rf_install("i");
Rf_defineVar(i, i_val, env);
SEXP out = PROTECT(Rf_allocVector(type, n));
for (int i = 0; i < n; ++i) {
if (i % 1000 == 0)
R_CheckUserInterrupt();
INTEGER(i_val)[0] = i + 1;
#if defined(R_VERSION) && R_VERSION >= R_Version(3, 2, 3)
SEXP res = PROTECT(R_forceAndCall(call, force_args, env));
#else
SEXP res = PROTECT(Rf_eval(call, env));
#endif
if (type != VECSXP && Rf_length(res) != 1)
Rf_errorcall(R_NilValue, "Result %i is not a length 1 atomic vector", i + 1);
set_vector_value(out, i, res, 0);
UNPROTECT(1);
}
UNPROTECT(2);
return out;
}
SEXP map_impl(SEXP env, SEXP x_name_, SEXP f_name_, SEXP type_) {
const char* x_name = CHAR(Rf_asChar(x_name_));
const char* f_name = CHAR(Rf_asChar(f_name_));
SEXP x = Rf_install(x_name);
SEXP f = Rf_install(f_name);
SEXP i = Rf_install("i");
SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
SEXP x_val = Rf_eval(x, env);
if (Rf_isNull(x_val)) {
return Rf_allocVector(type, 0);
} else if (!Rf_isVector(x_val)) {
Rf_errorcall(R_NilValue, "`.x` is not a vector (%s)", Rf_type2char(TYPEOF(x_val)));
}
int n = Rf_length(x_val);
// Constructs a call like f(x[[i]], ...) - don't want to substitute
// actual values for f or x, because they may be long, which creates
// bad tracebacks()
SEXP Xi = PROTECT(Rf_lang3(R_Bracket2Symbol, x, i));
SEXP f_call = PROTECT(Rf_lang3(f, Xi, R_DotsSymbol));
SEXP out = PROTECT(call_loop(env, f_call, n, type, 1));
copy_names(x_val, out);
UNPROTECT(3);
return out;
}
SEXP map2_impl(SEXP env, SEXP x_name_, SEXP y_name_, SEXP f_name_, SEXP type_) {
const char* x_name = CHAR(Rf_asChar(x_name_));
const char* y_name = CHAR(Rf_asChar(y_name_));
const char* f_name = CHAR(Rf_asChar(f_name_));
SEXP x = Rf_install(x_name);
SEXP y = Rf_install(y_name);
SEXP f = Rf_install(f_name);
SEXP i = Rf_install("i");
SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
SEXP x_val = PROTECT(Rf_eval(x, env));
SEXP y_val = PROTECT(Rf_eval(y, env));
if (!Rf_isVector(x_val) && !Rf_isNull(x_val))
Rf_errorcall(R_NilValue, "`.x` is not a vector (%s)", Rf_type2char(TYPEOF(x_val)));
if (!Rf_isVector(y_val) && !Rf_isNull(y_val))
Rf_errorcall(R_NilValue, "`.y` is not a vector (%s)", Rf_type2char(TYPEOF(y_val)));
int nx = Rf_length(x_val), ny = Rf_length(y_val);
if (nx == 0 || ny == 0) {
UNPROTECT(2);
return Rf_allocVector(type, 0);
}
if (nx != ny && !(nx == 1 || ny == 1)) {
Rf_errorcall(R_NilValue, "`.x` (%i) and `.y` (%i) are different lengths", nx, ny);
}
int n = (nx > ny) ? nx : ny;
// Constructs a call like f(x[[i]], y[[i]], ...)
SEXP one = PROTECT(Rf_ScalarInteger(1));
SEXP Xi = PROTECT(Rf_lang3(R_Bracket2Symbol, x, nx == 1 ? one : i));
SEXP Yi = PROTECT(Rf_lang3(R_Bracket2Symbol, y, ny == 1 ? one : i));
SEXP f_call = PROTECT(Rf_lang4(f, Xi, Yi, R_DotsSymbol));
SEXP out = PROTECT(call_loop(env, f_call, n, type, 2));
copy_names(x_val, out);
UNPROTECT(7);
return out;
}
SEXP pmap_impl(SEXP env, SEXP l_name_, SEXP f_name_, SEXP type_) {
const char* l_name = CHAR(Rf_asChar(l_name_));
SEXP l = Rf_install(l_name);
SEXP l_val = PROTECT(Rf_eval(l, env));
SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
if (!Rf_isVectorList(l_val))
Rf_errorcall(R_NilValue, "`.x` is not a list (%s)", Rf_type2char(TYPEOF(l_val)));
// Check all elements are lists and find maximum length
int m = Rf_length(l_val);
int n = 0;
for (int j = 0; j < m; ++j) {
SEXP j_val = VECTOR_ELT(l_val, j);
if (!Rf_isVector(j_val) && !Rf_isNull(j_val)) {
Rf_errorcall(R_NilValue, "Element %i is not a vector (%s)", j + 1, Rf_type2char(TYPEOF(j_val)));
}
int nj = Rf_length(j_val);
if (nj == 0) {
UNPROTECT(1);
return Rf_allocVector(type, 0);
} else if (nj > n) {
n = nj;
}
}
// Check length of all elements
for (int j = 0; j < m; ++j) {
SEXP j_val = VECTOR_ELT(l_val, j);
int nj = Rf_length(j_val);
if (nj != 1 && nj != n)
Rf_errorcall(R_NilValue, "Element %i has length %i, not 1 or %i.", j + 1, nj, n);
}
SEXP l_names = PROTECT(Rf_getAttrib(l_val, R_NamesSymbol));
int has_names = !Rf_isNull(l_names);
const char* f_name = CHAR(Rf_asChar(f_name_));
SEXP f = Rf_install(f_name);
SEXP i = Rf_install("i");
SEXP one = PROTECT(Rf_ScalarInteger(1));
// Construct call like f(.x[[c(1, i)]], .x[[c(2, i)]], ...)
// We construct the call backwards because can only add to the front of a
// linked list. That makes PROTECTion tricky because we need to update it
// each time to point to the start of the linked list.
SEXP f_call = Rf_lang1(R_DotsSymbol);
PROTECT_INDEX fi;
PROTECT_WITH_INDEX(f_call, &fi);
for (int j = m - 1; j >= 0; --j) {
int nj = Rf_length(VECTOR_ELT(l_val, j));
// Construct call like .l[[c(j, i)]]
SEXP j_ = PROTECT(Rf_ScalarInteger(j + 1));
SEXP ji_ = PROTECT(Rf_lang3(Rf_install("c"), j_, nj == 1 ? one : i));
SEXP l_ji = PROTECT(Rf_lang3(R_Bracket2Symbol, l, ji_));
REPROTECT(f_call = Rf_lcons(l_ji, f_call), fi);
if (has_names && CHAR(STRING_ELT(l_names, j))[0] != '\0')
SET_TAG(f_call, Rf_install(CHAR(STRING_ELT(l_names, j))));
UNPROTECT(3);
}
REPROTECT(f_call = Rf_lcons(f, f_call), fi);
SEXP out = PROTECT(call_loop(env, f_call, n, type, m));
copy_names(VECTOR_ELT(l_val, 0), out);
UNPROTECT(5);
return out;
}
purrr/src/init.c 0000644 0001762 0000144 00000002105 13171650762 013330 0 ustar ligges users #include
#include
#include // for NULL
#include
/* .Call calls */
extern SEXP coerce_impl(SEXP, SEXP);
extern SEXP extract_impl(SEXP, SEXP, SEXP);
extern SEXP flatten_impl(SEXP);
extern SEXP map_impl(SEXP, SEXP, SEXP, SEXP);
extern SEXP map2_impl(SEXP, SEXP, SEXP, SEXP, SEXP);
extern SEXP pmap_impl(SEXP, SEXP, SEXP, SEXP);
extern SEXP transpose_impl(SEXP, SEXP);
extern SEXP vflatten_impl(SEXP, SEXP);
static const R_CallMethodDef CallEntries[] = {
{"coerce_impl", (DL_FUNC) &coerce_impl, 2},
{"extract_impl", (DL_FUNC) &extract_impl, 3},
{"flatten_impl", (DL_FUNC) &flatten_impl, 1},
{"map_impl", (DL_FUNC) &map_impl, 4},
{"map2_impl", (DL_FUNC) &map2_impl, 5},
{"pmap_impl", (DL_FUNC) &pmap_impl, 4},
{"transpose_impl", (DL_FUNC) &transpose_impl, 2},
{"vflatten_impl", (DL_FUNC) &vflatten_impl, 2},
{NULL, NULL, 0}
};
void R_init_purrr(DllInfo *dll)
{
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
}
purrr/src/coerce.h 0000644 0001762 0000144 00000000251 13171650762 013632 0 ustar ligges users #ifndef UTILS_H
#define UTILS_H
// Set value of to[i] to from[j], coercing vectors using usual rules.
void set_vector_value(SEXP to, int i, SEXP from, int j);
#endif
purrr/src/extract.c 0000644 0001762 0000144 00000007567 13171650762 014060 0 ustar ligges users #define R_NO_REMAP
#include
#include
#include "coerce.h"
#include "backports.h"
#include
int find_offset(SEXP x, SEXP index, int i) {
if (Rf_length(index) > 1) {
Rf_errorcall(R_NilValue, "Index %i must have length 1", i + 1);
}
int n = Rf_length(x);
if (n == 0)
return -1;
if (TYPEOF(index) == INTSXP) {
int val = INTEGER(index)[0];
if (val == NA_INTEGER)
return -1;
val--;
if (val < 0 || val >= n)
return -1;
return val;
} if (TYPEOF(index) == REALSXP) {
double val = REAL(index)[0];
if (!R_finite(val))
return -1;
val--;
if (val < 0 || val >= n)
return -1;
return val;
} else if (TYPEOF(index) == STRSXP) {
SEXP names = PROTECT(Rf_getAttrib(x, R_NamesSymbol));
if (names == R_NilValue) {// vector doesn't have names
UNPROTECT(1);
return -1;
}
if (STRING_ELT(index, 0) == NA_STRING) {
UNPROTECT(1);
return -1;
}
const char* val = Rf_translateCharUTF8(STRING_ELT(index, 0));
if (val[0] == '\0') { // "" matches nothing
UNPROTECT(1);
return -1;
}
for (int j = 0; j < Rf_length(names); ++j) {
if (STRING_ELT(names, j) == NA_STRING)
continue;
const char* names_j = Rf_translateCharUTF8(STRING_ELT(names, j));
if (strcmp(names_j, val) == 0) {
UNPROTECT(1);
return j;
}
}
UNPROTECT(1);
return -1;
} else {
Rf_errorcall(
R_NilValue,
"Index %i must be a character or numeric vector", i + 1
);
}
}
SEXP extract_vector(SEXP x, SEXP index_i, int i) {
int offset = find_offset(x, index_i, i);
if (offset < 0)
return R_NilValue;
switch(TYPEOF(x)) {
case LGLSXP: return Rf_ScalarLogical(LOGICAL(x)[offset]);
case INTSXP: return Rf_ScalarInteger(INTEGER(x)[offset]);
case REALSXP: return Rf_ScalarReal(REAL(x)[offset]);
case STRSXP: return Rf_ScalarString(STRING_ELT(x, offset));
case VECSXP: return VECTOR_ELT(x, offset);
default:
Rf_errorcall(R_NilValue,
"Don't know how to index object of type %s at level %i",
Rf_type2char(TYPEOF(x)), i + 1
);
}
return R_NilValue;
}
SEXP extract_env(SEXP x, SEXP index_i, int i) {
if (TYPEOF(index_i) != STRSXP || Rf_length(index_i) != 1) {
Rf_errorcall(R_NilValue, "Index %i is not a string", i + 1);
}
SEXP index = STRING_ELT(index_i, 0);
if (index == NA_STRING)
return R_NilValue;
SEXP sym = Rf_installChar(index);
SEXP out = Rf_findVarInFrame3(x, sym, TRUE);
return (out == R_UnboundValue) ? R_NilValue : out;
}
SEXP extract_attr(SEXP x, SEXP index_i, int i) {
if (TYPEOF(index_i) != STRSXP || Rf_length(index_i) != 1) {
Rf_errorcall(R_NilValue, "Index %i is not a string", i + 1);
}
SEXP index = STRING_ELT(index_i, 0);
if (index == NA_STRING)
return R_NilValue;
SEXP sym = Rf_installChar(index);
return Rf_getAttrib(x, sym);
}
SEXP extract_clo(SEXP x, SEXP clo) {
SEXP expr = PROTECT(Rf_lang2(clo, x));
SEXP out = Rf_eval(expr, R_EmptyEnv);
UNPROTECT(1);
return out;
}
SEXP extract_impl(SEXP x, SEXP index, SEXP missing) {
if (TYPEOF(index) != VECSXP) {
Rf_errorcall(R_NilValue, "`index` must be a list (not a %s)",
Rf_type2char(TYPEOF(index)));
}
int n = Rf_length(index);
for (int i = 0; i < n; ++i) {
SEXP index_i = VECTOR_ELT(index, i);
if (TYPEOF(index_i) == CLOSXP) {
x = extract_clo(x, index_i);
} else {
if (Rf_isNull(x)) {
return missing;
} else if (Rf_isVector(x)) {
x = extract_vector(x, index_i, i);
} else if (Rf_isEnvironment(x)) {
x = extract_env(x, index_i, i);
} else if (Rf_isS4(x)) {
x = extract_attr(x, index_i, i);
} else {
Rf_errorcall(R_NilValue,
"Don't know how to pluck from a %s", Rf_type2char(TYPEOF(x))
);
}
}
}
return (Rf_length(x) == 0) ? missing : x;
}
purrr/NAMESPACE 0000644 0001762 0000144 00000006034 13137651733 012657 0 ustar ligges users # Generated by roxygen2: do not edit by hand
S3method(as_mapper,character)
S3method(as_mapper,default)
S3method(as_mapper,list)
S3method(as_mapper,numeric)
S3method(modify,default)
S3method(modify_at,default)
S3method(modify_depth,default)
S3method(modify_if,default)
export("%>%")
export("%@%")
export("%||%")
export(accumulate)
export(accumulate_right)
export(array_branch)
export(array_tree)
export(as_function)
export(as_mapper)
export(as_vector)
export(at_depth)
export(attr_getter)
export(auto_browse)
export(compact)
export(compose)
export(cross)
export(cross2)
export(cross3)
export(cross_d)
export(cross_df)
export(cross_n)
export(detect)
export(detect_index)
export(discard)
export(every)
export(flatten)
export(flatten_chr)
export(flatten_dbl)
export(flatten_df)
export(flatten_dfc)
export(flatten_dfr)
export(flatten_int)
export(flatten_lgl)
export(has_element)
export(head_while)
export(imap)
export(imap_chr)
export(imap_dbl)
export(imap_dfc)
export(imap_dfr)
export(imap_int)
export(imap_lgl)
export(invoke)
export(invoke_map)
export(invoke_map_chr)
export(invoke_map_dbl)
export(invoke_map_df)
export(invoke_map_dfc)
export(invoke_map_dfr)
export(invoke_map_int)
export(invoke_map_lgl)
export(is_atomic)
export(is_bare_atomic)
export(is_bare_character)
export(is_bare_double)
export(is_bare_integer)
export(is_bare_list)
export(is_bare_logical)
export(is_bare_numeric)
export(is_bare_vector)
export(is_character)
export(is_double)
export(is_empty)
export(is_formula)
export(is_function)
export(is_integer)
export(is_list)
export(is_logical)
export(is_null)
export(is_numeric)
export(is_scalar_atomic)
export(is_scalar_character)
export(is_scalar_double)
export(is_scalar_integer)
export(is_scalar_list)
export(is_scalar_logical)
export(is_scalar_numeric)
export(is_scalar_vector)
export(is_vector)
export(iwalk)
export(keep)
export(lift)
export(lift_dl)
export(lift_dv)
export(lift_ld)
export(lift_lv)
export(lift_vd)
export(lift_vl)
export(list_along)
export(list_merge)
export(list_modify)
export(lmap)
export(lmap_at)
export(lmap_if)
export(map)
export(map2)
export(map2_chr)
export(map2_dbl)
export(map2_df)
export(map2_dfc)
export(map2_dfr)
export(map2_int)
export(map2_lgl)
export(map_at)
export(map_call)
export(map_chr)
export(map_dbl)
export(map_df)
export(map_dfc)
export(map_dfr)
export(map_if)
export(map_int)
export(map_lgl)
export(modify)
export(modify_at)
export(modify_depth)
export(modify_if)
export(negate)
export(partial)
export(pluck)
export(pmap)
export(pmap_chr)
export(pmap_dbl)
export(pmap_df)
export(pmap_dfc)
export(pmap_dfr)
export(pmap_int)
export(pmap_lgl)
export(possibly)
export(prepend)
export(pwalk)
export(quietly)
export(rbernoulli)
export(rdunif)
export(reduce)
export(reduce2)
export(reduce2_right)
export(reduce_right)
export(rep_along)
export(rerun)
export(safely)
export(set_names)
export(simplify)
export(simplify_all)
export(some)
export(splice)
export(tail_while)
export(transpose)
export(update_list)
export(vec_depth)
export(walk)
export(walk2)
export(when)
import(rlang)
importFrom(magrittr,"%>%")
useDynLib(purrr, .registration = TRUE)
purrr/NEWS.md 0000644 0001762 0000144 00000030066 13171650736 012540 0 ustar ligges users # purrr 0.2.4
* Fixes for R 3.1.
* Fix PROTECTion problems in C code revealed by rchk.
# purrr 0.2.3
## Breaking changes
We noticed the following issues during reverse dependencies checks:
* If `reduce()` fails with this message: ``Error: `.x` is empty, and
no `.init` supplied``, this is because `reduce()` now returns
`.init` when `.x` is empty. Fix the problem by supplying an
appropriate argument to `.init`, or by providing special behaviour
when `.x` has length 0.
* The type predicates have been migrated to rlang. Consequently the
`bare-type-predicates` documentation topic is no longer in purrr,
which might cause a warning if you cross-reference it.
## Dependencies
purrr no longer depends on lazyeval or Rcpp (or dplyr, as of the
previous version). This makes the dependency graph of the tidyverse
simpler, and makes purrr more suitable as a dependency of lower-level
packages.
There have also been two changes to eliminate name conflicts between
purrr and dplyr:
* `order_by()`, `sort_by()` and `split_by()` have been removed. `order_by()`
conflicted with `dplyr::order_by()` and the complete family doesn't feel that
useful. Use tibbles instead (#217).
* `contains()` has been renamed to `has_element()` to avoid conflicts with
dplyr (#217).
## pluck()
The plucking mechanism used for indexing into data structures with
`map()` has been extracted into the function `pluck()`. Plucking is
often more readable to extract an element buried in a deep data
structure. Compare this syntax-heavy extraction which reads
non-linearly:
```
accessor(x[[1]])$foo
```
to the equivalent pluck:
```
x %>% pluck(1, accessor, "foo")
```
## Map helpers
* `as_function()` is now `as_mapper()` because it is a tranformation that
makes sense primarily for mapping functions, not in general (#298).
`.null` has been renamed to `.default` to better reflect its intent (#298).
`.default` is returned whenever an element is absent or empty (#231, #254).
`as_mapper()` sanitises primitive functions by transforming them to
closures with standardised argument names (using `rlang::as_closure()`).
For instance `+` is transformed to `function(.x, .y) .x + .y`. This
results in proper argument matching so that `map(1:10, partial(`-`,
.x = 5))` produces `list(5 - 1, 5 - 2, ...)`.
* Recursive indexing can now extract objects out of environments (#213) and
S4 objects (#200), as well as lists.
* `attr_getter()` makes it possible to extract from attributes
like `map(list(iris, mtcars), attr_getter("row.names"))`.
* The argument list for formula-functions has been tweaked so that you can
refer to arguments by position with `..1`, `..2`, and so on. This makes it
possible to use the formula shorthand for functions with more than two
arguments (#289).
* `possibly()`, `safely()` and friends no longer capture interrupts: this
means that you can now terminate a mapper using one of these with
Escape or Ctrl + C (#314)
## Map functions
* All map functions now treat `NULL` the same way as an empty vector (#199),
and return an empty vector if any input is an empty vector.
* All `map()` functions now force their arguments in the same way that base R
does for `lapply()` (#191). This makes `map()` etc easier to use when
generating functions.
* A new family of "indexed" map functions, `imap()`, `imap_lgl()` etc,
provide a short-hand for `map2(x, names(x))` or `map2(x, seq_along(x))`
(#240).
* The data frame suffix `_df` has been (soft) deprecated in favour of
`_dfr` to more clearly indicate that it's a row-bind. All variants now
also have a `_dfc` for column binding (#167). (These will not be terribly
useful until `dplyr::bind_rows()`/`dplyr::bind_cols()` have better
semantics for vectors.)
## Modify functions
A new `modify()` family returns the same output of the type as the
input `.x`. This is in contrast to the `map()` family which always
returns a list, regardless of the input type.
The modify functions are S3 generics. However their default methods
should be sufficient for most classes since they rely on the semantics
of `[<-`. `modify.default()` is thus a shorthand for `x[] <- map(x, f)`.
* `at_depth()` has been renamed to `modify_depth()`.
* `modify_depth()` gains new `.ragged` argument, and negative depths are
now computed relative to the deepest component of the list (#236).
## New functions
* `auto_browse(f)` returns a new function that automatically calls `browser()`
if `f` throws an error (#281).
* `vec_depth()` computes the depth (i.e. the number of levels of indexing)
or a vector (#243).
* `reduce2()` and `reduce2_right()` make it possible to reduce with a
3 argument function where the first argument is the accumulated value, the
second argument is `.x`, and the third argument is `.y` (#163).
* `list_modify()` extends `stats::modifyList()` to replace by position
if the list is not named.(#201). `list_merge()` operates similarly
to `list_modify()` but combines instead of replacing (#322).
* The legacy function `update_list()` is basically a version of
`list_modify` that evaluates formulas within the list. It is likely
to be deprecated in the future in favour of a tidyeval interface
such as a list method for `dplyr::mutate()`.
## Minor improvements and bug fixes
* Thanks to @dchiu911, the unit test coverage of purrr is now much greater.
* All predicate functions are re-exported from rlang (#124).
* `compact()` now works with standard mapper conventions (#282).
* `cross_n()` has been renamed to `cross()`. The `_n` suffix was
removed for consistency with `pmap()` (originally called `map_n()`
at the start of the project) and `transpose()` (originally called
`zip_n()`). Similarly, `cross_d()` has been renamed to `cross_df()`
for consistency with `map_df()`.
* `every()` and `some()` now return `NA` if present in the input (#174).
* `invoke()` uses a more robust approach to generate the argument list (#249)
It no longer uses lazyeval to figure out which enviroment a character `f`
comes from.
* `is_numeric()` and `is_scalar_numeric()` are deprecated because they
don't test for what you might expect at first sight.
* `reduce()` now throws an error if `.x` is empty and `.init` is not
supplied.
* Deprecated functions `flatmap()`, `map3()`, `map_n()`, `walk3()`,
`walk_n()`, `zip2()`, `zip3()`, `zip_n()` have been removed.
* `pmap()` coerces data frames to lists to avoid the expensive `[.data.frame`
which provides security that is unneeded here (#220).
* `rdunif()` checks its inputs for validity (#211).
* `set_names()` can now take a function to tranform the names programmatically
(#276), and you can supply names in `...` to reduce typing even more
more (#316). `set_names()` is now powered by `rlang::set_names()`.
* `safely()` now actually uses the `quiet` argument (#296).
* `transpose()` now matches by name if available (#164). You can
override the default choice with the new `.names` argument.
* The function argument of `detect()` and `detect_index()` have been
renamed from `.p` to `.f`. This is because they have mapper
semantics rather than predicate semantics.
# purrr 0.2.2.1
This is a compatibility release with dplyr 0.6.0.
* All data-frame based mappers have been removed in favour of new
functions and idioms in the tidyverse. `dmap()`, `dmap_at()`,
`dmap_if()`, `invoke_rows()`, `slice_rows()`, `map_rows()`,
`by_slice()`, `by_row()`, and `unslice()` have been moved to
purrrlyr. This is a bit of an aggresive change but it allows us to
make the dependencies much lighter.
# purrr 0.2.2
* Fix for dev tibble support.
* `as_function()` now supports list arguments which allow recursive indexing
using either names or positions. They now always stop when encountering
the first NULL (#173).
* `accumulate` and `reduce` correctly pass extra arguments to the
worker function.
# purrr 0.2.1
* `as_function()` gains a `.null` argument that for character and numeric
values allows you to specify what to return for null/absent elements (#110).
This can be used with any map function, e.g. `map_int(x, 1, .null = NA)`
* `as_function()` is now generic.
* New `is_function()` that returns `TRUE` only for regular functions.
* Fix crash on GCC triggered by `invoke_rows()`.
# purrr 0.2.0
## New functions
* There are two handy infix functions:
* `x %||% y` is shorthand for `if (is.null(x)) y else x` (#109).
* `x %@% "a"` is shorthand for `attr(x, "a", exact = TRUE)` (#69).
* `accumulate()` has been added to handle recursive folding. It is shortand
for `Reduce(f, .x, accumulate = TRUE)` and follows a similar syntax to
`reduce()` (#145). A right-hand version `accumulate_right()` was also added.
* `map_df()` row-binds output together. It's the equivalent of `plyr::ldply()`
(#127)
* `flatten()` is now type-stable and always returns a list. To return a simpler
vector, use `flatten_lgl()`, `flatten_int()`, `flatten_dbl()`,
`flatten_chr()`, or `flatten_df()`.
* `invoke()` has been overhauled to be more useful: it now works similarly
to `map_call()` when `.x` is NULL, and hence `map_call()` has been
deprecated. `invoke_map()` is a vectorised complement to `invoke()` (#125),
and comes with typed variants `invoke_map_lgl()`, `invoke_map_int()`,
`invoke_map_dbl()`, `invoke_map_chr()`, and `invoke_map_df()`.
* `transpose()` replaces `zip2()`, `zip3()`, and `zip_n()` (#128).
The name more clearly reflects the intent (transposing the first and second
levels of list). It no longer has fields argument or the `.simplify` argument;
instead use the new `simplify_all()` function.
* `safely()`, `quietly()`, and `possibly()` are experimental functions
for working with functions with side-effects (e.g. printed output,
messages, warnings, and errors) (#120). `safely()` is a version of `try()`
that modifies a function (rather than an expression), and always returns a
list with two components, `result` and `error`.
* `list_along()` and `rep_along()` generalise the idea of `seq_along()`.
(#122).
* `is_null()` is the snake-case version of `is.null()`.
* `pmap()` (parallel map) replaces `map_n()` (#132), and has typed-variants
suffixed `pmap_lgl()`, `pmap_int()`, `pmap_dbl()`, `pmap_chr()`, and
`pmap_df()`.
* `set_names()` is a snake-case alternative to `setNames()` with stricter
equality checking, and more convenient defaults for pipes:
`x %>% set_names()` is equivalent to `setNames(x, x)` (#119).
## Row based functionals
We are still figuring out what belongs in dplyr and what belongs in
purrr. Expect much experimentation and many changes with these
functions.
* `map()` now always returns a list. Data frame support has been moved
to `map_df()` and `dmap()`. The latter supports sliced data frames
as a shortcut for the combination of `by_slice()` and `dmap()`:
`x %>% by_slice(dmap, fun, .collate = "rows")`. The conditional
variants `dmap_at()` and `dmap_if()` also support sliced data frames
and will recycle scalar results to the slice size.
* `map_rows()` has been renamed to `invoke_rows()`. As other
rows-based functionals, it collates results inside lists by default,
but with column collation this function is equivalent to
`plyr::mdply()`.
* The rows-based functionals gain a `.to` option to name the output
column as well as a `.collate` argument. The latter allows to
collate the output in lists (by default), on columns or on
rows. This makes these functions more flexible and more predictable.
## Bug fixes and minor changes
* `as_function()`, which converts formulas etc to functions, is now
exported (#123).
* `rerun()` is correctly scoped (#95)
* `update_list()` can now modify an element called `x` (#98).
* `map*()` now use custom C code, rather than relying on `lapply()`, `mapply()`
etc. The performance characteristcs are very similar, but it allows us greater
control over the output (#118).
* `map_lgl()` now has second argument `.f`, not `.p` (#134).
## Deprecated functions
* `flatmap()` -> use `map()` followed by the appropriate `flatten()`.
* `map_call()` -> `invoke()`.
* `map_n()` -> `pmap()`; `walk_n()` -> `pwalk()`.
* `map3(x, y, z)` -> `map_n(list(x, y, z))`; `walk3(x, y, z) -> `pwalk(list(x, y, z))`
purrr/R/ 0000755 0001762 0000144 00000000000 13171452210 011621 5 ustar ligges users purrr/R/coerce.R 0000644 0001762 0000144 00000000475 13102444774 013224 0 ustar ligges users # Used internally by map and flatten.
# Exposed here for testing
coerce <- function(x, type) {
.Call(coerce_impl, x, type)
}
coerce_lgl <- function(x) coerce(x, "logical")
coerce_int <- function(x) coerce(x, "integer")
coerce_dbl <- function(x) coerce(x, "double")
coerce_chr <- function(x) coerce(x, "character")
purrr/R/modify.R 0000644 0001762 0000144 00000013074 13137651733 013255 0 ustar ligges users #' Modify elements selectively
#'
#' `modify()` is a short-cut for `x[] <- map(x, .f); return(x)`. `modify_if()`
#' only modifies the elements of `x` that satisfy a predicate and leaves the
#' others unchanged. `modify_at()` only modifies elements given by names or
#' positions. `modify_depth()` only modifies elements at a given level of a
#' nested data structure.
#'
#' Since the transformation can alter the structure of the input; it's
#' your responsibility to ensure that the transformation produces a
#' valid output. For example, if you're modifying a data frame, `.f`
#' must preserve the length of the input.
#'
#'
#' @section Genericity:
#'
#' All these functions are S3 generic. However, the default method is
#' sufficient in many cases. It should be suitable for any data type
#' that implements the subset-assignment method `[<-`.
#'
#' In some cases it may make sense to provide a custom implementation
#' with a method suited to your S3 class. For example, a `grouped_df`
#' method might take into account the grouped nature of a data frame.
#'
#' @inheritParams map
#' @param .depth Level of `.x` to map on. Use a negative value to count up
#' from the lowest level of the list.
#'
#' * `modify_depth(x, 0, fun)` is equivalent to `x[] <- fun(x)`
#' * `modify_depth(x, 1, fun)` is equivalent to `x[] <- map(x, fun)`
#' * `modify_depth(x, 2, fun)` is equivalent to `x[] <- map(x, ~ map(., fun))`
#' @return An object the same class as `.x`
#' @family map variants
#' @export
#' @examples
#' # Convert factors to characters
#' iris %>%
#' modify_if(is.factor, as.character) %>%
#' str()
#'
#' # Specify which columns to map with a numeric vector of positions:
#' mtcars %>% modify_at(c(1, 4, 5), as.character) %>% str()
#'
#' # Or with a vector of names:
#' mtcars %>% modify_at(c("cyl", "am"), as.character) %>% str()
#'
#' list(x = rbernoulli(100), y = 1:100) %>%
#' transpose() %>%
#' modify_if("x", ~ update_list(., y = ~ y * 100)) %>%
#' transpose() %>%
#' simplify_all()
#'
#' # Modify at specified depth ---------------------------
#' l1 <- list(
#' obj1 = list(
#' prop1 = list(param1 = 1:2, param2 = 3:4),
#' prop2 = list(param1 = 5:6, param2 = 7:8)
#' ),
#' obj2 = list(
#' prop1 = list(param1 = 9:10, param2 = 11:12),
#' prop2 = list(param1 = 12:14, param2 = 15:17)
#' )
#' )
#'
#' # In the above list, "obj" is level 1, "prop" is level 2 and "param"
#' # is level 3. To apply sum() on all params, we map it at depth 3:
#' l1 %>% modify_depth(3, sum) %>% str()
#'
#' # modify() lets us pluck the elements prop1/param2 in obj1 and obj2:
#' l1 %>% modify(c("prop1", "param2")) %>% str()
#'
#' # But what if we want to pluck all param2 elements? Then we need to
#' # act at a lower level:
#' l1 %>% modify_depth(2, "param2") %>% str()
#'
#' # modify_depth() can be with other purrr functions to make them operate at
#' # a lower level. Here we ask pmap() to map paste() simultaneously over all
#' # elements of the objects at the second level. paste() is effectively
#' # mapped at level 3.
#' l1 %>% modify_depth(2, ~ pmap(., paste, sep = " / ")) %>% str()
modify <- function(.x, .f, ...) {
UseMethod("modify")
}
#' @rdname modify
#' @export
modify.default <- function(.x, .f, ...) {
.x[] <- map(.x, .f, ...)
.x
}
#' @rdname modify
#' @export
modify_if <- function(.x, .p, .f, ...) {
UseMethod("modify_if")
}
#' @rdname modify
#' @export
modify_if.default <- function(.x, .p, .f, ...) {
sel <- probe(.x, .p)
.x[sel] <- map(.x[sel], .f, ...)
.x
}
#' @rdname modify
#' @export
modify_at <- function(.x, .at, .f, ...) {
UseMethod("modify_at")
}
#' @rdname modify
#' @export
modify_at.default <- function(.x, .at, .f, ...) {
sel <- inv_which(.x, .at)
.x[sel] <- map(.x[sel], .f, ...)
.x
}
#' @rdname modify
#' @export
#' @param .ragged If `TRUE`, will apply to leaves, even if they're not
#' at depth `.depth`. If `FALSE`, will throw an error if there are
#' no elements at depth `.depth`.
modify_depth <- function(.x, .depth, .f, ..., .ragged = .depth < 0) {
UseMethod("modify_depth")
}
#' @rdname modify
#' @export
modify_depth.default <- function(.x, .depth, .f, ..., .ragged = .depth < 0) {
stopifnot(is_integerish(.depth, n = 1))
if (.depth < 0) {
.depth <- vec_depth(.x) + .depth
}
.f <- as_mapper(.f, ...)
modify_depth_rec(.x, .depth, .f, ..., .ragged = .ragged)
}
modify_depth_rec <- function(.x, .depth, .f, ..., .ragged = FALSE) {
if (.depth == 0) {
.x[] <- .f(.x, ...)
} else if (.depth == 1) {
if (!is.list(.x)) {
if (.ragged) {
.x[] <- .f(.x, ...)
} else {
stop("List not deep enough", call. = FALSE)
}
} else {
.x[] <- map(.x, .f, ...)
}
} else if (.depth > 1) {
.x[] <- map(.x, function(x) {
modify_depth_rec(x, .depth - 1, .f, ..., .ragged = .ragged)
})
} else {
stop("Invalid `depth`", call. = FALSE)
}
.x
}
#' @export
#' @usage NULL
#' @rdname modify
at_depth <- function(.x, .depth, .f, ...) {
warning(
"at_depth() is deprecated, please use `modify_depth()` instead",
call. = FALSE
)
modify_depth(.x, .depth, .f, ...)
}
# Internal version of map_lgl() that works with logical vectors
probe <- function(.x, .p, ...) {
if (is_logical(.p)) {
stopifnot(length(.p) == length(.x))
.p
} else {
map_lgl(.x, .p, ...)
}
}
inv_which <- function(x, sel) {
if (is.character(sel)) {
names <- names(x)
if (is.null(names)) {
stop("character indexing requires a named object", call. = FALSE)
}
names %in% sel
} else if (is.numeric(sel)) {
seq_along(x) %in% sel
} else {
stop("unrecognised index type", call. = FALSE)
}
}
purrr/R/along.R 0000644 0001762 0000144 00000001133 13171415127 013050 0 ustar ligges users #' Helper to create vectors with matching length.
#'
#' These functions take the idea of [seq_along()] and generalise
#' it to creating lists (`list_along`) and repeating values
#' (`rep_along`).
#'
#' @param x A vector.
#' @param y Values to repeat.
#' @return A vector of the same length as `x`.
#' @keywords internal
#' @examples
#' x <- 1:5
#' rep_along(x, 1:2)
#' rep_along(x, 1)
#' list_along(x)
#' @name along
NULL
#' @export
#' @rdname along
list_along <- function(x) {
vector("list", length(x))
}
#' @export
#' @rdname along
rep_along <- function(x, y) {
rep(y, length.out = length(x))
}
purrr/R/as_mapper.R 0000644 0001762 0000144 00000014640 13171451356 013732 0 ustar ligges users #' Convert an object into a mapper function
#'
#' `as_mapper` is the powerhouse behind the varied function
#' specifications that most purrr functions allow. It is an S3
#' generic. The default method forwards its arguments to
#' [rlang::as_function()].
#'
#' @param .f A function, formula, or atomic vector.
#'
#' If a __function__, it is used as is.
#'
#' If a __formula__, e.g. `~ .x + 2`, it is converted to a function. There
#' are three ways to refer to the arguments:
#'
#' * For a single argument function, use `.`
#' * For a two argument function, use `.x` and `.y`
#' * For more arguments, use `..1`, `..2`, `..3` etc
#'
#' This syntax allows you to create very compact anonymous functions.
#'
#' If __character vector__, __numeric vector__, or __list__, it
#' is converted to an extractor function. Character vectors index by name
#' and numeric vectors index by position; use a list to index by position
#' and name at different levels. Within a list, wrap strings in [get-attr()]
#' to extract named attributes. If a component is not present, the value of
#' `.default` will be returned.
#' @param .default,.null Optional additional argument for extractor functions
#' (i.e. when `.f` is character, integer, or list). Returned when
#' value is absent (does not exist) or empty (has length 0).
#' `.null` is deprecated; please use `.default` instead.
#' @param ... Additional arguments passed on to methods.
#' @export
#' @examples
#' as_mapper(~ . + 1)
#' as_mapper(1)
#'
#' as_mapper(c("a", "b", "c"))
#' # Equivalent to function(x) x[["a"]][["b"]][["c"]]
#'
#' as_mapper(list(1, "a", 2))
#' # Equivalent to function(x) x[[1]][["a"]][[2]]
#'
#' as_mapper(list(1, attr_getter("a")))
#' # Equivalent to function(x) attr(x[[1]], "a")
#'
#' as_mapper(c("a", "b", "c"), .null = NA)
as_mapper <- function(.f, ...) {
UseMethod("as_mapper")
}
#' @export
#' @rdname as_mapper
#' @usage NULL
as_function <- function(...) {
warning(
"`as_function()` is deprecated; please use `as_mapper()` or `rlang::as_function()` instead",
call. = FALSE
)
as_mapper(...)
}
#' Pluck out a single an element from a vector or environment
#'
#' @description
#'
#' This is a generalised form of `[[` which allows you to index deeply
#' and flexibly into data structures. It supports R standard accessors
#' like integer positions and string names, and also accepts arbitrary
#' accessor functions, i.e. functions that take an object and return
#' some internal piece.
#'
#' `pluck()` is often more readable than a mix of operators and
#' accessors because it reads linearly and is free of syntactic
#' cruft. Compare: \code{accessor(x[[1]])$foo} to `pluck(x, 1,
#' accessor, "foo")`.
#'
#' Furthermore, `pluck()` never partial-matches unlike `$` which will
#' select the `disp` object if you write `mtcars$di`.
#'
#' @details
#'
#' Since it handles arbitrary accessor functions, `pluck()` is a type
#' of composition operator. However, it is indexing-oriented thanks to
#' its handling of strings and integers. By the same token is also
#' explicit regarding the intent of the composition (e.g. extraction).
#'
#' @param .x A vector or environment
#' @param ... A list of accessors for indexing into the object. Can be
#' an integer position, a string name, or an accessor function. If
#' the object being indexed is an S4 object, accessing it by name
#' will return the corresponding slot.
#'
#' These dots [splice lists automatically][rlang::dots_splice]. This
#' means you can supply arguments and lists of arguments
#' indistinctly.
#' @param .default Value to use if target is empty or absent.
#' @keywords internal
#' @export
#' @examples
#' # pluck() supports integer positions, string names, and functions.
#' # Using functions, you can easily extend pluck(). Let's create a
#' # list of data structures:
#' obj1 <- list("a", list(1, elt = "foobar"))
#' obj2 <- list("b", list(2, elt = "foobaz"))
#' x <- list(obj1, obj2)
#'
#' # And now an accessor for these complex data structures:
#' my_element <- function(x) x[[2]]$elt
#'
#' # The accessor can then be passed to pluck:
#' pluck(x, 1, my_element)
#' pluck(x, 2, my_element)
#'
#' # Even for this simple data structure, this is more readable than
#' # the alternative form because it requires you to read both from
#' # right-to-left and from left-to-right in different parts of the
#' # expression:
#' my_element(x[[1]])
#'
#'
#' # This technique is used for plucking into attributes with
#' # attr_getter(). It takes an attribute name and returns a function
#' # to access the attribute:
#' obj1 <- structure("obj", obj_attr = "foo")
#' obj2 <- structure("obj", obj_attr = "bar")
#' x <- list(obj1, obj2)
#'
#' # pluck() is handy for extracting deeply into a data structure.
#' # Here we'll first extract by position, then by attribute:
#' pluck(x, 1, attr_getter("obj_attr")) # From first object
#' pluck(x, 2, attr_getter("obj_attr")) # From second object
#'
#'
#' # pluck() splices lists of arguments automatically. The following
#' # pluck is equivalent to the one above:
#' idx <- list(1, attr_getter("obj_attr"))
#' pluck(x, idx)
pluck <- function(.x, ..., .default = NULL) {
.Call(extract_impl, .x, dots_splice(...), .default)
}
#' @export
#' @rdname pluck
#' @param attr An attribute name as string.
attr_getter <- function(attr) {
force(attr)
function(x) attr(x, attr)
}
# Vectors -----------------------------------------------------------------
#' @export
#' @rdname as_mapper
as_mapper.character <- function(.f, ..., .null, .default = NULL) {
.default <- find_extract_default(.null, .default)
plucker(as.list(.f), .default)
}
#' @export
#' @rdname as_mapper
as_mapper.numeric <- function(.f, ..., .null, .default = NULL) {
.default <- find_extract_default(.null, .default)
plucker(as.list(.f), .default)
}
#' @export
#' @rdname as_mapper
as_mapper.list <- function(.f, ..., .null, .default = NULL) {
.default <- find_extract_default(.null, .default)
plucker(.f, .default)
}
find_extract_default <- function(.null, .default) {
if (!missing(.null)) {
# warning("`.null` is deprecated; please use `.default` instead", call. = FALSE)
.null
} else {
.default
}
}
plucker <- function(i, default) {
# Interpolation creates a closure with a more readable source
expr_interp(function(x, ...)
pluck(x, !! i, .default = !! default)
)
}
# Default -----------------------------------------------------------------
#' @export
as_mapper.default <- function(.f, ...) {
rlang::as_closure(.f)
}
purrr/R/rerun.R 0000644 0001762 0000144 00000002131 13125254534 013104 0 ustar ligges users #' Re-run expressions multiple times.
#'
#' This is a convenient way of generating sample data. It works similarly to
#' \code{\link{replicate}(..., simplify = FALSE)}.
#'
#' @param .n Number of times to run expressions
#' @param ... Expressions to re-run.
#' @return A list of length `.n`. Each element of `...` will be
#' re-run once for each `.n`. It
#'
#' There is one special case: if there's a single unnamed input, the second
#' level list will be dropped. In this case, `rerun(n, x)` behaves like
#' `replicate(n, x, simplify = FALSE)`.
#' @export
#' @examples
#' 10 %>% rerun(rnorm(5))
#' 10 %>%
#' rerun(x = rnorm(5), y = rnorm(5)) %>%
#' map_dbl(~ cor(.x$x, .x$y))
rerun <- function(.n, ...) {
dots <- quos(...)
# Special case: if single unnamed argument, insert directly into the output
# rather than wrapping in a list.
if (length(dots) == 1 && !has_names(dots)) {
dots <- dots[[1]]
eval_dots <- eval_tidy
} else {
eval_dots <- function(x) lapply(x, eval_tidy)
}
out <- vector("list", .n)
for (i in seq_len(.n)) {
out[[i]] <- eval_dots(dots)
}
out
}
purrr/R/utils.R 0000644 0001762 0000144 00000004313 13147024312 013106 0 ustar ligges users #' Pipe operator
#'
#' @name %>%
#' @rdname pipe
#' @keywords internal
#' @export
#' @importFrom magrittr %>%
#' @usage lhs \%>\% rhs
NULL
maybe_as_data_frame <- function(out, x) {
if (is.data.frame(x)) {
tibble::as_tibble(out)
} else {
out
}
}
recycle_args <- function(args) {
lengths <- map_int(args, length)
n <- max(lengths)
stopifnot(all(lengths == 1L | lengths == n))
to_recycle <- lengths == 1L
args[to_recycle] <- lapply(args[to_recycle], function(x) rep.int(x, n))
args
}
names2 <- function(x) {
names(x) %||% rep("", length(x))
}
#' Default value for `NULL`.
#'
#' This infix function makes it easy to replace `NULL`s with a
#' default value. It's inspired by the way that Ruby's or operation (`||`)
#' works.
#'
#' @param x,y If `x` is NULL, will return `y`; otherwise returns
#' `x`.
#' @export
#' @name null-default
#' @examples
#' 1 %||% 2
#' NULL %||% 2
`%||%` <- function(x, y) {
if (is.null(x)) {
y
} else {
x
}
}
#' Infix attribute accessor
#'
#' @param x Object
#' @param name Attribute name
#' @export
#' @name get-attr
#' @examples
#' factor(1:3) %@% "levels"
#' mtcars %@% "class"
`%@%` <- function(x, name) attr(x, name, exact = TRUE)
#' Generate random sample from a Bernoulli distribution
#'
#' @param n Number of samples
#' @param p Probability of getting `TRUE`
#' @return A logical vector
#' @export
#' @examples
#' rbernoulli(10)
#' rbernoulli(100, 0.1)
rbernoulli <- function(n, p = 0.5) {
stats::runif(n) > (1 - p)
}
#' Generate random sample from a discrete uniform distribution
#'
#' @param n Number of samples to draw.
#' @param a,b Range of the distribution (inclusive).
#' @export
#' @examples
#' table(rdunif(1e3, 10))
#' table(rdunif(1e3, 10, -5))
rdunif <- function(n, b, a = 1) {
stopifnot(is.numeric(a), length(a) == 1)
stopifnot(is.numeric(b), length(b) == 1)
a1 <- min(a, b)
b1 <- max(a, b)
sample(b1 - a1 + 1, n, replace = TRUE) + a1 - 1
}
# magrittr placeholder
globalVariables(".")
has_names <- function(x) {
nms <- names(x)
if (is.null(nms)) {
rep_along(x, FALSE)
} else {
!(is.na(nms) | nms == "")
}
}
ndots <- function(...) nargs()
is_names <- function(nms) {
is_character(nms) && !any(is.na(nms) | nms == "")
}
purrr/R/splice.R 0000644 0001762 0000144 00000001523 13124217327 013232 0 ustar ligges users #' Splice objects and lists of objects into a list
#'
#' This splices all arguments into a list. Non-list objects and lists
#' with a S3 class are encapsulated in a list before concatenation.
#'
#' @param ... Objects to concatenate.
#' @return A list.
#' @export
#' @examples
#' inputs <- list(arg1 = "a", arg2 = "b")
#'
#' # splice() concatenates the elements of inputs with arg3
#' splice(inputs, arg3 = c("c1", "c2")) %>% str()
#' list(inputs, arg3 = c("c1", "c2")) %>% str()
#' c(inputs, arg3 = c("c1", "c2")) %>% str()
splice <- function(...) {
splice_if(list(...), is_bare_list)
}
splice_if <- function(.x, .p) {
unspliced <- !probe(.x, .p)
out <- modify_if(.x, unspliced, list)
# Copy outer names to inner
if (!is.null(names(.x))) {
out[unspliced] <- map2(out[unspliced], names(out)[unspliced], set_names)
}
flatten(out)
}
purrr/R/output.R 0000644 0001762 0000144 00000010617 13137651733 013326 0 ustar ligges users #' Capture side effects.
#'
#' These functions wrap functions so that instead of generating side effects
#' through printed output, messages, warnings, and errors, they return enhanced
#' output. They are all adverbs because they modify the action of a verb (a
#' function).
#'
#' @inheritParams map
#' @param quiet Hide errors (`TRUE`, the default), or display them
#' as they occur?
#' @param otherwise Default value to use when an error occurs.
#'
#' @return `safely`: wrapped function instead returns a list with
#' components `result` and `error`. One value is always `NULL`.
#'
#' `quietly`: wrapped function instead returns a list with components
#' `result`, `output`, `messages` and `warnings`.
#'
#' `possibly`: wrapped function uses a default value (`otherwise`)
#' whenever an error occurs.
#'
#' @export
#' @examples
#' safe_log <- safely(log)
#' safe_log(10)
#' safe_log("a")
#'
#' list("a", 10, 100) %>%
#' map(safe_log) %>%
#' transpose()
#'
#' # This is a bit easier to work with if you supply a default value
#' # of the same type and use the simplify argument to transpose():
#' safe_log <- safely(log, otherwise = NA_real_)
#' list("a", 10, 100) %>%
#' map(safe_log) %>%
#' transpose() %>%
#' simplify_all()
#'
#' # To replace errors with a default value, use possibly().
#' list("a", 10, 100) %>%
#' map_dbl(possibly(log, NA_real_))
#'
#' # For interactive usage, auto_browse() is useful because it automatically
#' # starts a browser() in the right place.
#' f <- function(x) {
#' y <- 20
#' if (x > 5) {
#' stop("!")
#' } else {
#' x
#' }
#' }
#' if (interactive()) {
#' map(1:6, auto_browse(f))
#' }
#'
#' # It doesn't make sense to use auto_browse with primitive functions,
#' # because they are implemented in C so there's no useful environment
#' # for you to interact with.
safely <- function(.f, otherwise = NULL, quiet = TRUE) {
.f <- as_mapper(.f)
function(...) capture_error(.f(...), otherwise, quiet)
}
#' @export
#' @rdname safely
quietly <- function(.f) {
.f <- as_mapper(.f)
function(...) capture_output(.f(...))
}
#' @export
#' @rdname safely
possibly <- function(.f, otherwise, quiet = TRUE) {
.f <- as_mapper(.f)
force(otherwise)
function(...) {
tryCatch(.f(...),
error = function(e) {
if (!quiet)
message("Error: ", e$message)
otherwise
},
interrupt = function(e) {
stop("Terminated by user", call. = FALSE)
}
)
}
}
#' @export
#' @rdname safely
auto_browse <- function(.f) {
if (is_primitive(.f)) {
abort("Can not auto_browse() primitive functions")
}
function(...) {
withCallingHandlers(
.f(...),
error = function(e) {
# 1: h(simpleError(msg, call))
# 2: .handleSimpleError(function (e) <...>
# 3: stop(...)
frame <- ctxt_frame(4)
browse_in_frame(frame)
},
warning = function(e) {
if (getOption("warn") >= 2) {
frame <- ctxt_frame(7)
browse_in_frame(frame)
}
},
interrupt = function(e) {
stop("Terminated by user", call. = FALSE)
}
)
}
}
browse_in_frame <- function(frame) {
# ESS should problably set `.Platform$GUI == "ESS"`
# In the meantime, check that ESSR is attached
if (is_scoped("ESSR")) {
# Workaround ESS issue
with_env(frame$env, on.exit({
browser()
NULL
}))
return_from(frame)
} else {
eval_bare(quote(browser()), env = frame$env)
}
}
capture_error <- function(code, otherwise = NULL, quiet = TRUE) {
tryCatch(
list(result = code, error = NULL),
error = function(e) {
if (!quiet)
message("Error: ", e$message)
list(result = otherwise, error = e)
},
interrupt = function(e) {
stop("Terminated by user", call. = FALSE)
}
)
}
capture_output <- function(code) {
warnings <- character()
wHandler <- function(w) {
warnings <<- c(warnings, w$message)
invokeRestart("muffleWarning")
}
messages <- character()
mHandler <- function(m) {
messages <<- c(messages, m$message)
invokeRestart("muffleMessage")
}
temp <- file()
sink(temp)
on.exit({
sink()
close(temp)
})
result <- withCallingHandlers(
code,
warning = wHandler,
message = mHandler
)
output <- paste0(readLines(temp, warn = FALSE), collapse = "\n")
list(
result = result,
output = output,
warnings = warnings,
messages = messages
)
}
purrr/R/reduce.R 0000644 0001762 0000144 00000011703 13147024610 013217 0 ustar ligges users #' Reduce a list to a single value by iteratively applying a binary function.
#'
#' `reduce()` combines from the left, `reduce_right()` combines from
#' the right. `reduce(list(x1, x2, x3), f)` is equivalent to
#' `f(f(x1, x2), x3)`; `reduce_right(list(x1, x2, x3), f)` is equivalent to
#' `f(f(x3, x2), x1)`.
#'
#' @inheritParams map
#' @param .y For `reduce2()`, an additional argument that is passed to
#' `.f`. If `init` is not set, `.y` should be 1 element shorter than
#' `.x`.
#' @param .f For `reduce()`, a 2-argument function. The function will be
#' passed the accumulated value as the first argument and the "next" value
#' as the second argument.
#'
#' For `reduce2()`, a 3-argument function. The function will be passed the
#' accumulated value as the first argument, the next value of `.x` as the
#' second argument, and the next value of `.y` as the third argument.
#'
#' @param .init If supplied, will be used as the first value to start
#' the accumulation, rather than using \code{x[[1]]}. This is useful if
#' you want to ensure that `reduce` returns a correct value when `.x`
#' is empty. If missing, and `x` is empty, will throw an error.
#' @export
#' @examples
#' 1:3 %>% reduce(`+`)
#' 1:10 %>% reduce(`*`)
#'
#' paste2 <- function(x, y, sep = ".") paste(x, y, sep = sep)
#' letters[1:4] %>% reduce(paste2)
#' letters[1:4] %>% reduce2(c("-", ".", "-"), paste2)
#'
#' samples <- rerun(2, sample(10, 5))
#' samples
#' reduce(samples, union)
#' reduce(samples, intersect)
#'
#' x <- list(c(0, 1), c(2, 3), c(4, 5))
#' x %>% reduce(c)
#' x %>% reduce_right(c)
#' # Equivalent to:
#' x %>% rev() %>% reduce(c)
reduce <- function(.x, .f, ..., .init) {
reduce_impl(.x, .f, ..., .init = .init, .left = TRUE)
}
#' @export
#' @rdname reduce
reduce_right <- function(.x, .f, ..., .init) {
reduce_impl(.x, .f, ..., .init = .init, .left = FALSE)
}
#' @export
#' @rdname reduce
reduce2 <- function(.x, .y, .f, ..., .init) {
reduce2_impl(.x, .y, .f, ..., .init = .init, .left = TRUE)
}
#' @export
#' @rdname reduce
reduce2_right <- function(.x, .y, .f, ..., .init) {
reduce2_impl(.x, .f, .y, ..., .init = .init, .left = FALSE)
}
reduce2_impl <- function(.x, .y, .f, ..., .init, .left = TRUE) {
out <- reduce_init(.x, .init, left = .left)
x_idx <- reduce_index(.x, .init, left = .left)
y_idx <- reduce_index(.y, NULL, left = .left)
if (length(x_idx) != length(y_idx)) {
stop("`.y` does not have length ", length(x_idx))
}
.f <- as_mapper(.f, ...)
for (i in seq_along(x_idx)) {
x_i <- x_idx[[i]]
y_i <- y_idx[[i]]
out <- .f(out, .x[[x_i]], .y[[y_i]], ...)
}
out
}
reduce_impl <- function(.x, .f, ..., .init, .left = TRUE) {
out <- reduce_init(.x, .init, left = .left)
idx <- reduce_index(.x, .init, left = .left)
.f <- as_mapper(.f, ...)
for (i in idx) {
out <- .f(out, .x[[i]], ...)
}
out
}
reduce_init <- function(x, init, left = TRUE) {
if (!missing(init)) {
init
} else {
if (is_empty(x)) {
stop("`.x` is empty, and no `.init` supplied", call. = FALSE)
} else if (left) {
x[[1]]
} else {
x[[length(x)]]
}
}
}
reduce_index <- function(x, init, left = TRUE) {
n <- length(x)
if (!missing(init)) {
if (left) {
seq_len(n)
} else {
rev(seq_len(n))
}
} else {
if (left) {
seq_len2(2L, n)
} else {
rev(seq_len2(1L, n - 1L))
}
}
}
seq_len2 <- function(start, end) {
if (start > end) {
return(integer(0))
}
start:end
}
#' Accumulate recursive folds across a list
#'
#' `accumulate` applies a function recursively over a list from the left, while
#' `accumulate_right` applies the function from the right. Unlike `reduce`
#' both functions keep the intermediate results.
#'
#' @inheritParams reduce
#' @export
#' @examples
#' 1:3 %>% accumulate(`+`)
#' 1:10 %>% accumulate_right(`*`)
#'
#' # From Haskell's scanl documentation
#' 1:10 %>% accumulate(max, .init = 5)
#'
#' # Understanding the arguments .x and .y when .f
#' # is a lambda function
#' # .x is the accumulating value
#' 1:10 %>% accumulate(~ .x)
#' # .y is element in the list
#' 1:10 %>% accumulate(~ .y)
#'
#' # Simulating stochastic processes with drift
#' \dontrun{
#' library(dplyr)
#' library(ggplot2)
#'
#' rerun(5, rnorm(100)) %>%
#' set_names(paste0("sim", 1:5)) %>%
#' map(~ accumulate(., ~ .05 + .x + .y)) %>%
#' map_dfr(~ data_frame(value = .x, step = 1:100), .id = "simulation") %>%
#' ggplot(aes(x = step, y = value)) +
#' geom_line(aes(color = simulation)) +
#' ggtitle("Simulations of a random walk with drift")
#' }
accumulate <- function(.x, .f, ..., .init) {
.f <- as_mapper(.f, ...)
f <- function(x, y) {
.f(x, y, ...)
}
Reduce(f, .x, init = .init, accumulate = TRUE)
}
#' @export
#' @rdname accumulate
accumulate_right <- function(.x, .f, ..., .init) {
.f <- as_mapper(.f, ...)
# Note the order of arguments is switched
f <- function(x, y) {
.f(y, x, ...)
}
Reduce(f, .x, init = .init, right = TRUE, accumulate = TRUE)
}
purrr/R/find-position.R 0000644 0001762 0000144 00000003561 13137651733 014550 0 ustar ligges users #' Find the value or position of the first match.
#'
#' @inheritParams every
#' @inheritParams map
#' @param .right If `FALSE`, the default, starts at the beginning
#' of the vector and move towards the end; if `TRUE`, starts at the end
#' of the vector and moves towards the beginning.
#' @return `detect` the value of the first item that matches the
#' predicate; `detect_index` the position of the matching item.
#' If not found, `detect` returns `NULL` and `detect_index`
#' returns 0.
#' @export
#' @examples
#' is_even <- function(x) x %% 2 == 0
#'
#' 3:10 %>% detect(is_even)
#' 3:10 %>% detect_index(is_even)
#'
#' 3:10 %>% detect(is_even, .right = TRUE)
#' 3:10 %>% detect_index(is_even, .right = TRUE)
#'
#'
#' # Since `.f` is passed to as_mapper(), you can supply a
#' # lambda-formula or a pluck object:
#' x <- list(
#' list(1, foo = FALSE),
#' list(2, foo = TRUE),
#' list(3, foo = TRUE)
#' )
#'
#' detect(x, "foo")
#' detect_index(x, "foo")
detect <- function(.x, .f, ..., .right = FALSE, .p) {
if (!missing(.p)) {
warn("`.p` has been renamed to `.f`", "purrr_2.2.3")
.f <- .p
}
.f <- as_mapper(.f, ...)
for (i in index(.x, .right)) {
if (is_true(.f(.x[[i]], ...))) return(.x[[i]])
}
NULL
}
#' @export
#' @rdname detect
detect_index <- function(.x, .f, ..., .right = FALSE, .p) {
if (!missing(.p)) {
warn("`.p` has been renamed to `.f`", "purrr_2.2.3")
.f <- .p
}
.f <- as_mapper(.f, ...)
for (i in index(.x, .right)) {
if (is_true(.f(.x[[i]], ...))) return(i)
}
0L
}
index <- function(x, right = FALSE) {
idx <- seq_along(x)
if (right)
idx <- rev(idx)
idx
}
#' Does a list contain an object?
#'
#' @inheritParams map
#' @param .y Object to test for
#' @export
#' @examples
#' x <- list(1:10, 5, 9.9)
#' x %>% has_element(1:10)
#' x %>% has_element(3)
has_element <- function(.x, .y) {
some(.x, identical, .y)
}
purrr/R/head-tail.R 0000644 0001762 0000144 00000001314 13102346452 013577 0 ustar ligges users #' Find head/tail that all satisfies a predicate.
#'
#' @inheritParams map_if
#' @inheritParams map
#' @return A vector the same type as `.x`.
#' @export
#' @examples
#' pos <- function(x) x >= 0
#' head_while(5:-5, pos)
#' tail_while(5:-5, negate(pos))
#'
#' big <- function(x) x > 100
#' head_while(0:10, big)
#' tail_while(0:10, big)
head_while <- function(.x, .p, ...) {
# Find location of first FALSE
loc <- detect_index(.x, negate(.p), ...)
if (loc == 0) return(.x)
.x[seq_len(loc - 1)]
}
#' @export
#' @rdname head_while
tail_while <- function(.x, .p, ...) {
# Find location of last FALSE
loc <- detect_index(.x, negate(.p), ..., .right = TRUE)
if (loc == 0) return(.x)
.x[-seq_len(loc)]
}
purrr/R/invoke.R 0000644 0001762 0000144 00000010660 13124217327 013250 0 ustar ligges users #' Invoke functions.
#'
#' This pair of functions make it easier to combine a function and list
#' of parameters to get a result. `invoke` is a wrapper around
#' `do.call` that makes it easy to use in a pipe. `invoke_map`
#' makes it easier to call lists of functions with lists of parameters.
#'
#' @param .f For `invoke`, a function; for `invoke_map` a
#' list of functions.
#' @param .x For `invoke`, an argument-list; for `invoke_map` a
#' list of argument-lists the same length as `.f` (or length 1).
#' The default argument, `list(NULL)`, will be recycled to the
#' same length as `.f`, and will call each function with no
#' arguments (apart from any supplied in `...`.
#' @param ... Additional arguments passed to each function.
#' @param .env Environment in which [do.call()] should
#' evaluate a constructed expression. This only matters if you pass
#' as `.f` the name of a function rather than its value, or as
#' `.x` symbols of objects rather than their values.
#' @inheritParams map
#' @export
#' @family map variants
#' @examples
#' # Invoke a function with a list of arguments
#' invoke(runif, list(n = 10))
#' # Invoke a function with named arguments
#' invoke(runif, n = 10)
#'
#' # Combine the two:
#' invoke(paste, list("01a", "01b"), sep = "-")
#' # That's more natural as part of a pipeline:
#' list("01a", "01b") %>%
#' invoke(paste, ., sep = "-")
#'
#' # Invoke a list of functions, each with different arguments
#' invoke_map(list(runif, rnorm), list(list(n = 10), list(n = 5)))
#' # Or with the same inputs:
#' invoke_map(list(runif, rnorm), list(list(n = 5)))
#' invoke_map(list(runif, rnorm), n = 5)
#' # Or the same function with different inputs:
#' invoke_map("runif", list(list(n = 5), list(n = 10)))
#'
#' # Or as a pipeline
#' list(m1 = mean, m2 = median) %>% invoke_map(x = rcauchy(100))
#' list(m1 = mean, m2 = median) %>% invoke_map_dbl(x = rcauchy(100))
#'
#' # Note that you can also match by position by explicitly omitting `.x`.
#' # This can be useful when the argument names of the functions are not
#' # identical
#' list(m1 = mean, m2 = median) %>%
#' invoke_map(, rcauchy(100))
#'
#' # If you have pairs of function name and arguments, it's natural
#' # to store them in a data frame. Here we use a tibble because
#' # it has better support for list-columns
#' df <- tibble::tibble(
#' f = c("runif", "rpois", "rnorm"),
#' params = list(
#' list(n = 10),
#' list(n = 5, lambda = 10),
#' list(n = 10, mean = -3, sd = 10)
#' )
#' )
#' df
#' invoke_map(df$f, df$params)
invoke <- function(.f, .x = NULL, ..., .env = NULL) {
.env <- .env %||% parent.frame()
args <- c(as.list(.x), list(...))
do.call(.f, args, envir = .env)
}
as_invoke_function <- function(f) {
if (is.function(f)) {
list(f)
} else {
f
}
}
#' @rdname invoke
#' @export
invoke_map <- function(.f, .x = list(NULL), ..., .env = NULL) {
.env <- .env %||% parent.frame()
.f <- as_invoke_function(.f)
map2(.f, .x, invoke, ..., .env = .env)
}
#' @rdname invoke
#' @export
invoke_map_lgl <- function(.f, .x = list(NULL), ..., .env = NULL) {
.env <- .env %||% parent.frame()
.f <- as_invoke_function(.f)
map2_lgl(.f, .x, invoke, ..., .env = .env)
}
#' @rdname invoke
#' @export
invoke_map_int <- function(.f, .x = list(NULL), ..., .env = NULL) {
.env <- .env %||% parent.frame()
.f <- as_invoke_function(.f)
map2_int(.f, .x, invoke, ..., .env = .env)
}
#' @rdname invoke
#' @export
invoke_map_dbl <- function(.f, .x = list(NULL), ..., .env = NULL) {
.env <- .env %||% parent.frame()
.f <- as_invoke_function(.f)
map2_dbl(.f, .x, invoke, ..., .env = .env)
}
#' @rdname invoke
#' @export
invoke_map_chr <- function(.f, .x = list(NULL), ..., .env = NULL) {
.env <- .env %||% parent.frame()
.f <- as_invoke_function(.f)
map2_chr(.f, .x, invoke, ..., .env = .env)
}
#' @rdname invoke
#' @export
invoke_map_dfr <- function(.f, .x = list(NULL), ..., .env = NULL) {
.env <- .env %||% parent.frame()
.f <- as_invoke_function(.f)
map2_dfr(.f, .x, invoke, ..., .env = .env)
}
#' @rdname invoke
#' @export
invoke_map_dfc <- function(.f, .x = list(NULL), ..., .env = NULL) {
.env <- .env %||% parent.frame()
.f <- as_invoke_function(.f)
map2_dfc(.f, .x, invoke, ..., .env = .env)
}
#' @rdname invoke
#' @export
#' @usage NULL
invoke_map_df <- invoke_map_dfr
#' @rdname invoke
#' @export
#' @usage NULL
map_call <- function(.x, .f, ...) {
warning("`map_call()` is deprecated. Please use `invoke()` instead.")
invoke(.f, .x, ...)
}
purrr/R/map2-pmap.R 0000644 0001762 0000144 00000012413 13137651733 013554 0 ustar ligges users #' Map over multiple inputs simultaneously.
#'
#' These functions are variants of `map()` iterate over multiple
#' arguments in parallel. `map2()` and `walk2()` are specialised for the two
#' argument case; `pmap()` and `pwalk()` allow you to provide any number of
#' arguments in a list.
#'
#' Note that arguments to be vectorised over come before the `.f`,
#' and arguments that are supplied to every call come after `.f`.
#'
#' @inheritParams map
#' @param .x,.y Vectors of the same length. A vector of length 1 will
#' be recycled.
#' @param .l A list of lists. The length of `.l` determines the
#' number of arguments that `.f` will be called with. List
#' names will be used if present.
#' @return An atomic vector, list, or data frame, depending on the suffix.
#' Atomic vectors and lists will be named if `.x` or the first
#' element of `.l` is named.
#'
#' If all input is length 0, the output will be length 0. If any
#' input is length 1, it will be recycled to the length of the longest.
#' @export
#' @family map variants
#' @examples
#' x <- list(1, 10, 100)
#' y <- list(1, 2, 3)
#' z <- list(5, 50, 500)
#'
#' map2(x, y, ~ .x + .y)
#' # Or just
#' map2(x, y, `+`)
#'
#' # Split into pieces, fit model to each piece, then predict
#' by_cyl <- mtcars %>% split(.$cyl)
#' mods <- by_cyl %>% map(~ lm(mpg ~ wt, data = .))
#' map2(mods, by_cyl, predict)
#'
#' pmap(list(x, y, z), sum)
#'
#' # Matching arguments by position
#' pmap(list(x, y, z), function(a, b ,c) a / (b + c))
#'
#' # Matching arguments by name
#' l <- list(a = x, b = y, c = z)
#' pmap(l, function(c, b, a) a / (b + c))
#'
#' # Vectorizing a function over multiple arguments
#' df <- data.frame(
#' x = c("apple", "banana", "cherry"),
#' pattern = c("p", "n", "h"),
#' replacement = c("x", "f", "q"),
#' stringsAsFactors = FALSE
#' )
#' pmap(df, gsub)
#' pmap_chr(df, gsub)
#'
#' ## Use `...` to absorb unused components of input list .l
#' df <- data.frame(
#' x = 1:3 + 0.1,
#' y = 3:1 - 0.1,
#' z = letters[1:3]
#' )
#' plus <- function(x, y) x + y
#' \dontrun{
#' ## this won't work
#' pmap(df, plus)
#' }
#' ## but this will
#' plus2 <- function(x, y, ...) x + y
#' pmap_dbl(df, plus2)
#'
map2 <- function(.x, .y, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map2_impl, environment(), ".x", ".y", ".f", "list")
}
#' @export
#' @rdname map2
map2_lgl <- function(.x, .y, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map2_impl, environment(), ".x", ".y", ".f", "logical")
}
#' @export
#' @rdname map2
map2_int <- function(.x, .y, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map2_impl, environment(), ".x", ".y", ".f", "integer")
}
#' @export
#' @rdname map2
map2_dbl <- function(.x, .y, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map2_impl, environment(), ".x", ".y", ".f", "double")
}
#' @export
#' @rdname map2
map2_chr <- function(.x, .y, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map2_impl, environment(), ".x", ".y", ".f", "character")
}
#' @rdname map2
#' @export
map2_dfr <- function(.x, .y, .f, ..., .id = NULL) {
if (!is_installed("dplyr")) {
abort("`map2_dfr()` requires dplyr")
}
.f <- as_mapper(.f, ...)
res <- map2(.x, .y, .f, ...)
dplyr::bind_rows(res, .id = .id)
}
#' @rdname map2
#' @export
map2_dfc <- function(.x, .y, .f, ...) {
if (!is_installed("dplyr")) {
abort("`map2_dfc()` requires dplyr")
}
.f <- as_mapper(.f, ...)
res <- map2(.x, .y, .f, ...)
dplyr::bind_cols(res)
}
#' @rdname map2
#' @export
#' @usage NULL
map2_df <- map2_dfr
#' @export
#' @rdname map2
walk2 <- function(.x, .y, .f, ...) {
pwalk(list(.x, .y), .f, ...)
invisible(.x)
}
#' @export
#' @rdname map2
pmap <- function(.l, .f, ...) {
.f <- as_mapper(.f, ...)
if (is.data.frame(.l)) {
.l <- as.list(.l)
}
.Call(pmap_impl, environment(), ".l", ".f", "list")
}
#' @export
#' @rdname map2
pmap_lgl <- function(.l, .f, ...) {
.f <- as_mapper(.f, ...)
if (is.data.frame(.l)) {
.l <- as.list(.l)
}
.Call(pmap_impl, environment(), ".l", ".f", "logical")
}
#' @export
#' @rdname map2
pmap_int <- function(.l, .f, ...) {
.f <- as_mapper(.f, ...)
if (is.data.frame(.l)) {
.l <- as.list(.l)
}
.Call(pmap_impl, environment(), ".l", ".f", "integer")
}
#' @export
#' @rdname map2
pmap_dbl <- function(.l, .f, ...) {
.f <- as_mapper(.f, ...)
if (is.data.frame(.l)) {
.l <- as.list(.l)
}
.Call(pmap_impl, environment(), ".l", ".f", "double")
}
#' @export
#' @rdname map2
pmap_chr <- function(.l, .f, ...) {
.f <- as_mapper(.f, ...)
if (is.data.frame(.l)) {
.l <- as.list(.l)
}
.Call(pmap_impl, environment(), ".l", ".f", "character")
}
#' @rdname map2
#' @export
pmap_dfr <- function(.l, .f, ..., .id = NULL) {
if (!is_installed("dplyr")) {
abort("`pmap_dfr()` requires dplyr")
}
.f <- as_mapper(.f, ...)
res <- pmap(.l, .f, ...)
dplyr::bind_rows(res, .id = .id)
}
#' @rdname map2
#' @export
pmap_dfc <- function(.l, .f, ...) {
if (!is_installed("dplyr")) {
abort("`pmap_dfc()` requires dplyr")
}
.f <- as_mapper(.f, ...)
res <- pmap(.l, .f, ...)
dplyr::bind_cols(res)
}
#' @rdname map2
#' @export
#' @usage NULL
pmap_df <- pmap_dfr
#' @export
#' @rdname map2
pwalk <- function(.l, .f, ...) {
.f <- as_mapper(.f, ...)
args_list <- recycle_args(.l) %>% transpose()
for (args in args_list) {
do.call(".f", c(args, list(...)))
}
invisible(.l)
}
purrr/R/depth.R 0000644 0001762 0000144 00000001035 13137651733 013064 0 ustar ligges users #' Compute the depth of a vector
#'
#' The depth of a vector is basically how many levels that you can index
#' into it.
#'
#' @param x A vector
#' @return An integer.
#' @export
#' @examples
#' x <- list(
#' list(),
#' list(list()),
#' list(list(list(1)))
#' )
#' vec_depth(x)
#' x %>% map_int(vec_depth)
vec_depth <- function(x) {
if (is_null(x)) {
0L
} else if (is_atomic(x)) {
1L
} else if (is_list(x)) {
depths <- map_int(x, vec_depth)
1L + max(depths, 0L)
} else {
abort("`x` must be a vector")
}
}
purrr/R/arrays.R 0000644 0001762 0000144 00000004633 13102346452 013257 0 ustar ligges users #' Coerce array to list
#'
#' `array_branch()` and `array_tree()` enable arrays to be
#' used with purrr's functionals by turning them into lists. The
#' details of the coercion are controlled by the `margin`
#' argument. `array_tree()` creates an hierarchical list (a tree)
#' that has as many levels as dimensions specified in `margin`,
#' while `array_branch()` creates a flat list (by analogy, a
#' branch) along all mentioned dimensions.
#'
#' When no margin is specified, all dimensions are used by
#' default. When `margin` is a numeric vector of length zero, the
#' whole array is wrapped in a list.
#' @param array An array to coerce into a list.
#' @param margin A numeric vector indicating the positions of the
#' indices to be to be enlisted. If `NULL`, a full margin is
#' used. If `numeric(0)`, the array as a whole is wrapped in a
#' list.
#' @name array-coercion
#' @export
#' @examples
#' # We create an array with 3 dimensions
#' x <- array(1:12, c(2, 2, 3))
#'
#' # A full margin for such an array would be the vector 1:3. This is
#' # the default if you don't specify a margin
#'
#' # Creating a branch along the full margin is equivalent to
#' # as.list(array) and produces a list of size length(x):
#' array_branch(x) %>% str()
#'
#' # A branch along the first dimension yields a list of length 2
#' # with each element containing a 2x3 array:
#' array_branch(x, 1) %>% str()
#'
#' # A branch along the first and third dimensions yields a list of
#' # length 2x3 whose elements contain a vector of length 2:
#' array_branch(x, c(1, 3)) %>% str()
#'
#' # Creating a tree from the full margin creates a list of lists of
#' # lists:
#' array_tree(x) %>% str()
#'
#' # The ordering and the depth of the tree are controlled by the
#' # margin argument:
#' array_tree(x, c(3, 1)) %>% str()
array_branch <- function(array, margin = NULL) {
dim(array) <- dim(array) %||% length(array)
margin <- margin %||% seq_along(dim(array))
if (length(margin) == 0) {
list(array)
} else {
apply(array, margin, list) %>% flatten()
}
}
#' @rdname array-coercion
#' @export
array_tree <- function(array, margin = NULL) {
dim(array) <- dim(array) %||% length(array)
margin <- margin %||% seq_along(dim(array))
if (length(margin) > 1) {
new_margin <- ifelse(margin[-1] > margin[[1]], margin[-1] - 1, margin[-1])
apply(array, margin[[1]], . %>% array_tree(new_margin))
} else {
array_branch(array, margin)
}
}
purrr/R/imap.R 0000644 0001762 0000144 00000003332 13102346452 012677 0 ustar ligges users #' Apply a function to each element of a vector, and its index
#'
#' `imap_xxx(x, ...)`, an indexed map, is short hand for
#' `map2(x, names(x), ...)` if `x` has names, or `map2(x, seq_along(x), ...)`
#' if it does not. This is useful if you need to compute on both the value
#' and the position of an element.
#'
#' @inheritParams map
#' @return A vector the same length as `.x`.
#' @export
#' @family map variants
#' @examples
#' # Note that when using the formula shortcut, the first argument
#' # is the value, and the second is the position
#' imap_chr(sample(10), ~ paste0(.y, ": ", .x))
#' iwalk(mtcars, ~ cat(.y, ": ", median(.x), "\n", sep = ""))
imap <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
map2(.x, vec_index(.x), .f, ...)
}
#' @rdname imap
#' @export
imap_lgl <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
map2_lgl(.x, vec_index(.x), .f, ...)
}
#' @rdname imap
#' @export
imap_chr <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
map2_chr(.x, vec_index(.x), .f, ...)
}
#' @rdname imap
#' @export
imap_int <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
map2_int(.x, vec_index(.x), .f, ...)
}
#' @rdname imap
#' @export
imap_dbl <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
map2_dbl(.x, vec_index(.x), .f, ...)
}
#' @rdname imap
#' @export
imap_dfr <- function(.x, .f, ..., .id = NULL) {
.f <- as_mapper(.f, ...)
map2_dfr(.x, vec_index(.x), .f, ...)
}
#' @rdname imap
#' @export
imap_dfc <- function(.x, .f, ..., .id = NULL) {
.f <- as_mapper(.f, ...)
map2_dfc(.x, vec_index(.x), .f, ...)
}
#' @export
#' @rdname imap
iwalk <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
walk2(.x, vec_index(.x), .f, ...)
}
vec_index <- function(x) {
names(x) %||% seq_along(x)
}
purrr/R/flatten.R 0000644 0001762 0000144 00000003541 13124220045 013401 0 ustar ligges users #' Flatten a list of lists into a simple vector.
#'
#' These functions remove a level hierarchy from a list. They are similar to
#' [unlist()], only ever remove a single layer of hierarchy, and
#' are type-stable so you always know what the type of the output is.
#'
#' @param .x A list of flatten. The contents of the list can be anything for
#' `flatten` (as a list is returned), but the contents must match the
#' type for the other functions.
#' @return `flatten()` returns a list, `flatten_lgl()` a logical
#' vector, `flatten_int()` an integer vector, `flatten_dbl()` a
#' double vector, and `flatten_chr()` a character vector.
#'
#' `flatten_dfr()` and `flatten_dfc()` return data frames created by
#' row-binding and column-binding respectively. They require dplyr to
#' be installed.
#' @inheritParams map
#' @export
#' @examples
#' x <- rerun(2, sample(4))
#' x
#' x %>% flatten()
#' x %>% flatten_int()
#'
#' # You can use flatten in conjunction with map
#' x %>% map(1L) %>% flatten_int()
#' # But it's more efficient to use the typed map instead.
#' x %>% map_int(1L)
flatten <- function(.x) {
.Call(flatten_impl, .x)
}
#' @export
#' @rdname flatten
flatten_lgl <- function(.x) {
.Call(vflatten_impl, .x, "logical")
}
#' @export
#' @rdname flatten
flatten_int <- function(.x) {
.Call(vflatten_impl, .x, "integer")
}
#' @export
#' @rdname flatten
flatten_dbl <- function(.x) {
.Call(vflatten_impl, .x, "double")
}
#' @export
#' @rdname flatten
flatten_chr <- function(.x) {
.Call(vflatten_impl, .x, "character")
}
#' @export
#' @rdname flatten
flatten_dfr <- function(.x, .id = NULL) {
res <- .Call(flatten_impl, .x)
dplyr::bind_rows(res, .id = .id)
}
#' @export
#' @rdname flatten
flatten_dfc <- function(.x) {
res <- .Call(flatten_impl, .x)
dplyr::bind_cols(res)
}
#' @export
#' @rdname flatten
#' @usage NULL
flatten_df <- flatten_dfr
purrr/R/cross.R 0000644 0001762 0000144 00000012524 13102346452 013105 0 ustar ligges users #' Produce all combinations of list elements
#'
#' `cross2()` returns the product set of the elements of
#' `.x` and `.y`. `cross3()` takes an additional
#' `.z` argument. `cross()` takes a list `.l` and
#' returns the cartesian product of all its elements in a list, with
#' one combination by element. `cross_df()` is like
#' `cross()` but returns a data frame, with one combination by
#' row.
#'
#' `cross()`, `cross2()` and `cross3()` return the
#' cartesian product is returned in wide format. This makes it more
#' amenable to mapping operations. `cross_df()` returns the output
#' in long format just as `expand.grid()` does. This is adapted
#' to rowwise operations.
#'
#' When the number of combinations is large and the individual
#' elements are heavy memory-wise, it is often useful to filter
#' unwanted combinations on the fly with `.filter`. It must be
#' a predicate function that takes the same number of arguments as the
#' number of crossed objects (2 for `cross2()`, 3 for
#' `cross3()`, `length(.l)` for `cross()`) and
#' returns `TRUE` or `FALSE`. The combinations where the
#' predicate function returns `TRUE` will be removed from the
#' result.
#' @seealso [expand.grid()]
#' @param .x,.y,.z Lists or atomic vectors.
#' @param .l A list of lists or atomic vectors. Alternatively, a data
#' frame. `cross_df()` requires all elements to be named.
#' @param .filter A predicate function that takes the same number of
#' arguments as the number of variables to be combined.
#' @return `cross2()`, `cross3()` and `cross()`
#' always return a list. `cross_df()` always returns a data
#' frame. `cross()` returns a list where each element is one
#' combination so that the list can be directly mapped
#' over. `cross_df()` returns a data frame where each row is one
#' combination.
#' @export
#' @examples
#' # We build all combinations of names, greetings and separators from our
#' # list of data and pass each one to paste()
#' data <- list(
#' id = c("John", "Jane"),
#' greeting = c("Hello.", "Bonjour."),
#' sep = c("! ", "... ")
#' )
#'
#' data %>%
#' cross() %>%
#' map(lift(paste))
#'
#' # cross() returns the combinations in long format: many elements,
#' # each representing one combination. With cross_df() we'll get a
#' # data frame in long format: crossing three objects produces a data
#' # frame of three columns with each row being a particular
#' # combination. This is the same format that expand.grid() returns.
#' args <- data %>% cross_df()
#'
#' # In case you need a list in long format (and not a data frame)
#' # just run as.list() after cross_df()
#' args %>% as.list()
#'
#' # This format is often less pratical for functional programming
#' # because applying a function to the combinations requires a loop
#' out <- vector("list", length = nrow(args))
#' for (i in seq_along(out))
#' out[[i]] <- map(args, i) %>% invoke(paste, .)
#' out
#'
#' # It's easier to transpose and then use invoke_map()
#' args %>% transpose() %>% map_chr(~ invoke(paste, .))
#'
#' # Unwanted combinations can be filtered out with a predicate function
#' filter <- function(x, y) x >= y
#' cross2(1:5, 1:5, .filter = filter) %>% str()
#'
#' # To give names to the components of the combinations, we map
#' # setNames() on the product:
#' seq_len(3) %>%
#' cross2(., ., .filter = `==`) %>%
#' map(setNames, c("x", "y"))
#'
#' # Alternatively we can encapsulate the arguments in a named list
#' # before crossing to get named components:
#' seq_len(3) %>%
#' list(x = ., y = .) %>%
#' cross(.filter = `==`)
cross <- function(.l, .filter = NULL) {
if (is_empty(.l)) {
return(.l)
}
if (!is.null(.filter)) {
.filter <- as_mapper(.filter)
}
n <- length(.l)
lengths <- lapply(.l, length)
names <- names(.l)
factors <- cumprod(lengths)
total_length <- factors[n]
factors <- c(1, factors[-n])
out <- replicate(total_length, vector("list", n), simplify = FALSE)
for (i in seq_along(out)) {
for (j in seq_len(n)) {
index <- floor((i - 1) / factors[j]) %% length(.l[[j]]) + 1
out[[i]][[j]] <- .l[[j]][[index]]
}
names(out[[i]]) <- names
# Filter out unwanted elements. We set them to NULL instead of
# completely removing them so we don't mess up the loop indexing.
# NULL elements are removed later on.
if (!is.null(.filter)) {
is_to_filter <- do.call(".filter", unname(out[[i]]))
if (!is.logical(is_to_filter) || !length(is_to_filter) == 1) {
stop("The filter function must return TRUE or FALSE", call. = FALSE)
}
if (is_to_filter) {
out[i] <- list(NULL)
}
}
}
# Remove filtered elements
compact(out)
}
#' @export
#' @rdname cross
cross2 <- function(.x, .y, .filter = NULL) {
cross(list(.x, .y), .filter = .filter)
}
#' @export
#' @rdname cross
cross3 <- function(.x, .y, .z, .filter = NULL) {
cross(list(.x, .y, .z), .filter = .filter)
}
#' @rdname cross
#' @export
cross_df <- function(.l, .filter = NULL) {
cross(.l, .filter = .filter) %>%
transpose() %>%
simplify_all() %>%
tibble::as_tibble()
}
#' @export
#' @usage NULL
#' @rdname cross
cross_n <- function(...) {
warning("`cross_n()` is deprecated; please use `cross()` instead.",
call. = FALSE)
cross(...)
}
#' @export
#' @usage NULL
#' @rdname cross
cross_d <- function(...) {
warning("`cross_d()` is deprecated; please use `cross_df()` instead.",
call. = FALSE)
cross_df(...)
}
purrr/R/map.R 0000644 0001762 0000144 00000012630 13171452210 012523 0 ustar ligges users #' Apply a function to each element of a vector
#'
#' @description
#'
#' The map functions transform their input by applying a function to
#' each element and returning a vector the same length as the input.
#'
#' * `map()`, `map_if()` and `map_at()` always return a list. See the
#' [modify()] family for versions that return an object of the same
#' type as the input.
#'
#' The `_if` and `_at` variants take a predicate function `.p` that
#' determines which elements of `.x` are transformed with `.f`.
#'
#' * `map_lgl()`, `map_int()`, `map_dbl()` and `map_chr()` return
#' vectors of the corresponding type (or die trying).
#'
#' * `map_dfr()` and `map_dfc()` return data frames created by
#' row-binding and column-binding respectively. They require dplyr
#' to be installed.
#'
#' * `walk()` calls `.f` for its side-effect and returns the input `.x`.
#'
#' @inheritParams as_mapper
#' @param .x A list or atomic vector.
#' @param .p A single predicate function, a formula describing such a
#' predicate function, or a logical vector of the same length as `.x`.
#' Alternatively, if the elements of `.x` are themselves lists of
#' objects, a string indicating the name of a logical element in the
#' inner lists. Only those elements where `.p` evaluates to
#' `TRUE` will be modified.
#' @param .at A character vector of names or a numeric vector of
#' positions. Only those elements corresponding to `.at` will be
#' modified.
#' @param ... Additional arguments passed on to `.f`.
#' @return All functions return a vector the same length as `.x`.
#'
#' `map()` returns a list, `map_lgl()` a logical vector, `map_int()` an
#' integer vector, `map_dbl()` a double vector, and `map_chr()` a character
#' vector. The output of `.f` will be automatically typed upwards,
#' e.g. logical -> integer -> double -> character.
#'
#' `walk()` returns the input `.x` (invisibly). This makes it easy to
#' use in pipe.
#' @export
#' @family map variants
#' @examples
#' 1:10 %>%
#' map(rnorm, n = 10) %>%
#' map_dbl(mean)
#'
#' # Or use an anonymous function
#' 1:10 %>%
#' map(function(x) rnorm(10, x))
#'
#' # Or a formula
#' 1:10 %>%
#' map(~ rnorm(10, .x))
#'
#' # Extract by name or position
#' # .default specifies value for elements that are missing or NULL
#' l1 <- list(list(a = 1L), list(a = NULL, b = 2L), list(b = 3L))
#' l1 %>% map("a", .default = "???")
#' l1 %>% map_int("b", .default = NA)
#' l1 %>% map_int(2, .default = NA)
#'
#' # Supply multiple values to index deeply into a list
#' l2 <- list(
#' list(num = 1:3, letters[1:3]),
#' list(num = 101:103, letters[4:6]),
#' list()
#' )
#' l2 %>% map(c(2, 2))
#'
#' # Use a list to build an extractor that mixes numeric indices and names,
#' # and .default to provide a default value if the element does not exist
#' l2 %>% map(list("num", 3))
#' l2 %>% map_int(list("num", 3), .default = NA)
#'
#' # A more realistic example: split a data frame into pieces, fit a
#' # model to each piece, summarise and extract R^2
#' mtcars %>%
#' split(.$cyl) %>%
#' map(~ lm(mpg ~ wt, data = .x)) %>%
#' map(summary) %>%
#' map_dbl("r.squared")
#'
#' # Use map_lgl(), map_dbl(), etc to reduce to a vector.
#' # * list
#' mtcars %>% map(sum)
#' # * vector
#' mtcars %>% map_dbl(sum)
#'
#' # If each element of the output is a data frame, use
#' # map_dfr to row-bind them together:
#' mtcars %>%
#' split(.$cyl) %>%
#' map(~ lm(mpg ~ wt, data = .x)) %>%
#' map_dfr(~ as.data.frame(t(as.matrix(coef(.)))))
#' # (if you also want to preserve the variable names see
#' # the broom package)
map <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map_impl, environment(), ".x", ".f", "list")
}
#' @rdname map
#' @export
map_if <- function(.x, .p, .f, ...) {
sel <- probe(.x, .p)
out <- list_along(.x)
out[sel] <- map(.x[sel], .f, ...)
out[!sel] <- .x[!sel]
set_names(out, names(.x))
}
#' @rdname map
#' @export
map_at <- function(.x, .at, .f, ...) {
sel <- inv_which(.x, .at)
out <- list_along(.x)
out[sel] <- map(.x[sel], .f, ...)
out[!sel] <- .x[!sel]
set_names(out, names(.x))
}
#' @rdname map
#' @export
map_lgl <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map_impl, environment(), ".x", ".f", "logical")
}
#' @rdname map
#' @export
map_chr <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map_impl, environment(), ".x", ".f", "character")
}
#' @rdname map
#' @export
map_int <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map_impl, environment(), ".x", ".f", "integer")
}
#' @rdname map
#' @export
map_dbl <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
.Call(map_impl, environment(), ".x", ".f", "double")
}
#' @rdname map
#' @param .id If not `NULL` a variable with this name will be created
#' giving either the name or the index of the data frame.
#' @export
map_dfr <- function(.x, .f, ..., .id = NULL) {
if (!is_installed("dplyr")) {
abort("`map_df()` requires dplyr")
}
.f <- as_mapper(.f, ...)
res <- map(.x, .f, ...)
dplyr::bind_rows(res, .id = .id)
}
#' @rdname map
#' @export
#' @usage NULL
map_df <- map_dfr
#' @rdname map
#' @export
map_dfc <- function(.x, .f, ...) {
if (!is_installed("dplyr")) {
abort("`map_dfc()` requires dplyr")
}
.f <- as_mapper(.f, ...)
res <- map(.x, .f, ...)
dplyr::bind_cols(res)
}
#' @export
#' @rdname map
walk <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
for (i in seq_along(.x)) {
.f(.x[[i]], ...)
}
invisible(.x)
}
purrr/R/partial.R 0000644 0001762 0000144 00000005552 13137651733 013424 0 ustar ligges users #' Partial apply a function, filling in some arguments.
#'
#' Partial function application allows you to modify a function by pre-filling
#' some of the arguments. It is particularly useful in conjunction with
#' functionals and other function operators.
#'
#' @section Design choices:
#'
#' There are many ways to implement partial function application in R.
#' (see e.g. `dots` in \url{https://github.com/crowding/ptools} for another
#' approach.) This implementation is based on creating functions that are as
#' similar as possible to the anonymous functions that you'd create by hand,
#' if you weren't using `partial`.
#'
#' @param ...f a function. For the output source to read well, this should be a
#' named function.
#' @param ... named arguments to `...f` that should be partially applied.
#' @param .env the environment of the created function. Defaults to
#' [parent.frame()] and you should rarely need to modify this.
#' @param .lazy If `TRUE` arguments evaluated lazily, if `FALSE`,
#' evaluated when `partial` is called.
#' @param .first If `TRUE`, the partialized arguments are placed
#' to the front of the function signature. If `FALSE`, they are
#' moved to the back. Only useful to control position matching of
#' arguments when the partialized arguments are not named.
#' @export
#' @examples
#' # Partial is designed to replace the use of anonymous functions for
#' # filling in function arguments. Instead of:
#' compact1 <- function(x) discard(x, is.null)
#'
#' # we can write:
#' compact2 <- partial(discard, .p = is.null)
#'
#' # and the generated source code is very similar to what we made by hand
#' compact1
#' compact2
#'
#' # Note that the evaluation occurs "lazily" so that arguments will be
#' # repeatedly evaluated
#' f <- partial(runif, n = rpois(1, 5))
#' f
#' f()
#' f()
#'
#' # You can override this by saying .lazy = FALSE
#' f <- partial(runif, n = rpois(1, 5), .lazy = FALSE)
#' f
#' f()
#' f()
#'
#' # This also means that partial works fine with functions that do
#' # non-standard evaluation
#' my_long_variable <- 1:10
#' plot2 <- partial(plot, my_long_variable)
#' plot2()
#' plot2(runif(10), type = "l")
partial <- function(...f, ..., .env = parent.frame(), .lazy = TRUE,
.first = TRUE) {
stopifnot(is.function(...f))
if (.lazy) {
fcall <- substitute(...f(...))
} else {
fcall <- make_call(substitute(...f), .args = list(...))
}
# Pass on ... from parent function
n <- length(fcall)
if (!.first && n > 1) {
tmp <- fcall[1]
tmp[[2]] <- quote(...)
tmp[seq(3, n + 1)] <- fcall[seq(2, n)]
names(tmp)[seq(3, n + 1)] <- names2(fcall)[seq(2, n)]
fcall <- tmp
} else {
fcall[[n + 1]] <- quote(...)
}
args <- list("..." = quote(expr = ))
new_function(args, fcall, .env)
}
make_call <- function(f, ..., .args = list()) {
if (is.character(f)) f <- as.name(f)
as.call(c(f, ..., .args))
}
purrr/R/prepend.R 0000644 0001762 0000144 00000001430 13102346452 013403 0 ustar ligges users #' Prepend a vector
#'
#' This is a companion to [append()] to help merging two
#' lists or atomic vectors. `prepend()` is a clearer semantic
#' signal than `c()` that a vector is to be merged at the beginning of
#' another, especially in a pipe chain.
#'
#' @param x the vector to be modified.
#' @param values to be included in the modified vector.
#' @param before a subscript, before which the values are to be appended.
#' @return A merged vector.
#' @export
#' @examples
#' x <- as.list(1:3)
#'
#' x %>% append("a")
#' x %>% prepend("a")
#' x %>% prepend(list("a", "b"), before = 3)
prepend <- function(x, values, before = 1) {
n <- length(x)
stopifnot(before > 0 && before <= n)
if (before == 1) {
c(values, x)
} else {
c(x[1:(before - 1)], values, x[before:n])
}
}
purrr/R/composition.R 0000644 0001762 0000144 00000015560 13124217327 014324 0 ustar ligges users #' Lift the domain of a function
#'
#' `lift_xy()` is a composition helper. It helps you compose
#' functions by lifting their domain from a kind of input to another
#' kind. The domain can be changed from and to a list (l), a vector
#' (v) and dots (d). For example, `lift_ld(fun)` transforms a
#' function taking a list to a function taking dots.
#'
#' The most important of those helpers is probably `lift_dl()`
#' because it allows you to transform a regular function to one that
#' takes a list. This is often essential for composition with purrr
#' functional tools. Since this is such a common function,
#' `lift()` is provided as an alias for that operation.
#'
#' @inheritParams as_vector
#' @param ..f A function to lift.
#' @param ... Default arguments for `..f`. These will be
#' evaluated only once, when the lifting factory is called.
#' @return A function.
#' @name lift
#' @seealso [invoke()]
NULL
#' @rdname lift
#' @section from ... to `list(...)` or `c(...)`:
#' Here dots should be taken here in a figurative way. The lifted
#' functions does not need to take dots per se. The function is
#' simply wrapped a function in [do.call()], so instead
#' of taking multiple arguments, it takes a single named list or
#' vector which will be interpreted as its arguments. This is
#' particularly useful when you want to pass a row of a data frame
#' or a list to a function and don't want to manually pull it apart
#' in your function.
#' @param .unnamed If `TRUE`, `ld` or `lv` will not
#' name the parameters in the lifted function signature. This
#' prevents matching of arguments by name and match by position
#' instead.
#' @export
#' @examples
#' ### Lifting from ... to list(...) or c(...)
#'
#' x <- list(x = c(1:100, NA, 1000), na.rm = TRUE, trim = 0.9)
#' lift_dl(mean)(x)
#'
#' # Or in a pipe:
#' mean %>% lift_dl() %>% invoke(x)
#'
#' # You can also use the lift() alias for this common operation:
#' lift(mean)(x)
#'
#' # Default arguments can also be specified directly in lift_dl()
#' list(c(1:100, NA, 1000)) %>% lift_dl(mean, na.rm = TRUE)()
#'
#' # lift_dl() and lift_ld() are inverse of each other.
#' # Here we transform sum() so that it takes a list
#' fun <- sum %>% lift_dl()
#' fun(list(3, NA, 4, na.rm = TRUE))
#'
#' # Now we transform it back to a variadic function
#' fun2 <- fun %>% lift_ld()
#' fun2(3, NA, 4, na.rm = TRUE)
#'
#' # It can sometimes be useful to make sure the lifted function's
#' # signature has no named parameters, as would be the case for a
#' # function taking only dots. The lifted function will take a list
#' # or vector but will not match its arguments to the names of the
#' # input. For instance, if you give a data frame as input to your
#' # lifted function, the names of the columns are probably not
#' # related to the function signature and should be discarded.
#' lifted_identical <- lift_dl(identical, .unnamed = TRUE)
#' mtcars[c(1, 1)] %>% lifted_identical()
#' mtcars[c(1, 2)] %>% lifted_identical()
lift <- function(..f, ..., .unnamed = FALSE) {
force(..f)
defaults <- list(...)
function(.x = list(), ...) {
if (.unnamed) {
.x <- unname(.x)
}
do.call("..f", c(.x, defaults, list(...)))
}
}
#' @rdname lift
#' @export
lift_dl <- lift
#' @rdname lift
#' @export
lift_dv <- function(..f, ..., .unnamed = FALSE) {
force(..f)
defaults <- list(...)
function(.x, ...) {
if (.unnamed) {
.x <- unname(.x)
}
.x <- as.list(.x)
do.call("..f", c(.x, defaults, list(...)))
}
}
#' @rdname lift
#' @section from `c(...)` to `list(...)` or `...`:
#' These factories allow a function taking a vector to take a list
#' or dots instead. The lifted function internally transforms its
#' inputs back to an atomic vector. purrr does not obey the usual R
#' casting rules (e.g., `c(1, "2")` produces a character
#' vector) and will produce an error if the types are not
#' compatible. Additionally, you can enforce a particular vector
#' type by supplying `.type`.
#' @export
#' @examples
#' #
#'
#'
#' ### Lifting from c(...) to list(...) or ...
#'
#' # In other situations we need the vector-valued function to take a
#' # variable number of arguments as with pmap(). This is a job for
#' # lift_vd():
#' pmap(mtcars, lift_vd(mean))
#'
#' # lift_vd() will collect the arguments and concatenate them to a
#' # vector before passing them to ..f. You can add a check to assert
#' # the type of vector you expect:
#' lift_vd(tolower, .type = character(1))("this", "is", "ok")
lift_vl <- function(..f, ..., .type) {
force(..f)
defaults <- list(...)
if (missing(.type)) .type <- NULL
function(.x = list(), ...) {
x <- as_vector(.x, .type)
do.call("..f", c(list(x), defaults, list(...)))
}
}
#' @rdname lift
#' @export
lift_vd <- function(..f, ..., .type) {
force(..f)
defaults <- list(...)
if (missing(.type)) .type <- NULL
function(...) {
x <- as_vector(list(...), .type)
do.call("..f", c(list(x), defaults))
}
}
#' @rdname lift
#' @section from list(...) to c(...) or ...:
#' `lift_ld()` turns a function that takes a list into a
#' function that takes dots. `lift_vd()` does the same with a
#' function that takes an atomic vector. These factory functions are
#' the inverse operations of `lift_dl()` and `lift_dv()`.
#'
#' `lift_vd()` internally coerces the inputs of `..f` to
#' an atomic vector. The details of this coercion can be controlled
#' with `.type`.
#'
#' @export
#' @examples
#' #
#'
#'
#' ### Lifting from list(...) to c(...) or ...
#'
#' # cross() normally takes a list of elements and returns their
#' # cartesian product. By lifting it you can supply the arguments as
#' # if it was a function taking dots:
#' cross_dots <- lift_ld(cross)
#' out1 <- cross(list(a = 1:2, b = c("a", "b", "c")))
#' out2 <- cross_dots(a = 1:2, b = c("a", "b", "c"))
#' identical(out1, out2)
#'
#' # This kind of lifting is sometimes needed for function
#' # composition. An example would be to use pmap() with a function
#' # that takes a list. In the following, we use some() on each row of
#' # a data frame to check they each contain at least one element
#' # satisfying a condition:
#' mtcars %>% pmap(lift_ld(some, partial(`<`, 200)))
#'
#' # Default arguments for ..f can be specified in the call to
#' # lift_ld()
#' lift_ld(cross, .filter = `==`)(1:3, 1:3) %>% str()
#'
#'
#' # Here is another function taking a list and that we can update to
#' # take a vector:
#' glue <- function(l) {
#' if (!is.list(l)) stop("not a list")
#' l %>% invoke(paste, .)
#' }
#'
#' \dontrun{
#' letters %>% glue() # fails because glue() expects a list}
#'
#' letters %>% lift_lv(glue)() # succeeds
lift_ld <- function(..f, ...) {
force(..f)
defaults <- list(...)
function(...) {
do.call("..f", c(list(list(...)), defaults))
}
}
#' @rdname lift
#' @export
lift_lv <- function(..f, ...) {
force(..f)
defaults <- list(...)
function(.x, ...) {
do.call("..f", c(list(as.list(.x)), defaults, list(...)))
}
}
purrr/R/when.R 0000644 0001762 0000144 00000005476 13124217327 012727 0 ustar ligges users #' Match/validate a set of conditions for an object and continue with the action
#' associated with the first valid match.
#'
#' `when` is a flavour of pattern matching (or an if-else abstraction) in
#' which a value is matched against a sequence of condition-action sets. When a
#' valid match/condition is found the action is executed and the result of the
#' action is returned.
#'
#' @param . the value to match against
#' @param ... formulas; each containing a condition as LHS and an action as RHS.
#' named arguments will define additional values.
#' @keywords internal
#' @return The value resulting from the action of the first valid
#' match/condition is returned. If no matches are found, and no default is
#' given, NULL will be returned.
#'
# @details condition-action sets are written as formulas with conditions as
# left-hand sides and actions as right-hand sides. A formula with only a
# right-hand will be treated as a condition which is always satisfied. For
# such a default case one can also omit the `~` symbol, but note that its
# value will then be evaluated. Any named argument will be made available in
# all conditions and actions, which is useful in avoiding repeated temporary
# computations or temporary assignments.
#
#' Validity of the conditions are tested with `isTRUE`, or equivalently
#' with `identical(condition, TRUE)`.
#' In other words conditions resulting in more than one logical will never
#' be valid. Note that the input value is always treated as a single object,
#' as opposed to the `ifelse` function.
#'
#' @examples
#' 1:10 %>%
#' when(
#' sum(.) <= 50 ~ sum(.),
#' sum(.) <= 100 ~ sum(.)/2,
#' ~ 0
#' )
#'
#' 1:10 %>%
#' when(
#' sum(.) <= x ~ sum(.),
#' sum(.) <= 2*x ~ sum(.)/2,
#' ~ 0,
#' x = 60
#' )
#'
#' iris %>%
#' subset(Sepal.Length > 10) %>%
#' when(
#' nrow(.) > 0 ~ .,
#' ~ iris %>% head(10)
#' )
#'
#' iris %>%
#' head %>%
#' when(nrow(.) < 10 ~ .,
#' ~ stop("Expected fewer than 10 rows."))
#' @export
when <- function(., ...) {
dots <- list(...)
names <- names(dots)
named <- if (is.null(names)) rep(FALSE, length(dots)) else names != ""
if (sum(!named) == 0)
stop("At least one matching condition is needed.",
call. = FALSE)
is_formula <-
vapply(dots,
function(dot) identical(class(dot), "formula"),
logical(1L))
env <- new.env(parent = parent.frame())
env[["."]] <- .
if (sum(named) > 0)
for (i in which(named))
env[[names[i]]] <- dots[[i]]
result <- NULL
for (i in which(!named)) {
if (is_formula[i]) {
action <- length(dots[[i]])
if (action == 2 || is_true(eval(dots[[i]][[2]], env, env))) {
result <- eval(dots[[i]][[action]], env, env)
break
}
} else {
result <- dots[[i]]
}
}
result
}
purrr/R/list-modify.R 0000644 0001762 0000144 00000005617 13137651733 014232 0 ustar ligges users #' Modify a list
#'
#' @description
#'
#' `list_modify()` and `list_merge()` recursively combine two lists, matching
#' elements either by name or position. If an sub-element is present in
#' both lists `list_modify()` takes the value from `y`, and `list_merge()`
#' concatenates the values together.
#'
#' `update_list()` handles formulas and quosures that can refer to
#' values existing within the input list. Note that this function
#' might be deprecated in the future in favour of a `dplyr::mutate()`
#' method for lists.
#'
#' @param .x List to modify.
#' @param ... New values of a list. Use `NULL` to remove values.
#' Use a formula to evaluate in the context of the list values.
#' These dots have [splicing semantics][rlang::dots_list].
#' @export
#' @examples
#' x <- list(x = 1:10, y = 4, z = list(a = 1, b = 2))
#' str(x)
#'
#' # Update values
#' str(list_modify(x, a = 1))
#' # Replace values
#' str(list_modify(x, z = 5))
#' str(list_modify(x, z = list(a = 1:5)))
#' # Remove values
#' str(list_modify(x, z = NULL))
#'
#' # Combine values
#' str(list_merge(x, x = 11, z = list(a = 2:5, c = 3)))
#'
#'
#' # All these functions take dots with splicing. Use !!! or UQS() to
#' # splice a list of arguments:
#' l <- list(new = 1, y = NULL, z = 5)
#' str(list_modify(x, !!! l))
#'
#' # In update_list() you can also use quosures and formulas to
#' # compute new values. This function is likely to be deprecated in
#' # the future
#' update_list(x, z1 = ~z[[1]])
#' update_list(x, z = rlang::quo(x + y))
list_modify <- function(.x, ...) {
dots <- dots_list(...)
list_recurse(.x, dots, function(x, y) y)
}
#' @export
#' @rdname list_modify
list_merge <- function(.x, ...) {
dots <- dots_list(...)
list_recurse(.x, dots, c)
}
list_recurse <- function(x, y, base_case) {
stopifnot(is.list(x), is.list(y))
if (is_empty(x)) {
return(y)
} else if (is_empty(y)) {
return(x)
}
x_names <- names(x)
y_names <- names(y)
if (!is_names(x_names) && !is_names(y_names)) {
for (i in rev(seq_along(y))) {
if (i <= length(x) && is_list(x[[i]]) && is_list(y[[i]])) {
x[[i]] <- list_recurse(x[[i]], y[[i]], base_case)
} else {
x[[i]] <- base_case(x[[i]], y[[i]])
}
}
} else if (is_names(x_names) && is_names(y_names)) {
for (nm in y_names) {
if (has_name(x, nm) && is_list(x[[nm]]) && is_list(y[[nm]])) {
x[[nm]] <- list_recurse(x[[nm]], y[[nm]], base_case)
} else {
x[[nm]] <- base_case(x[[nm]], y[[nm]])
}
}
} else {
stop("`x` and `y` must be either both named or both unnamed", call. = FALSE)
}
x
}
#' @rdname list_modify
#' @export
#' @usage NULL
update_list <- function(.x, ...) {
dots <- dots_list(...)
formulas <- map_lgl(dots, is_bare_formula, lhs = FALSE, scoped = TRUE)
dots <- map_if(dots, formulas, as_quosure)
dots <- map_if(dots, is_quosure, eval_tidy, data = .x)
list_recurse(.x, dots, function(x, y) y)
}
purrr/R/keep.R 0000644 0001762 0000144 00000002363 13102346452 012700 0 ustar ligges users #' Keep or discard elements using a predicate function.
#'
#' `keep` and `discard` are opposites. `compact` is a handy
#' wrapper that removes all elements that are `NULL`.
#'
#' These are usually called `select` or `filter` and `reject` or
#' `drop`, but those names are already taken. `keep` is similar to
#' [Filter()] but the argument order is more convenient, and the
#' evaluation of `.f` is stricter.
#'
#' @param .x A list or vector.
#' @param ... Additional arguments passed on to `.p`.
#' @inheritParams map_if
#' @export
#' @examples
#' rep(10, 10) %>%
#' map(sample, 5) %>%
#' keep(function(x) mean(x) > 6)
#'
#' # Or use a formula
#' rep(10, 10) %>%
#' map(sample, 5) %>%
#' keep(~ mean(.x) > 6)
#'
#' # Using a string instead of a function will select all list elements
#' # where that subelement is TRUE
#' x <- rerun(5, a = rbernoulli(1), b = sample(10))
#' x
#' x %>% keep("a")
#' x %>% discard("a")
keep <- function(.x, .p, ...) {
sel <- probe(.x, .p, ...)
.x[!is.na(sel) & sel]
}
#' @export
#' @rdname keep
discard <- function(.x, .p, ...) {
sel <- probe(.x, .p, ...)
.x[is.na(sel) | !sel]
}
#' @export
#' @rdname keep
compact <- function(.x, .p = identity) {
.f <- as_mapper(.p)
.x %>% discard(function(x) is_empty(.f(x)))
}
purrr/R/coercion.R 0000644 0001762 0000144 00000005671 13102346452 013562 0 ustar ligges users #' 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)
}
purrr/R/purrr.R 0000644 0001762 0000144 00000000134 13102444774 013126 0 ustar ligges users #' @keywords internal
#' @import rlang
#' @useDynLib purrr, .registration = TRUE
"_PACKAGE"
purrr/R/compose.R 0000644 0001762 0000144 00000000760 13102346452 013420 0 ustar ligges users #' Compose multiple functions
#'
#' @param ... n functions to apply in order from right to left.
#' @return A function
#' @export
#' @examples
#' not_null <- compose(`!`, is.null)
#' not_null(4)
#' not_null(NULL)
#'
#' add1 <- function(x) x + 1
#' compose(add1, add1)(8)
compose <- function(...) {
fs <- lapply(list(...), match.fun)
n <- length(fs)
last <- fs[[n]]
rest <- fs[-n]
function(...) {
out <- last(...)
for (f in rev(rest)) {
out <- f(out)
}
out
}
}
purrr/R/set_names.R 0000644 0001762 0000144 00000002035 13124216350 013723 0 ustar ligges users #' @title Set names in a vector
#'
#' @details
#' This is a snake case wrapper for [stats::setNames()], with
#' tweaked defaults, and stricter argument checking.
#'
#' @usage set_names(x, nm = x, ...)
#' @param x Vector to name
#' @param nm,... Vector of names, the same length as `x`.
#'
#' You can specify names in three ways:
#'
#' * If you do nothing, `x` will be named with itself
#'
#' * You can supply either a character vector to `nm` or individual
#' strings in to `...``
#'
#' * If `x` already has names, you can provide a function or formula
#' to transform the existing names.
#'
#' @return `.x` with the names attribute set.
#' @export
#' @examples
#' set_names(1:4, c("a", "b", "c", "d"))
#' set_names(1:4, letters[1:4])
#' set_names(1:4, "a", "b", "c", "d")
#'
#' # If the second argument is ommitted a vector is named with itself
#' set_names(letters[1:5])
#'
#' # Alternatively you can supply a function
#' set_names(1:10, ~ letters[seq_along(.)])
#' set_names(head(mtcars), toupper)
#' @name set_names
rlang::set_names
purrr/R/negate.R 0000644 0001762 0000144 00000000766 13102451525 013222 0 ustar ligges users #' Negate a predicate function.
#'
#' @inheritParams map_if
#' @inheritParams as_mapper
#' @return A new predicate function.
#' @export
#' @examples
#' negate("x")
#' negate(is.null)
#' negate(~ .x > 0)
#'
#' x <- transpose(list(x = 1:10, y = rbernoulli(10)))
#' x %>% keep("y") %>% length()
#' x %>% keep(negate("y")) %>% length()
#' # Same as
#' x %>% discard("y") %>% length()
negate <- function(.p, .default = FALSE) {
.p <- as_mapper(.p)
body(.p) <- expr({
! ( !! body(.p) )
})
.p
}
purrr/R/every-some.R 0000644 0001762 0000144 00000001361 13102346452 014044 0 ustar ligges users #' Do every or some elements of a list satisfy a predicate?
#'
#' @inheritParams map_if
#' @inheritParams map
#' @return A logical vector of length 1.
#' @export
#' @examples
#' x <- list(0, 1, TRUE)
#' x %>% every(identity)
#' x %>% some(identity)
#'
#' y <- list(0:10, 5.5)
#' y %>% every(is.numeric)
#' y %>% every(is.integer)
every <- function(.x, .p, ...) {
.p <- as_mapper(.p, ...)
for (i in seq_along(.x)) {
val <- .p(.x[[i]], ...)
if (is_false(val)) return(FALSE)
if (anyNA(val)) return(NA)
}
TRUE
}
#' @export
#' @rdname every
some <- function(.x, .p, ...) {
.p <- as_mapper(.p, ...)
for (i in seq_along(.x)) {
val <- .p(.x[[i]], ...)
if (is_true(val)) return(TRUE)
if (anyNA(val)) return(NA)
}
FALSE
}
purrr/R/transpose.R 0000644 0001762 0000144 00000003661 13123217241 013770 0 ustar ligges users #' Transpose a list.
#'
#' Transpose turns a list-of-lists "inside-out"; it turns a pair of lists into a
#' list of pairs, or a list of pairs into pair of lists. For example,
#' if you had a list of length n where each component had values `a` and
#' `b`, `transpose()` would make a list with elements `a` and
#' `b` that contained lists of length n. It's called transpose because
#' \code{x[[1]][[2]]} is equivalent to \code{transpose(x)[[2]][[1]]}.
#'
#' Note that `transpose()` is its own inverse, much like the
#' transpose operation on a matrix. You can get back the original
#' input by transposing it twice.
#'
#' @param .l A list of vectors to zip. The first element is used as the
#' template; you'll get a warning if a sub-list is not the same length as
#' the first element.
#' @param .names For efficiency, `transpose()` usually inspects the
#' first component of `.l` to determine the structure. Use `.names`
#' if you want to override this default.
#' @return A list with indexing transposed compared to `.l`.
#' @export
#' @examples
#' x <- rerun(5, x = runif(1), y = runif(5))
#' x %>% str()
#' x %>% transpose() %>% str()
#' # Back to where we started
#' x %>% transpose() %>% transpose() %>% str()
#'
#' # transpose() is useful in conjunction with safely() & quietly()
#' x <- list("a", 1, 2)
#' y <- x %>% map(safely(log))
#' y %>% str()
#' y %>% transpose() %>% str()
#'
#' # Use simplify_all() to reduce to atomic vectors where possible
#' x <- list(list(a = 1, b = 2), list(a = 3, b = 4), list(a = 5, b = 6))
#' x %>% transpose()
#' x %>% transpose() %>% simplify_all()
#'
#' # Provide explicit component names to prevent loss of those that don't
#' # appear in first component
#' ll <- list(
#' list(x = 1, y = "one"),
#' list(z = "deux", x = 2)
#' )
#' ll %>% transpose()
#' nms <- ll %>% map(names) %>% reduce(union)
#' ll %>% transpose(.names = nms)
transpose <- function(.l, .names = NULL) {
.Call(transpose_impl, .l, .names)
}
purrr/R/predicates.R 0000644 0001762 0000144 00000003105 13125254534 014076 0 ustar ligges users #' Test is an object is integer or double
#'
#' Numeric is used in three different ways in base R:
#' * as an alias for double (as in [as.numeric()])
#' * to mean either integer or double (as in [mode()])
#' * for something representable as numeric (as in [as.numeric()])
#' This function tests for the second, which is often not what you want
#' so these functions are deprecated.
#'
#' @export
#' @keywords internal
is_numeric <- function(x) {
warning("Deprecated", call. = FALSE)
is_integer(x) || is_double(x)
}
#' @export
#' @rdname is_numeric
is_scalar_numeric <- function(x) {
warning("Deprecated", call. = FALSE)
is_scalar_integer(x) || is_scalar_double(x)
}
# Re-exports from purrr ---------------------------------------------------
#' @export
rlang::is_bare_list
#' @export
rlang::is_bare_atomic
#' @export
rlang::is_bare_vector
#' @export
rlang::is_bare_double
#' @export
rlang::is_bare_integer
#' @export
rlang::is_bare_numeric
#' @export
rlang::is_bare_character
#' @export
rlang::is_bare_logical
#' @export
rlang::is_list
#' @export
rlang::is_atomic
#' @export
rlang::is_vector
#' @export
rlang::is_integer
#' @export
rlang::is_double
#' @export
rlang::is_character
#' @export
rlang::is_logical
#' @export
rlang::is_null
#' @export
rlang::is_function
#' @export
rlang::is_scalar_list
#' @export
rlang::is_scalar_atomic
#' @export
rlang::is_scalar_vector
#' @export
rlang::is_scalar_double
#' @export
rlang::is_scalar_character
#' @export
rlang::is_scalar_logical
#' @export
rlang::is_scalar_integer
#' @export
rlang::is_empty
#' @export
rlang::is_formula
purrr/R/lmap.R 0000644 0001762 0000144 00000007140 13171415127 012705 0 ustar ligges users #' Apply a function to list-elements of a list
#'
#' `lmap()`, `lmap_at()` and `lmap_if()` are similar to
#' `map()`, `map_at()` and `map_if()`, with the
#' difference that they operate exclusively on functions that take
#' \emph{and} return a list (or data frame). Thus, instead of mapping
#' the elements of a list (as in \code{.x[[i]]}), they apply a
#' function `.f` to each subset of size 1 of that list (as in
#' `.x[i]`). We call those those elements `list-elements`).
#'
#' Mapping the list-elements `.x[i]` has several advantages. It
#' makes it possible to work with functions that exclusively take a
#' list or data frame. It enables `.f` to access the attributes
#' of the encapsulating list, like the name of the components it
#' receives. It also enables `.f` to return a larger list than
#' the list-element of size 1 it got as input. Conversely, `.f`
#' can also return empty lists. In these cases, the output list is
#' reshaped with a different size than the input list `.x`.
#' @param .x A list or data frame.
#' @param .f A function that takes and returns a list or data frame.
#' @inheritParams map_if
#' @inheritParams map_at
#' @inheritParams map
#' @return If `.x` is a list, a list. If `.x` is a data
#' frame, a data frame.
#' @family map variants
#' @export
#' @examples
#' # Let's write a function that returns a larger list or an empty list
#' # depending on some condition. This function also uses the names
#' # metadata available in the attributes of the list-element
#' maybe_rep <- function(x) {
#' n <- rpois(1, 2)
#' out <- rep_len(x, n)
#' if (length(out) > 0) {
#' names(out) <- paste0(names(x), seq_len(n))
#' }
#' out
#' }
#'
#' # The output size varies each time we map f()
#' x <- list(a = 1:4, b = letters[5:7], c = 8:9, d = letters[10])
#' x %>% lmap(maybe_rep)
#'
#' # We can apply f() on a selected subset of x
#' x %>% lmap_at(c("a", "d"), maybe_rep)
#'
#' # Or only where a condition is satisfied
#' x %>% lmap_if(is.character, maybe_rep)
#'
#'
#' # A more realistic example would be a function that takes discrete
#' # variables in a dataset and turns them into disjunctive tables, a
#' # form that is amenable to fitting some types of models.
#'
#' # A disjunctive table contains only 0 and 1 but has as many columns
#' # as unique values in the original variable. Ideally, we want to
#' # combine the names of each level with the name of the discrete
#' # variable in order to identify them. Given these requirements, it
#' # makes sense to have a function that takes a data frame of size 1
#' # and returns a data frame of variable size.
#' disjoin <- function(x, sep = "_") {
#' name <- names(x)
#' x <- as.factor(x[[1]])
#'
#' out <- lapply(levels(x), function(level) {
#' as.numeric(x == level)
#' })
#'
#' names(out) <- paste(name, levels(x), sep = sep)
#' tibble::as_tibble(out)
#' }
#'
#' # Now, we are ready to map disjoin() on each categorical variable of a
#' # data frame:
#' iris %>% lmap_if(is.factor, disjoin)
#' mtcars %>% lmap_at(c("cyl", "vs", "am"), disjoin)
lmap <- function(.x, .f, ...) {
.x %>% lmap_at(seq_along(.x), .f, ...)
}
#' @rdname lmap
#' @export
lmap_if <- function(.x, .p, .f, ...) {
sel <- probe(.x, .p) %>% which()
.x %>% lmap_at(sel, .f, ...)
}
#' @rdname lmap
#' @export
lmap_at <- function(.x, .at, .f, ...) {
if (is_formula(.f)) {
.f <- as_mapper(.f, ...)
}
sel <- inv_which(.x, .at)
out <- vector("list", length(.x))
for (i in seq_along(.x)) {
res <-
if (sel[[i]]) {
.f(.x[i], ...)
} else {
.x[i]
}
stopifnot(is.list(res))
out[[i]] <- res
}
flatten(out) %>% maybe_as_data_frame(.x)
}
purrr/vignettes/ 0000755 0001762 0000144 00000000000 13171650761 013443 5 ustar ligges users purrr/vignettes/other-langs.Rmd 0000644 0001762 0000144 00000004060 13124217327 016325 0 ustar ligges users ---
title: "Functional programming in other languages"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Functional programming in other languages}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
purrr draws inspiration from many related tools:
* List operations defined in the Haskell [prelude][haskell]
* Scala's [list methods][scala].
* Functional programming librarys for javascript:
[underscore.js](http://underscorejs.org),
[lodash](https://lodash.com) and
[lazy.js](http://danieltao.com/lazy.js/).
* [rlist](http://renkun.me/rlist/), another R package to support working
with lists. Similar goals but somewhat different philosophy.
However, the goal of purrr is not to try and simulate a purer functional programming language in R; we don't want to implement a second-class version of Haskell in R. The goal is to give you similar expressiveness to an FP language, while allowing you to write code that looks and works like R:
* Instead of point free (tacit) style, we use the pipe, `%>%`, to write code
that can be read from left to right.
* Instead of currying, we use `...` to pass in extra arguments.
* Anonymous functions are verbose in R, so we provide two convenient shorthands.
For unary functions, `~ .x + 1` is equivalent to `function(.x) .x + 1`.
For chains of transformations functions, `. %>% f() %>% g()` is
equivalent to `function(.) . %>% f() %>% g()` (this shortcut is provided
by magrittr).
* R is weakly typed, so we need `map` variants that describe the output type
(like `map_int()`, `map_dbl()`, etc) because don't the return type of `.f`.
* R has named arguments, so instead of providing different functions for
minor variations (e.g. `detect()` and `detectLast()`) we use a named
argument, `.right`. Type-stable functions are easy to reason about so
additional arguments will never change the type of the output.
[scala]:http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.List
[haskell]:http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#g:11
purrr/README.md 0000644 0001762 0000144 00000004475 13147024610 012713 0 ustar ligges users
purrr
======================================================
[](http://cran.r-project.org/package=purrr) [](https://travis-ci.org/tidyverse/purrr) [](https://codecov.io/github/tidyverse/purrr?branch=master)
Overview
--------
purrr enhances R's functional programming (FP) toolkit by providing a complete and consistent set of tools for working with functions and vectors. If you've never heard of FP before, the best place to start is the family of `map()` functions which allow you to replace many for loops with code that is both more succinct and easier to read. The best place to learn about the `map()` functions is the [iteration chapter](http://r4ds.had.co.nz/iteration.html) in R for data science.
Installation
------------
``` r
# The easiest way to get purrr is to install the whole tidyverse:
install.packages("tidyverse")
# Alternatively, install just purrr:
install.packages("purrr")
# Or the the development version from GitHub:
# install.packages("devtools")
devtools::install_github("tidyverse/purrr")
```
Usage
-----
The following example uses purrr to solve a fairly realistic problem: split a data frame into pieces, fit a model to each piece, compute the summary, then extract the R2.
``` r
library(purrr)
mtcars %>%
split(.$cyl) %>% # from base R
map(~ lm(mpg ~ wt, data = .)) %>%
map(summary) %>%
map_dbl("r.squared")
#> 4 6 8
#> 0.5086326 0.4645102 0.4229655
```
This example illustrates some of the advantages of purrr functions over the equivalents in base R:
- The first argument is always the data, so purrr works naturally with the pipe.
- All purrr functions are type-stable. They always return the advertised output type (`map()` returns lists; `map_dbl()` returns double vectors), or they throw an errror.
- All `map()` functions either accept function, formulas (used for succinctly generating anonymous functions), a character vector (used to extract components by name), or a numeric vector (used to extract by position).
purrr/MD5 0000644 0001762 0000144 00000015176 13171733547 011761 0 ustar ligges users 059324d1413bf11790eebfcc85afa281 *DESCRIPTION
d32239bcb673463ab874e80d47fae504 *LICENSE
b8b691d3ec56c2995d0eed7679ea7311 *NAMESPACE
461cec5f26550acd7a7153013ca2d401 *NEWS.md
d7434232639ef496b59a8863d526f4d5 *R/along.R
48b5ad68f9c3a686e0e5d78499feeec7 *R/arrays.R
dfc2fc05065d029dfbf897c83b347bd2 *R/as_mapper.R
f0ec1ea254e478033f6d34fb33405499 *R/coerce.R
78c61636c1cf9295234fd97e97662b5c *R/coercion.R
7cad8d9769011f8e8becd44c0c046905 *R/compose.R
60109119d2926aa5eb8d0b25995b7103 *R/composition.R
5a1819f7c6a77d026fd8a8dd85af818d *R/cross.R
42de484120cddf450bfaefe35ea0a2be *R/depth.R
fb21301f998ee034966f53c6a46453ad *R/every-some.R
4de7e21a61ef6ef0fa1a00c2522d2547 *R/find-position.R
2dd0023bfec19b388810951407f922c9 *R/flatten.R
aa1335a2e02edc206e9388d0f002125f *R/head-tail.R
8fcc2f91aed2123309302e0acad47189 *R/imap.R
72aad2226d57cf651c1fc9e25af870bd *R/invoke.R
4fc05f9f9e735ec87fb0f64bcaecde91 *R/keep.R
bebdbf11ecf1702b245387b214e1e46c *R/list-modify.R
36e036bfe9a070a893b9dda9f0cf1d09 *R/lmap.R
fc88d69357fe258dd5cdfebe943c619d *R/map.R
b84099b9b82c8ba315f0d8599bb3c4c1 *R/map2-pmap.R
e420126f44f750c89a79894bef58d9fe *R/modify.R
a848e46817874cd07d6f725c7fd112f2 *R/negate.R
ee7ea8075ca7b0f200e8cdbb6947f303 *R/output.R
46e6971d5cab3972e96f537c953d1ccb *R/partial.R
29a5243a6ac22ef61660fa10619167aa *R/predicates.R
b35704dde8e2ca4e17c830d75a1c44fd *R/prepend.R
ab9be6571c4276e9e8260d553248d8b1 *R/purrr.R
e19e96be648daac4265a5da6bd4cdc2b *R/reduce.R
8e41a48c6995da83d85d3407bde4477d *R/rerun.R
421ca7ab2db6ed008fba8b5558674827 *R/set_names.R
35b5580292c6c4227b218128bdd7da85 *R/splice.R
f6afdc22a2adf80cede2a04ec0536488 *R/transpose.R
b5ac331a5179d8aeae4b9e70cf003177 *R/utils.R
3011f46c733271400987666dc871373d *R/when.R
d00538240fc18c7c44547f5fb7ab04ad *README.md
ba009ec33d50810392dbb70cf5b9b5dc *build/vignette.rds
d0a12786bea51ec8b9d57563e6edacd8 *inst/doc/other-langs.Rmd
bf6d0d378ac18f2b89f70758cd62aedc *inst/doc/other-langs.html
a1af9c3fdda713bc8e76b3049e14ec9c *man/accumulate.Rd
31bf146d7265441ae4e72caa8c366f6c *man/along.Rd
a5fb7427a5582b7f26fb4628b0285fb3 *man/array-coercion.Rd
7bf23b591562d3cdb2dd0015350c2a08 *man/as_mapper.Rd
e7f26ac9b0e6831a8e74e8bb6c4c0849 *man/as_vector.Rd
ebc8d00b74c57e535a75097d5b3efb73 *man/compose.Rd
1079d4e27459673cef82d40c1c3ff770 *man/cross.Rd
768621965a402f6899719bed8b04dfa0 *man/detect.Rd
ed53201c56c68c61eaa8cba9836027dc *man/every.Rd
1d60cfe447b30937913953382e194602 *man/figures/logo.png
f936d540f0f4c19df702b72e57b1db0f *man/flatten.Rd
2d83b8c056befed23e23c5fa0a7e2901 *man/get-attr.Rd
45f2c89c9029e869a44213e3ecfdb393 *man/has_element.Rd
99e9c4dc80463cc0440f53cd30950239 *man/head_while.Rd
72c05631b758873c13c62dd9fffc9ea4 *man/imap.Rd
8d8d59bf99551df5138fdc384f0764bb *man/invoke.Rd
f4654f144511be7d26d7e88f01c5c5c4 *man/is_numeric.Rd
ed663b2e2ea63c371e2aae84526b8bbf *man/keep.Rd
aaf7edad66c589562b6542de0ab1fa14 *man/lift.Rd
91c2686573f852772455c3173a1d0730 *man/list_modify.Rd
7cf636dd7f4135242360f83bc6bcb2de *man/lmap.Rd
b59015aef9c5662cdc8664a444e67fd2 *man/map.Rd
3730114285585c293188cf3472480a42 *man/map2.Rd
a5a848f017dc57f6a32984a0be5688c9 *man/modify.Rd
c6f50cc4df88e89d974ae859659ac48a *man/negate.Rd
41960071b98ae08db2626bf0176474ad *man/null-default.Rd
fff26baafa6d12a021a1eccc418b98d1 *man/partial.Rd
a64a7ea44fcaa33c2d3ad0f7909cbc3e *man/pipe.Rd
6afcb054faa852df21065f001191d6b8 *man/pluck.Rd
c2e38b7a30f26c4ea0435141446254e4 *man/prepend.Rd
1d2d8fea562ac7cf11eca1c642a81a6b *man/purrr-package.Rd
f34d8370301c799a28aa8307b6394911 *man/rbernoulli.Rd
2df5a87d08db6c4b73447fa625ea444d *man/rdunif.Rd
a899f7774548591e1c94c14c593b42cc *man/reduce.Rd
50df6a47659d7e3831a580ff7b008b27 *man/reexports.Rd
028392c9b567bf0e18039256b65d54df *man/rerun.Rd
ed9a095395b1cf9025495309eb307ede *man/safely.Rd
1833c1e1d1e45f2a8fa6843672c92a8e *man/set_names.Rd
d198d9d3ffebb0bdca5968a34b70ccf4 *man/splice.Rd
987b0b3f1704943ddbad57e9e2c21e8d *man/transpose.Rd
5cf4da2f617677a3ab17aea0e6dcfcfd *man/vec_depth.Rd
476045071aee1d250fccf59f5bab87f9 *man/when.Rd
ff2850c9a8f09363bbcb7439f1a55e70 *src/backports.c
3a1ddd472e6b17c3679467cbd3409b10 *src/backports.h
74a87782ba250e74b5d1830e8864fb84 *src/coerce.c
6c50acdeb1bbbf26649665739256d211 *src/coerce.h
badf005339bec33e87c784155c10f8a3 *src/extract.c
9106493550bd308acceb970096f8a587 *src/flatten.c
829d93cfd9ef1890b0c06c402f5cb8ce *src/init.c
a516fbb25d2cce016910df6950e1ff4a *src/map.c
daa7efc01a8489ded96d8c0863e69b0f *src/map.h
7a43db39af0dbbbe838dd5204ee7e29a *src/transpose.c
8e9d16c5c6aedcc157783b13df5b9db0 *tests/testthat.R
ef7812928b1473706a682fd6948e9f98 *tests/testthat/test-along.R
dcbf48a2fbdba7f91e3880f7a0064701 *tests/testthat/test-arrays.R
f27cabfbbf69b64d662b72644ea39538 *tests/testthat/test-as-mapper.R
59bad9250777fd79da0f44408d7191d2 *tests/testthat/test-coerce.R
297f88674faf22665a960389c8fda450 *tests/testthat/test-compose.R
b3708b23403b14b7c15db43e810029e8 *tests/testthat/test-composition.R
ca3d5a991b34b91ec6cff68a6884cad1 *tests/testthat/test-cross.R
c548d3237286977e949b2b665b8a5792 *tests/testthat/test-depth.R
a22e930c9691c0655ba9c5fe94866adc *tests/testthat/test-every-some.R
14532ea09ed8dfca0d19e65a4f1d7c41 *tests/testthat/test-find-position.R
6857d64342f75696d507790e01efef80 *tests/testthat/test-flatten.R
fb8adbe7e936c917da7db33858f2df4a *tests/testthat/test-head-tail.R
d4c61639a083a3ec2dddc8a7cf632343 *tests/testthat/test-imap.R
5ffc01118f1e0f07b9c0d90cfcdce1cb *tests/testthat/test-invoke.R
1aad6ffcce7a01de2c838b1f475bd78d *tests/testthat/test-list-modify-update.R
325bad5b7fa5a5a642da9ce50753e437 *tests/testthat/test-lmap.R
c70e2e34a59f3a5986b1f2ba2a0128a3 *tests/testthat/test-map.R
2b67c975d77b2e433f0ad8ea693e0e47 *tests/testthat/test-map2.R
8c865f413f11c6a265d15e1f842df9b4 *tests/testthat/test-map_n.R
93bd710e49b0ae1af8dd27d021ddc39d *tests/testthat/test-modify.R
ce1638acb8d41d5bfe2fc29877484b8a *tests/testthat/test-negate.R
192dfcaa7553057ad63e307c8a474e5b *tests/testthat/test-output.R
d772931501f353938cd4f19fef56df05 *tests/testthat/test-partial.R
a3d9e4801e73a775b96e6e3c433285e5 *tests/testthat/test-pluck.R
ea532e7f3b213aff3ad4f66c6a23b7b4 *tests/testthat/test-predicates.R
b91718777144b4a6ad6d8ca71585a67c *tests/testthat/test-prepend.R
98068929576937ae3a307a5daa19a9fb *tests/testthat/test-recycle_args.R
273e7c7e8c27c6d25e8dae194f435de6 *tests/testthat/test-reduce.R
e34255b41581de0f6faf55eadd8cfbdb *tests/testthat/test-rerun.R
0eb43c0afc550daeead9401117b73f03 *tests/testthat/test-simplify.R
3f87bdf05305f94b2b5012ecdaabbb30 *tests/testthat/test-splice.R
0d12dc2b093f55b36f46c31a4c69dacb *tests/testthat/test-transpose.R
6069a05aa8cef11b1ca0a42d6067e7cb *tests/testthat/test-utils.R
7125b68b593f2e86140faabd4e8586ee *tests/testthat/test-when.R
d0a12786bea51ec8b9d57563e6edacd8 *vignettes/other-langs.Rmd
purrr/build/ 0000755 0001762 0000144 00000000000 13171650761 012532 5 ustar ligges users purrr/build/vignette.rds 0000644 0001762 0000144 00000000341 13171650761 015067 0 ustar ligges users mP0?Ix)/x%!cxIv}k