尝试使用sapply(替代for循环)来避免使用gsub。

3
尝试使用sapply来避免在以下代码中使用for循环,如果可能的话。对于我来说,循环解决方案完全正常,我只是想学习更多R并尽可能探索许多方法。
目标:有一个向量i和两个向量sf(搜索)和rp(替换)。对于每个i,需要循环遍历sf并在匹配时用rp替换。
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,所以我不确定如何将“内部”隐式循环转换为矢量化解决方案。任何帮助(即使是一句话 - 这是不可能的)都会受到赞赏!
(我知道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循环提供了最佳的时间,并且(在我看来)最直接、简单和可能优雅。坚持使用循环。
谢谢大家。所有建议都被接受并赞同。

1
通常,您会同时在所有要测试的内容上使用microbenchmark。此外,如果性能再次成为问题,您可能希望在未来的问题中标记为performance。顺便说一句,Shenglin的答案实际上确实使用了sapply - Frank
我的目标是估计每个解决方案单独运行的时间,以防另一种解决方案比循环更快。我也点赞了Shenglin的答案。 - Alexey Ferapontov
3个回答

3

一种方法是简洁明了但显然不适合函数式编程 - 因为它在修改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,从而得到所需的结果。


不,我只是展示 i 而非 mapply 的结果! - Colonel Beauvel
好的,我错过了那部分,因为它被注释掉了。 - akrun
没问题,我添加了一些“合理”的解决方案。 - Colonel Beauvel

2
这是一个顺序执行的问题,因此循环看起来很自然。下面是一种解决方案,几乎和<<一样糟糕:
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"

实际上,你应该使用循环。


1
嗯...不能接受>1个答案。不过已经点赞了,谢谢! - Alexey Ferapontov

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

1
你能添加你看到的输出吗?它对我来说看起来不太像原帖。尝试在末尾添加[,1] - Frank

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