purrr Exercises in R: 50 Real Practice Problems

Fifty practice problems on purrr: map family, walk, reduce, safely/possibly, predicate variants, and many-models. Hidden solutions.

RRun this once before any exercise
library(purrr) library(dplyr) library(tibble) library(broom) library(readr) library(tidyr)

  

Section 1. map family basics (10 problems)

Exercise 1.1: map a function

Difficulty: Beginner. Square each of 1:5 returning a list.

Show solution
RInteractive R
map(1:5, ~ .x^2)

  

Exercise 1.2: map_dbl

Difficulty: Beginner. Same but return numeric.

Show solution
RInteractive R
map_dbl(1:5, ~ .x^2)

  

Exercise 1.3: map_chr

Difficulty: Beginner. Convert numbers to strings.

Show solution
RInteractive R
map_chr(1:5, ~ paste0("v_", .x))

  

Exercise 1.4: map_int

Difficulty: Beginner. Length of each list element.

Show solution
RInteractive R
map_int(list(1:3, 1:5, 1), length)

  

Exercise 1.5: map_lgl

Difficulty: Intermediate. Are means positive?

Show solution
RInteractive R
map_lgl(list(c(1,-2,3), c(-1,-2), c(5)), ~ mean(.x) > 0)

  

Exercise 1.6: map_dfr

Difficulty: Intermediate. Build a tibble from a list of vectors.

Show solution
RInteractive R
map_dfr(1:3, ~ tibble(n = .x, sq = .x^2))

  

Exercise 1.7: map_dfc

Difficulty: Advanced. Combine columnwise.

Show solution
RInteractive R
map_dfc(c("a","b","c"), ~ tibble(!!.x := 1:3))

  

Exercise 1.8: map with named list

Difficulty: Intermediate. Apply mean per element.

Show solution
RInteractive R
lst <- list(a = 1:5, b = 6:10, c = 11:15) map_dbl(lst, mean)

  

Exercise 1.9: map a function with extra args

Difficulty: Intermediate. Pass na.rm = TRUE.

Show solution
RInteractive R
lst <- list(c(1,NA,3), c(NA,5,6)) map_dbl(lst, mean, na.rm = TRUE)

  

Exercise 1.10: map with anonymous function

Difficulty: Intermediate. Use \() shorthand (R 4.1+).

Show solution
RInteractive R
map_dbl(1:5, \(x) x * 10)

  

Section 2. map2 and pmap (8 problems)

Exercise 2.1: map2_dbl

Difficulty: Intermediate. Element-wise x^y.

Show solution
RInteractive R
map2_dbl(c(2,3,4), c(1,2,3), ~ .x^.y)

  

Exercise 2.2: map2_chr

Difficulty: Intermediate. Combine two vectors into formatted strings.

Show solution
RInteractive R
map2_chr(c("Alice","Bob"), c(30, 25), ~ paste(.x, "is", .y))

  

Exercise 2.3: pmap with three vectors

Difficulty: Advanced. Compute x*y + z.

Show solution
RInteractive R
pmap_dbl(list(1:3, 4:6, 7:9), ~ ..1 * ..2 + ..3)

  

Exercise 2.4: pmap with a tibble

Difficulty: Advanced. Treat each row as args.

Show solution
RInteractive R
df <- tibble(x = 1:3, y = 4:6) pmap_dbl(df, ~ ..1 + ..2)

  

Exercise 2.5: imap (with index)

Difficulty: Intermediate. map with the index/name.

Show solution
RInteractive R
imap_chr(c("a","b","c"), ~ paste0(.y, ":", .x))

  

Exercise 2.6: walk for side effects

Difficulty: Intermediate. Print each element.

Show solution
RInteractive R
walk(c("a","b","c"), print)

  

Exercise 2.7: walk2

Difficulty: Intermediate. Save plots per group.

Show solution
RInteractive R
# walk2(filenames, plots, ggsave) # demo signature walk2(c("v1","v2"), c(10, 20), ~ message(.x, ": ", .y))

  

