如何在 R 中编写递归组合函数?
How can I write a recursive compose function in R?
我想在 R 中创建一个函数 "compose",它将组成任意数量的函数作为参数。
到目前为止,我已经通过定义一个由两个参数组成的函数 "of" 然后减少这个来完成这个:
of <- function(f,g) function(x) f(g(x))
id <- function(x) x
compose <- function(...) {
argms = c(...)
Reduce(of,argms,id)
}
这似乎工作正常,但由于我正在学习 R,我想我会尝试以显式递归风格编写它,即放弃使用 Reduce,我想你会做的那种事情在这样的方案中:
(define (compose . args)
(if (null? args) identity
((car args) (apply compose (cdr args)))))
我遇到了很多障碍,目前主要的障碍似乎是参数的第一个元素没有被识别为函数。到目前为止我的弱尝试:
comp <- function(...) {
argms <- list(...)
len <- length(argms)
if(len==0) { return(id) }
else {
(argms[1])(do.call(comp,argms[2:len]))
}
}
吐出:Error in comp(sin, cos, tan) : attempt to apply non-function
一定有某种方法可以做到这一点,但我不知道。有什么建议吗?
一个问题是,如果len==1
,那么argms[2:len]
returns一个长度为2的列表;特别是,
> identical(argms[2:1], list(NULL, argms[[1]]))
[1] TRUE
要解决这个问题,您可以使用 argms[-1]
删除列表的第一个元素。
您还需要使用 of
函数,因为您可能注意到 sin(cos)
returns 是错误而不是函数。将这些放在一起我们得到:
comp <- function(...) {
argms <- c(...)
len <- length(argms)
if(len==1) { return(of(argms[[1]], id)) }
else {
of(argms[[1]], comp(argms[-1]))
}
}
> comp(sin, cos, tan)(1)
[1] 0.0133878
> compose(sin, cos, tan)(1)
[1] 0.0133878
1)试试这个:
comp1 <- function(f, ...) {
if (missing(f)) identity
else function(x) f(comp1(...)(x))
}
# test
comp1(sin, cos, tan)(pi/4)
## [1] 0.5143953
# compose is defined in the question
compose(sin, cos, tan)(pi/4)
## [1] 0.5143953
functional::Compose(tan, cos, sin)(pi/4)
## [1] 0.5143953
sin(cos(tan(pi/4)))
## [1] 0.5143953
library(magrittr)
(pi/4) %>% tan %>% cos %>% sin
## [1] 0.5143953
(. %>% tan %>% cos %>% sin)(pi/4)
## [1] 0.5143953
1a) (1) 使用 Recall
的变体是:
comp1a <- function(f, ...) {
if (missing(f)) identity
else {
fun <- Recall(...)
function(x) f(fun(x))
}
}
comp1a(sin, cos, tan)(pi/4)
## [1] 0.5143953
2) 这是另一个实现:
comp2 <- function(f, g, ...) {
if (missing(f)) identity
else if (missing(g)) f
else Recall(function(x) f(g(x)), ...)
}
comp2(sin, cos, tan)(pi/4)
## [1] 0.5143953
3)这个实现比较接近题中的代码。它使用问题中定义的 of
:
comp3 <- function(...) {
if(...length() == 0) identity
else of(..1, do.call("comp3", list(...)[-1]))
}
comp3(sin, cos, tan)(pi/4)
## [1] 0.5143953
这里有一个解决方案,returns一个简单易懂的函数
func <- function(f, ...){
cl <- match.call()
if(length(cl) == 2L)
return(eval(bquote(function(...) .(cl[[2L]]))))
le <- max(which(sapply(cl, inherits, "name")))
if(le == length(cl)){
tmp <- cl[le]
tmp[[2L]] <- quote(...)
cl[[length(cl)]] <- tmp
} else if(le == length(cl) - 1L){
tmp <- cl[le]
tmp[[2L]] <- cl[[le + 1L]]
cl[[le]] <- tmp
cl[[le + 1L]] <- NULL
} else
stop("something is wrong...")
eval(cl)
}
func(sin, cos, tan) # clear what the function does
#R function (...)
#R sin(cos(tan(...)))
#R <environment: 0x000000001a189778>
func(sin, cos, tan)(pi/4) # gives correct value
#R [1] 0.5143953
可能需要将 sapply(cl, inherits, "name")
行调整为更通用的内容...
滚动您自己的函数组合的另一种方法是使用 gestalt 包,它提供组合作为高阶函数 compose()
和中缀运算符 [=14] =]. (为了让它们读起来一样,函数是从左到右组成的。)
基本用法很简单:
library(gestalt)
f <- compose(tan, cos, sin) # apply tan, then cos, then sin
f(pi/4)
#> [1] 0.514395258524
g <- tan %>>>% cos %>>>% sin
g(pi/4)
#> [1] 0.514395258524
但是你得到了很多额外的灵活性:
## You can annotate composite functions and apply list methods
f <- first: tan %>>>% cos %>>>% sin
f[[1]](pi/4)
#> [1] 1
f$first(pi/4)
#> [1] 1
## magrittr %>% semantics, such as implicity currying, is supported
scramble <- sample %>>>% paste(collapse = "")
set.seed(1); scramble(letters, 5)
#> [1] "gjnue"
## Compositions are list-like; you can inspect them using higher-order functions
stepwise <- lapply(`%>>>%`, print) %>>>% compose
stepwise(f)(pi/4)
#> [1] 1
#> [1] 0.540302305868
#> [1] 0.514395258524
## formals are preserved
identical(formals(scramble), formals(sample))
#> [1] TRUE
关于 R 中的函数调用,您应该记住的一件事是它们的成本不可忽略。与文字函数组合不同,compose()
(和 %>>>%
)在调用时展平组合。特别是,以下调用产生相同的功能,operationally:
fs <- list(tan, cos, sin)
## compose(tan, cos, sin)
Reduce(compose, fs)
Reduce(`%>>>%`, fs)
compose(fs)
compose(!!!fs) # tidyverse unquote-splicing
这是一个从调用构建函数的解决方案,它提供类似于 Benjamin 的可读输出:
compose_explicit <- function(...){
funs <- as.character(match.call()[-1])
body <- Reduce(function(x,y) call(y,x), rev(funs), init = quote(x))
eval.parent(call("function",as.pairlist(alist(x=)),body))
}
compose_explicit(sin, cos, tan)
# function (x)
# sin(cos(tan(x)))
compose_explicit(sin, cos, tan)(pi/4)
# [1] 0.5143953
看起来还挺健壮的:
compose_explicit()
# function (x)
# x
compose_explicit(sin)
# function (x)
# sin(x)
和无关但有用,这里是purrr:compose
的代码:
#' Compose multiple functions
#'
#' @param ... n functions to apply in order from right to left.
#' @return A function
#' @export
#' @examples
#' not_null <- compose(`!`, is.null)
#' not_null(4)
#' not_null(NULL)
#'
#' add1 <- function(x) x + 1
#' compose(add1, add1)(8)
compose <- function(...) {
fs <- lapply(list(...), match.fun)
n <- length(fs)
last <- fs[[n]]
rest <- fs[-n]
function(...) {
out <- last(...)
for (f in rev(rest)) {
out <- f(out)
}
out
}
}
我想在 R 中创建一个函数 "compose",它将组成任意数量的函数作为参数。
到目前为止,我已经通过定义一个由两个参数组成的函数 "of" 然后减少这个来完成这个:
of <- function(f,g) function(x) f(g(x))
id <- function(x) x
compose <- function(...) {
argms = c(...)
Reduce(of,argms,id)
}
这似乎工作正常,但由于我正在学习 R,我想我会尝试以显式递归风格编写它,即放弃使用 Reduce,我想你会做的那种事情在这样的方案中:
(define (compose . args)
(if (null? args) identity
((car args) (apply compose (cdr args)))))
我遇到了很多障碍,目前主要的障碍似乎是参数的第一个元素没有被识别为函数。到目前为止我的弱尝试:
comp <- function(...) {
argms <- list(...)
len <- length(argms)
if(len==0) { return(id) }
else {
(argms[1])(do.call(comp,argms[2:len]))
}
}
吐出:Error in comp(sin, cos, tan) : attempt to apply non-function
一定有某种方法可以做到这一点,但我不知道。有什么建议吗?
一个问题是,如果len==1
,那么argms[2:len]
returns一个长度为2的列表;特别是,
> identical(argms[2:1], list(NULL, argms[[1]]))
[1] TRUE
要解决这个问题,您可以使用 argms[-1]
删除列表的第一个元素。
您还需要使用 of
函数,因为您可能注意到 sin(cos)
returns 是错误而不是函数。将这些放在一起我们得到:
comp <- function(...) {
argms <- c(...)
len <- length(argms)
if(len==1) { return(of(argms[[1]], id)) }
else {
of(argms[[1]], comp(argms[-1]))
}
}
> comp(sin, cos, tan)(1)
[1] 0.0133878
> compose(sin, cos, tan)(1)
[1] 0.0133878
1)试试这个:
comp1 <- function(f, ...) {
if (missing(f)) identity
else function(x) f(comp1(...)(x))
}
# test
comp1(sin, cos, tan)(pi/4)
## [1] 0.5143953
# compose is defined in the question
compose(sin, cos, tan)(pi/4)
## [1] 0.5143953
functional::Compose(tan, cos, sin)(pi/4)
## [1] 0.5143953
sin(cos(tan(pi/4)))
## [1] 0.5143953
library(magrittr)
(pi/4) %>% tan %>% cos %>% sin
## [1] 0.5143953
(. %>% tan %>% cos %>% sin)(pi/4)
## [1] 0.5143953
1a) (1) 使用 Recall
的变体是:
comp1a <- function(f, ...) {
if (missing(f)) identity
else {
fun <- Recall(...)
function(x) f(fun(x))
}
}
comp1a(sin, cos, tan)(pi/4)
## [1] 0.5143953
2) 这是另一个实现:
comp2 <- function(f, g, ...) {
if (missing(f)) identity
else if (missing(g)) f
else Recall(function(x) f(g(x)), ...)
}
comp2(sin, cos, tan)(pi/4)
## [1] 0.5143953
3)这个实现比较接近题中的代码。它使用问题中定义的 of
:
comp3 <- function(...) {
if(...length() == 0) identity
else of(..1, do.call("comp3", list(...)[-1]))
}
comp3(sin, cos, tan)(pi/4)
## [1] 0.5143953
这里有一个解决方案,returns一个简单易懂的函数
func <- function(f, ...){
cl <- match.call()
if(length(cl) == 2L)
return(eval(bquote(function(...) .(cl[[2L]]))))
le <- max(which(sapply(cl, inherits, "name")))
if(le == length(cl)){
tmp <- cl[le]
tmp[[2L]] <- quote(...)
cl[[length(cl)]] <- tmp
} else if(le == length(cl) - 1L){
tmp <- cl[le]
tmp[[2L]] <- cl[[le + 1L]]
cl[[le]] <- tmp
cl[[le + 1L]] <- NULL
} else
stop("something is wrong...")
eval(cl)
}
func(sin, cos, tan) # clear what the function does
#R function (...)
#R sin(cos(tan(...)))
#R <environment: 0x000000001a189778>
func(sin, cos, tan)(pi/4) # gives correct value
#R [1] 0.5143953
可能需要将 sapply(cl, inherits, "name")
行调整为更通用的内容...
滚动您自己的函数组合的另一种方法是使用 gestalt 包,它提供组合作为高阶函数 compose()
和中缀运算符 [=14] =]. (为了让它们读起来一样,函数是从左到右组成的。)
基本用法很简单:
library(gestalt)
f <- compose(tan, cos, sin) # apply tan, then cos, then sin
f(pi/4)
#> [1] 0.514395258524
g <- tan %>>>% cos %>>>% sin
g(pi/4)
#> [1] 0.514395258524
但是你得到了很多额外的灵活性:
## You can annotate composite functions and apply list methods
f <- first: tan %>>>% cos %>>>% sin
f[[1]](pi/4)
#> [1] 1
f$first(pi/4)
#> [1] 1
## magrittr %>% semantics, such as implicity currying, is supported
scramble <- sample %>>>% paste(collapse = "")
set.seed(1); scramble(letters, 5)
#> [1] "gjnue"
## Compositions are list-like; you can inspect them using higher-order functions
stepwise <- lapply(`%>>>%`, print) %>>>% compose
stepwise(f)(pi/4)
#> [1] 1
#> [1] 0.540302305868
#> [1] 0.514395258524
## formals are preserved
identical(formals(scramble), formals(sample))
#> [1] TRUE
关于 R 中的函数调用,您应该记住的一件事是它们的成本不可忽略。与文字函数组合不同,compose()
(和 %>>>%
)在调用时展平组合。特别是,以下调用产生相同的功能,operationally:
fs <- list(tan, cos, sin)
## compose(tan, cos, sin)
Reduce(compose, fs)
Reduce(`%>>>%`, fs)
compose(fs)
compose(!!!fs) # tidyverse unquote-splicing
这是一个从调用构建函数的解决方案,它提供类似于 Benjamin 的可读输出:
compose_explicit <- function(...){
funs <- as.character(match.call()[-1])
body <- Reduce(function(x,y) call(y,x), rev(funs), init = quote(x))
eval.parent(call("function",as.pairlist(alist(x=)),body))
}
compose_explicit(sin, cos, tan)
# function (x)
# sin(cos(tan(x)))
compose_explicit(sin, cos, tan)(pi/4)
# [1] 0.5143953
看起来还挺健壮的:
compose_explicit()
# function (x)
# x
compose_explicit(sin)
# function (x)
# sin(x)
和无关但有用,这里是purrr:compose
的代码:
#' Compose multiple functions
#'
#' @param ... n functions to apply in order from right to left.
#' @return A function
#' @export
#' @examples
#' not_null <- compose(`!`, is.null)
#' not_null(4)
#' not_null(NULL)
#'
#' add1 <- function(x) x + 1
#' compose(add1, add1)(8)
compose <- function(...) {
fs <- lapply(list(...), match.fun)
n <- length(fs)
last <- fs[[n]]
rest <- fs[-n]
function(...) {
out <- last(...)
for (f in rev(rest)) {
out <- f(out)
}
out
}
}