尝试使用 sapply 避免 for 循环(对于 gsub)

Trying to avoid for loop with sapply (for gsub)

尽量避免在以下代码中使用 for 循环,方法是利用 sapply,如果可能的话。带循环的解决方案对我来说非常好,我只是想学习更多 R 并探索尽可能多的方法。

Objective:有一个向量i和两个向量sf(搜索)和rp(替换)。对于每个 i 需要循环 sf 并替换为 rp where match.

i  = c("1 6 5 4","7 4 3 1")
sf = c("1","2","3")
rp = c("one","two","three")

funn <- function(i) {
  for (j in seq_along(sf)) i = gsub(sf[j],rp[j],i,fixed=T)
  return(i)
}
print(funn(i))

结果(正确):

[1] "one 6 5 4"     "7 4 three one"

我也想这样做,但是 sapply

#Trying to avoid a for loop in a fun
#funn1 <- function(i) {
#  i = gsub(sf,rp,i,fixed=T)
#  return(i)
#}
#print(sapply(i,funn1))

显然,上面注释的代码将不起作用,因为我只能获取 sf 的第一个元素。这是我第一次使用 sapply,所以我不确定如何将 "inner" 隐式循环转换为矢量化解决方案。感谢任何帮助(即使是声明 - 这是不可能的)!

(我知道 mgsub 但这不是这里的解决方案。想保留 gsub

编辑:包含包的完整代码以及以下提供的解决方案和时间安排:

#timing
library(microbenchmark)
library(functional)

i  = rep(c("1 6 5 4","7 4 3 1"),10000)
sf = rep(c("1","2","3"),100)
rp = rep(c("one","two","three"),100)

#Loop
funn <- function(i) {
  for (j in seq_along(sf)) i = gsub(sf[j],rp[j],i,fixed=T)
  return(i)
}
t1 = proc.time()
k = funn(i)
t2 = proc.time()

#print(k)

print(microbenchmark(funn(i),times=10))

#mapply
t3 = proc.time()
mapply(function(u,v) i<<-gsub(u,v,i), sf, rp)
t4 = proc.time()

#print(i)

print(microbenchmark(mapply(function(u,v) i<<-gsub(u,v,i), sf, rp),times=10))

#Curry
t5 = proc.time()
Reduce(Compose, Map(function(u,v) Curry(gsub, pattern=u, replacement=v), sf, rp))(i)
t6 = proc.time()

print(microbenchmark(Reduce(Compose, Map(function(u,v) Curry(gsub, pattern=u, replacement=v), sf, rp))(i), times=10))

#4th option
n <- length(sf)
sf <- setNames(sf,1:n)
rp <- setNames(rp,1:n)

t7 = proc.time()
Reduce(function(x,j) gsub(sf[j],rp[j],x,fixed=TRUE),c(list(i),as.list(1:n)))
t8 = proc.time()

print(microbenchmark(Reduce(function(x,j) gsub(sf[j],rp[j],x,fixed=TRUE),c(list(i),as.list(1:n))),times=10))

#Usual proc.time
print(t2-t1)
print(t4-t3)
print(t6-t5)
print(t8-t7)

次数:

Unit: milliseconds
    expr min  lq mean median  uq max neval
 funn(i) 143 143  149    145 147 165    10
Unit: seconds
                                               expr min  lq mean median  uq max neval
 mapply(function(u, v) i <<- gsub(u, v, i), sf, rp) 4.1 4.2  4.4    4.3 4.4 4.9    10
Unit: seconds
                                                                                           expr min  lq mean median  uq max neval
 Reduce(Compose, Map(function(u, v) Curry(gsub, pattern = u, replacement = v),      sf, rp))(i) 1.6 1.6  1.7    1.7 1.7 1.7    10
Unit: milliseconds
                                                                                      expr min  lq mean median  uq max neval
 Reduce(function(x, j) gsub(sf[j], rp[j], x, fixed = TRUE), c(list(i),      as.list(1:n))) 141 144  147    145 146 162    10
   user  system elapsed 
   0.15    0.00    0.15 
   user  system elapsed 
   4.49    0.03    4.52 
   user  system elapsed 
   1.68    0.02    1.68 
   user  system elapsed 
   0.19    0.00    0.18 

因此,确实在这种情况下,for 循环提供了最佳时机,并且(在我看来)是最直接、最简单且可能最优雅的。坚持循环。

感谢大家。接受并赞成所有建议。

一种方法 - 优点是简洁但显然不是面向函数式编程 - 因为它在修改 i:

时具有边界效应
mapply(function(u,v) i<<-gsub(u,v,i), sf, rp)
#> i
#[1] "one 6 5 4"     "7 4 three one"

或者这里是一个纯函数式编程的方法:

library(functional)
Reduce(Compose, Map(function(u,v) Curry(gsub, pattern=u, replacement=v), sf, rp))(i)
#[1] "one 6 5 4"     "7 4 three one"

所做的是Map(function(u,v) Curry(gsub, pattern=u, replacement=v), sf, rp)构建一个函数列表,分别将1替换为one2替换为two等。然后将这些函数组合并应用于 i,给出所需的结果。

这是顺序的,因此循环看起来很自然。这是一个几乎和 <<-:

一样糟糕的解决方案
n  <- length(sf)
Reduce(function(x,j) gsub(sf[j],rp[j],x,fixed=TRUE),c(list(i),as.list(1:n)))
# [1] "one 6 5 4"     "7 4 three one"

真的,你应该使用循环。

sapply(seq_along(sf),function(x)i<-gsub(sf[x],rp[x],i))