Exercise 2.8: pwalk

Difficulty: Advanced. Iterate over multiple parallel inputs for side effects.

Show solution
RInteractive R
pwalk(list(c("a","b"), c(1,2)), ~ message(..1, "=", ..2))

  

Section 3. reduce and accumulate (6 problems)

Exercise 3.1: Sum with reduce

Difficulty: Beginner.

Show solution
RInteractive R
reduce(1:10, `+`)

  

Exercise 3.2: Reduce with accumulator

Difficulty: Intermediate. Running max.

Show solution
RInteractive R
accumulate(c(3, 1, 4, 1, 5, 9, 2, 6), max)

  

Exercise 3.3: Reduce list of data frames

Difficulty: Advanced. Inner-join three.

Show solution
RInteractive R
dfs <- list( tibble(id = 1:3, a = 10:12), tibble(id = 1:3, b = 20:22), tibble(id = 1:3, c = 30:32) ) reduce(dfs, inner_join, by = "id")

  

Exercise 3.4: Reduce with init

Difficulty: Intermediate. Sum starting from 100.

Show solution
RInteractive R
reduce(1:5, `+`, .init = 100)

  

Exercise 3.5: Reduce right-to-left

Difficulty: Advanced.

Show solution
RInteractive R
reduce(c("a","b","c"), paste, .dir = "backward")

  

Exercise 3.6: Build cumulative product

Difficulty: Intermediate.

Show solution
RInteractive R
accumulate(1:5, `*`)

  

Section 4. Predicates and filtering (8 problems)

Exercise 4.1: keep

Difficulty: Beginner. Keep list elements with mean > 5.

Show solution
RInteractive R
keep(list(c(1,2,3), c(7,8,9), c(4,5)), ~ mean(.x) > 5)

  

Exercise 4.2: discard

Difficulty: Beginner. Drop NAs from a list.

Show solution
RInteractive R
discard(list(1, NA, 3, NA, 5), is.na)

  

Exercise 4.3: detect (first match)

Difficulty: Intermediate. First element with length > 2.

Show solution
RInteractive R
detect(list(c(1), c(2,3), c(4,5,6)), ~ length(.x) > 2)

  

Exercise 4.4: detect_index

Difficulty: Intermediate. Position of first match.

Show solution
RInteractive R
detect_index(c(2, 4, 6, 7, 8), ~ .x %% 2 == 1)

  

Exercise 4.5: every

Difficulty: Intermediate. Are all elements positive?

Show solution
RInteractive R
every(c(1, 2, 3), ~ .x > 0)

  

Exercise 4.6: some

Difficulty: Intermediate. Any element negative?

Show solution
RInteractive R
some(c(1, -2, 3), ~ .x < 0)

  

Exercise 4.7: keep_at

Difficulty: Advanced. Keep specific named elements of a list.

Show solution
RInteractive R
keep_at(list(a = 1, b = 2, c = 3), c("a","c"))

  

Exercise 4.8: compact

Difficulty: Intermediate. Drop NULL/empty elements.

Show solution
RInteractive R
compact(list(1, NULL, 3, NULL, 5))

  

Section 5. Errors and safety (6 problems)

Exercise 5.1: safely

Difficulty: Intermediate. Wrap log so it never errors.

Show solution
RInteractive R
safe_log <- safely(log) result <- map(c(10, -1, 5), safe_log) result

  

Exercise 5.2: Extract results from safely

Difficulty: Intermediate. Pluck results.

Show solution
RInteractive R
out <- map(list(10, "x", 5), safely(log)) map(out, "result")

  

Exercise 5.3: possibly

Difficulty: Intermediate. Replace failures with default.

Show solution
RInteractive R
poss_log <- possibly(log, otherwise = NA_real_) map_dbl(list(10, "x", 5), poss_log)

  

Exercise 5.4: quietly

