【问题标题】:Factorial of a vector向量的阶乘
【发布时间】:2019-02-24 11:08:50
【问题描述】:

作为一个新手,我尝试定义自己的函数来计算阶乘。我已经设法构建了非常适用于数字的函数。

fact1 = function(x){
    a=1 
    for(i in 1:x){
        a = a*i
    }
    return(a)
}   

factorial = function(x){
    ifelse(x>=0 & round(x) == x , fact1(as.integer(x)),"NA")
}

但是,我怎样才能改进它,让你可以在其中输入一个向量并计算每个元素的阶乘?

【问题讨论】:

    标签: r factorial


    【解决方案1】:

    这似乎是 Vectorize 的完美案例:只需在 factorial 函数的定义周围使用 Vectorize 即可使其输入向量化。

    fact1 = function(x){
      a=1 
      for(i in 1:x){
        a = a*i
      }
      return(a)
    }   
    
    factorial = Vectorize(function(x){
      ifelse(x>=0 & round(x) == x , fact1(as.integer(x)),"NA")
    })
    
    factorial(c(1,2,3))
    #> [1] 1 2 6
    

    【讨论】:

      【解决方案2】:

      除了上面的lapply 注释之外,您还可以使用vapplysapply 返回一个向量而不是列表:

      vapply(c(1, 2, 3),
             factorial, 
             FUN.VALUE = numeric(1))
      
      [1] 1 2 6
      

      【讨论】:

        【解决方案3】:

        问题的答案似乎有点过于复杂。 阶乘已经是一个存在的函数,如果你有一些数据,你可以简单地将它放入函数中,因此它是矢量化的。如果您想定义负数以返回 0,这也可以通过使用逻辑语句来合并。请注意,我使用的是下面的内置函数factorial,而不是问题中的那个。

        dat <- round(runif(1000, -10, 10))
        dat_over_zero <- dat > 0 
        fact_vector <- numeric(1000)
        fact_vector <- factorial(dat[dat_over_zero])
        

        现在,如果您只是创建一个练习来学习,您可以使用相同的想法非常简单地对函数进行矢量化,避免不必要的 for 循环。只需使用一个循环并在此循环期间迭代向量中的每个元素。

        R_factorial <- function(x){
          if(!is.numeric(x) || length(dim(x)))
            stop("X must be a numeric vector!")
          #create an output vector
          output <- numeric(NROW(x))
          #set initial value
          output[x >= 1] <- 1
          output[x < 1] <- NA
          #Find the max factor (using only integer values, not gamma approximations)
          mx <- max(round(x))
          #Increment each output by multiplying the next factor (only on those which needs to be incremented) 
          for(i in seq(2, mx)){
            output[x >= i] <- output[x >= i] * i
          }
          #return output
          output
        }
        

        需要注意的几点:

        1. 首先使用output &lt;- numeric(length)分配整个向量,其中length是输出的数量(例如length(x)这里或更一般的NROW(x))。
        2. 使用 R 常量 NA 代替 "NA" 表示无数值。第一个被识别为数字,而后者会将您的向量更改为字符向量。

        现在替代答案建议 lapply 或 vapply。这与遍历向量中的每个值并在每个值上使用函数或多或少相同。因此,向量化函数通常是一种缓慢(但非常易读!)的方式。但是,如果可以避免这种情况,您通常可以获得速度提升。 For 循环和 apply 不一定是坏事,但与矢量化函数相比,它通常要慢很多。请参阅this stackoverflow page,它以非常容易理解的方式解释了原因。 另一种选择是使用建议的Vectorize 函数。这是一个快速而肮脏的解决方案。根据我的经验,它通常比执行简单循环要慢,并且可能会对多个参数函数产生一些意想不到的副作用。这并不一定是坏事,因为通常可以提高底层代码的可读性。


        速度比较

        现在矢量化版本与替代答案相比要快得多。使用 microbenchmark 包中的 microbenchmark 函数,我们可以确切地看到快了多少。下面显示了多少(注意这里我在问题描述中使用阶乘函数):

        microbenchmark::microbenchmark(R_factorial = R_factorial(x),
                                       Vapply = vapply(x,
                                                      factorial, 
                                                      FUN.VALUE = numeric(1)),
                                       Lapply = lapply(x, factorial),
                                       Vfactorial = Vfactorial(x))
        Unit: microseconds
                expr       min        lq      mean    median       uq       max neval
         R_factorial   186.525   197.287  232.2394  212.9565  241.464   395.706   100
              Vapply  2209.982  2354.596 3004.9264 2428.7905 3842.265  6165.144   100
              Lapply  2182.041  2299.092 2584.3881 2374.9855 2430.867  5061.852   100
        Vfactorial(x) 2381.027 2505.4395 2842.9820 2595.3040 2669.310  5920.094   100
        

        正如我们所见,与 vapply 或 lapply (2428.8 / 212.96 = 11.4) 相比,R_factorial 大约快 11 - 12 倍。这是一个相当大的速度提升。可以进行其他改进以进一步加快速度(例如,使用阶乘逼近算法、Rcpp 和其他选项),但对于本示例来说可能就足够了。

        【讨论】:

          【解决方案4】:

          使用 lapply 功能

          lapply(c(1,2,3),factorial)
          [[1]]
          [1] 1
          
          [[2]]
          [1] 2
          
          [[3]]
          [1] 6
          

          R Documentation for lapply function

          【讨论】:

            【解决方案5】:

            你也可以使用类型安全的 purrr::map_dbl-function:

            purrr::map_dbl(c(1,2,3), fact1)
            

            [1] 1 2 6

            【讨论】:

              猜你喜欢
              • 2013-02-19
              • 1970-01-01
              • 1970-01-01
              • 2014-03-24
              • 1970-01-01
              • 2015-04-19
              • 1970-01-01
              • 2020-03-16
              • 1970-01-01
              相关资源
              最近更新 更多