我正在寻找关于将函数列表作为参数传递代码的最佳方法的建议。
我想做什么:
我想将一个函数作为参数传递给一个特定输入的函数。并根据这些提供输出名称。
A"可重复的"示例
input = 1:5
要传递的功能是mean
,min
预期电话:
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"))
我对这种方法的不满意是我们并没有真正将函数对象作为参数传递。
我的问题:
两种方式都有效,但我不太确定选择哪种方式。
你可以帮助我:
如果您需要更多信息,请不要犹豫。
谢谢!
答案 0 :(得分:4)
问题中的第一个解决方案要求列表被命名,第二个要求函数具有作为字符串传递的名称。可以使用以下简化来实现这两个用户界面。请注意,我们向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
或者,我们可以在foo2
上构建foo1
:
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
循环代替Map
或lapply
,以便我们不生成一个新的功能范围。
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
答案 1 :(得分:1)
您缺少的一件事是用for
替换lapply
循环。同样对于函数式编程,将函数分开来做一件事通常是一种很好的做法。我个人喜欢解决方案1中直接传递函数的版本,因为它避免了R中的另一个调用,因此效率更高。在解决方案2中,最好使用match.fun
而不是get
。 <{1}}在搜索功能方面比match.fun
更严格。
get
上面的代码简化了您的解决方案1.要添加到此功能,您可以添加一些错误处理,例如x <- 1:5
foo <- function(input, funs) {
lapply(funs, function(fun) fun(input))
}
foo(x, c(mean=mean, min=min))
用于x和is.numeric
用于操作。