Difficulty: Advanced. Capture warnings/messages.

Show solution
RInteractive R
q_log <- quietly(log) q_log(-1)

  

Exercise 5.5: Handle a batch of file reads

Difficulty: Advanced. Read CSVs, capture errors.

Show solution
RInteractive R
files <- c("a.csv","missing.csv","b.csv") results <- map(files, safely(readr::read_csv)) ok_results <- compact(map(results, "result"))

  

Exercise 5.6: rate_backoff for retries

Difficulty: Advanced. Retry up to 3 times.

Show solution
RInteractive R
maybe_fail <- function() if (runif(1) < 0.5) stop("err") else 1 purrr::insistently(maybe_fail, rate = rate_backoff(max_times = 3))()

  

Section 6. Many models and pipelines (4 problems)

Exercise 6.1: lm per group

Difficulty: Advanced. Fit lm per Species.

Show solution
RInteractive R
iris |> group_by(Species) |> tidyr::nest() |> mutate(model = map(data, ~ lm(Sepal.Length ~ Petal.Length, data = .x)))

  

Exercise 6.2: Tidy results

Difficulty: Advanced. Get coefficient tibbles per group.

Show solution
RInteractive R
iris |> group_by(Species) |> tidyr::nest() |> mutate(model = map(data, ~ lm(Sepal.Length ~ Petal.Length, data = .x)), tidy = map(model, broom::tidy)) |> tidyr::unnest(tidy)

  

Exercise 6.3: Per-group R-squared

Difficulty: Advanced.

Show solution
RInteractive R
iris |> group_by(Species) |> tidyr::nest() |> mutate(r2 = map_dbl(data, ~ summary(lm(Sepal.Length ~ Petal.Length, data = .x))$r.squared))

  

Exercise 6.4: Predict with each model

Difficulty: Advanced. Add fitted values back.

Show solution
RInteractive R
iris |> group_by(Species) |> tidyr::nest() |> mutate(model = map(data, ~ lm(Sepal.Length ~ Petal.Length, data = .x)), fits = map2(model, data, predict))

  

Section 7. Composition and currying (8 problems)

Exercise 7.1: compose

Difficulty: Advanced. Compose abs(log(x)).

Show solution
RInteractive R
fn <- compose(abs, log) fn(-2.7)

  

Exercise 7.2: partial application

Difficulty: Advanced. Make a paste with fixed sep.

Show solution
RInteractive R
paste_dash <- partial(paste, sep = "-") paste_dash("a","b","c")

  

Exercise 7.3: Negate

Difficulty: Intermediate. Invert a predicate.

Show solution
RInteractive R
not_null <- negate(is.null) not_null(NULL) # FALSE not_null(1) # TRUE

  

Exercise 7.4: list_modify

Difficulty: Advanced. Update list elements.

Show solution
RInteractive R
list_modify(list(a = 1, b = 2), b = 99, c = 3)

  

Exercise 7.5: list_flatten

Difficulty: Intermediate.

Show solution
RInteractive R
list_flatten(list(list(1, 2), list(3, 4)))

  

Exercise 7.6: list_rbind

Difficulty: Intermediate. Modern alternative to map_dfr.

Show solution
RInteractive R
list_rbind(list(tibble(x = 1), tibble(x = 2)))

  

Exercise 7.7: pluck

Difficulty: Intermediate. Deep extraction from nested list.

Show solution
RInteractive R
nested <- list(a = list(b = list(c = 42))) pluck(nested, "a", "b", "c")

  

Exercise 7.8: as_mapper string shortcut

Difficulty: Advanced. Use a string to access by name.

Show solution
RInteractive R
lst <- list(list(name = "A"), list(name = "B")) map_chr(lst, "name")

  

What to do next

  • R-Functional-Programming-Exercises (coming), base R FP idioms.
  • tidyverse-Exercises (shipped), purrr inside larger pipelines.
  • Apply-Family-Exercises (coming), purrr alternatives.