Equivalent of `break` in purrr::map

  • A+
Category:Languages

Say I want to run a loop until a condition is met, at which point the result is saved and the loop exits:

library(tidyverse)  for (i in 1:5) {    df <- iris %>% select(i) %>% head(2)    if (names(df) == "Petal.Width") {     out <- df     break     } }  out 

How can I rewrite this using purr::map without having to evaluate each i?

Doing the following gives the result I need, but has to evaluate 5 times, whereas the for loop only 3 times:

fun <- function(x) {    df <- iris %>% select(x) %>% head(2)    if (names(df) == "Petal.Width") {   return(df)   } }  map_df(1:5, fun) 

 


1) callCC can be used to get this effect:

callCC(function(k) {   fun2 <- function(x) {     print(x) # just to show that x = 5 is never run     df <- iris %>% select(x) %>% head(2)     if (names(df) == "Petal.Width") k(df)   }   map_df(1:5, fun2) }) 

giving:

[1] 1 [1] 2 [1] 3 [1] 4   Petal.Width 1         0.2 2         0.2 

1a) If it is important to use fun without change then try this instead:

callCC(function(k) map_df(1:5, ~ if (!is.null(df <- fun(.x))) k(df))) 

2) purrr::reduce An alternative is to use reduce from purrr (or Reduce from base R):

f <- function(x, y) if (is.null(x)) fun(y) else x reduce(1:5, f, .init = NULL) 

This is not as good as (1) and (1a) from the viewpoint that it will still involve iterating over each element of 1:5 but will only invoke fun for 1:4. In contrast (1) and (1a) actually return after running fun or fun2 on 4.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: