【问题标题】:What is the best way to pass a list of functions as argument?将函数列表作为参数传递的最佳方法是什么?
【发布时间】:2018-06-09 04:25:45
【问题描述】:

我正在寻找有关将函数列表作为参数传递的最佳编码方式的建议。

我想做的事:

我想将一个函数列表作为参数传递,以将它们应用于特定输入。并根据这些给出输出名称。

一个“可重现”的例子

input = 1:5

要传递的函数是meanmin

预计来电:

foo(input, something_i_ask_help_for)

预期输出:

list(mean = 3, min = 1)

如果不是很清楚,请查看我的两个解决方案以进行说明。

解决方案 1: 将函数作为参数传递

foo <- function(input, funs){
  # Initialize output
  output = list()

  # Compute output
  for (fun_name in names(funs)){
    # For each function I calculate it and store it in output
    output[fun_name] = funs[[fun_name]](input)
  }

  return(output)
}

foo(1:5, list(mean=mean, min=min))

我不喜欢这种方法的是我们不能通过这样做来调用它:foo(1:5, list(mean, min))

解决方案 2: 将函数名称作为参数传递并使用 get

foo2 <- function(input, funs){
  # Initialize output
  output = list()

  # Compute output
  for (fun in funs){
    # For each function I calculate it and store it in output
    output[fun] = get(fun)(input)
  }

  return(output)
}
foo2(1:5, c("mean", "min"))

我不喜欢这种方法的是我们并没有真正将函数对象作为参数传递。

我的问题:

两种方法都可以,但我不太确定该选择哪一种。

你能帮我吗:

  • 告诉我哪个最好?
  • 每种方法的优点和默认值是什么?
  • 还有其他(更好的)方法

如果您需要更多信息,请随时询问。

谢谢!

【问题讨论】:

  • 如果你可以不用函数foo返回一个命名列表,也许你可以从lapply(funs, function(f) f(input))获得一些灵感。

标签: r function parameter-passing


【解决方案1】:

简化相关解决方案

问题中的第一个解决方案要求命名列表,第二个要求函数具有作为字符串传递的名称。这两个用户界面可以使用以下简化来实现。请注意,我们将 envir 参数添加到 foo2 以确保按预期进行函数名称查找。其中第一个看起来更简洁,但如果要以交互方式使用函数并且需要更少的输入,那么第二个确实不需要指定名称。

foo1 <- function(input, funs) Map(function(f) f(input), funs)
foo1(1:5, list(min = min, max = max)) # test

foo2 <- function(input, nms, envir = parent.frame()) {
    Map(function(nm) do.call(nm, list(input), envir = envir), setNames(nms, nms))
}
foo2(1:5, list("min", "max")) # test

或者,我们可以在foo1 上构建foo2

foo2a <- function(input, funs, envir = parent.frame()) {
    foo1(input, mget(unlist(funs), envir = envir, inherit = TRUE))
}
foo2a(1:5, list("min", "max")) # test

或将用户界面基于传递包含函数名称的公式,因为公式已经包含环境的概念:

foo2b <- function(input, fo) foo2(input, all.vars(fo), envir = environment(fo))
foo2b(1:5, ~ min + max) # test

传递函数本身时的可选名称

但是,问题表明最好是

  • 函数本身被传递
  • 名称是可选的

为了合并这些功能,以下允许列表有名称或没有名称或混合使用。如果列表元素没有名称,则使用定义函数的表达式(通常是其名称)。

我们可以从列表的名称中派生名称,或者当缺少名称时,我们可以使用函数名称本身,或者如果函数是匿名的并且作为其定义给出,那么名称可以是定义函数的表达式。

关键是使用match.call 并把它拆开。我们确保funs 是一个列表,以防它被指定为字符向量。 match.fun 将解释函数和字符串命名函数并在父框架中查找它们,因此我们使用 for 循环而不是 Maplapply,以便我们不会生成新的函数范围。

foo3 <- function(input, funs) {
  cl <- match.call()[[3]][-1]
  nms <- names(cl)
  if (is.null(nms)) nms <- as.character(cl)
  else nms[nms == ""] <- as.character(cl)[nms == ""]
  funs <- as.list(funs)
  for(i in seq_along(funs)) funs[[i]] <- match.fun(funs[[i]])(input)
  setNames(funs, nms)
}

foo3(1:5, list(mean = mean, min = "min", sd, function(x) x^2))

给予:

$mean
[1] 3

$min
[1] 1

$sd
[1] 1.581139

$`function(x) x^2`
[1]  1  4  9 16 25

【讨论】:

  • 当列表直接作为funs 传递时,foo3 效果很好。但是,如果我先将列表保存在对象 (l_funs &lt;- list(mean = mean, min = "min", sd, function(x) x^2)) 中,然后将其传递给 foo3 (foo3(1:5, l_funs)),则它不起作用。错误:object of type 'symbol' is not subsettable。有一个简单的解决方法吗?我尝试过使用evalenquo 等,但无法弄清楚。
  • 您必须决定是否需要未评估或评估的参数。你可以做foo2(1:5, l_funs)do.call("foo3", list(1:5, l_funs))
【解决方案2】:

您缺少的一件事是将for 循环替换为lapply。同样对于函数式编程,将函数分开来做一件事通常是一种好习惯。我个人喜欢解决方案 1 中直接传递函数的版本,因为它避免了 R 中的另一个调用,因此效率更高。在解决方案 2 中,最好使用match.fun 而不是getmatch.funget 搜索函数更严格。

x <- 1:5

foo <- function(input, funs) {
  lapply(funs, function(fun) fun(input))
}

foo(x, c(mean=mean, min=min))

上面的代码简化了您的解决方案 1. 要添加到此函数,您可以添加一些错误处理,例如 is.numeric for x 和 is.function for funs。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多