purrr::map中类似于"break"的功能是什么?

14

假设我想运行一个循环,直到满足某个条件,此时结果被保存并退出循环:

library(tidyverse)

for (i in 1:5) {

  df <- iris %>% select(i) %>% head(2)

  if (names(df) == "Petal.Width") {
    out <- df
    break 

  }
}

out

如何使用purr::map重写此内容,而不必评估每个i?

做以下操作可以得到我需要的结果,但必须评估5次,而for循环只有3次:

fun <- function(x) {

  df <- iris %>% select(x) %>% head(2)

  if (names(df) == "Petal.Width") {
  return(df)
  }
}

map_df(1:5, fun)

3
如果你需要一个“break”,那么你将无法使用“map”。这些“map”函数被优化为返回与输入长度相同的向量。它们不是循环的替代品,只是用于循环的最常见情况的便利函数。 - MrFlick
可能有一种hacky的方法可以在函数内部使用 purrr::possiblypurrr::safely 并带有一个 stop 调用,但正如@KonradRudolph在下面所说的那样,这可能不是这些辅助函数的最佳用途或目的。 - camille
2个回答

15

没有等价物。事实上,使map(和类似函数)在可读性方面比通用循环更加出色的一件事是它们具有完全可预测的行为:对于每个元素,它们将恰好执行一次函数,没有例外(除非,嗯,有异常:您可以通过stop引发条件以短路执行,但这非常罕见,不建议这样做)。

相反,你的情况并不需要使用map,而是需要类似于purrr::keeppurrr::reduce的东西。

可以这样理解:mapreduce等是抽象化,对应于更一般的for循环的特定特殊情况。它们的目的是清楚地表明正在处理哪种特殊情况。作为程序员,你的任务就是找到正确的抽象。

在你的特定情况下,我可能会完全重写语句,使用dplyr,因此很难给出“最佳”purrr解决方案:最好的解决方案是不使用purrr。尽管如此,你可以使用purrr::detect,如下所示:

names(iris) %>%
    detect(`==`, 'Sepal.Width') %>%
    `[`(iris, .) %>%
    head(2)

或者

seq_along(iris) %>%
    detect(~ names(iris[.x]) == 'Sepal.Width') %>%
    `[`(iris, .) %>%
    head(2)

...但实际上,以下是用于比较的dplyr:

iris %>%
    select(Sepal.Width) %>%
    head(2)

1
趣闻时间,我目前正在编写一段代码,它恰好做了我所反对的事情:在 lapply 内部发出一个条件信号。我认为在我的情况下这是合适的,但我并不确定。 - Konrad Rudolph
你能帮忙使用 keepreduce 重写一下吗? - Shinobi_Atobe
@Shinobi_Atobe 请查看修订后的答案。 - Konrad Rudolph
谢谢- 实际上这是一个网络爬虫练习,我正在爬取每个页面上的多个表格并检查是否满足条件。它太复杂了,无法发布,因此我将其简化为我的问题实质。 - Shinobi_Atobe

2

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)
})

提供:

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

1a) 如果必须使用不改变的fun,请尝试使用以下方法:

最初的回答:

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

2) purrr::reduce 的另一种方法是使用 purrr 中的 reduce(或基础 R 中的 Reduce):


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

这不如从1和1a的角度来看好,因为它仍然涉及迭代1:5中的每个元素,但仅为1:4调用fun。 相比之下,(1)和(1a)实际上在运行funfun2后返回4。"最初的回答"。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接