Факториал вектора - PullRequest
       36

Факториал вектора

0 голосов
/ 24 февраля 2019

как новичок я попытался определить свою собственную функцию для вычисления факториала.Мне удалось построить функцию, которая отлично работает для чисел.

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")
}

Однако, как я могу улучшить его таким образом, чтобы вы могли ввести в него вектор, и он вычисляет факториал каждого элемента?

Ответы [ 5 ]

0 голосов
/ 24 февраля 2019

Вы также можете использовать тип safe purrr :: map_dbl-function:

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

[1] 1 2 6

0 голосов
/ 24 февраля 2019

Ответы на вопрос кажутся немного сложными.Факториал - это уже существующая функция, и она векторизована как таковая, если у вас есть некоторые данные, которые вы можете просто поместить в функцию.Если вы хотите определить отрицательные числа для возврата 0, это также может быть включено с помощью логического утверждения.Обратите внимание, что я использую встроенную функцию factorial ниже, а не ту, которая указана в вопросе.

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

Теперь, если вы просто создаете упражнение для обучения, вы можете довольно просто векторизовать функцию, избегая ненужныхдля циклов, используя ту же идею.Просто используйте один цикл и повторяйте каждый элемент вектора во время этого цикла.

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 <- numeric(length), гдедлина - это количество выходов (например, length(x) здесь или, в более общем случае, NROW(x)).
  2. Используйте постоянную R NA для числовых значений вместо "NA".Первый признается как число, а последний изменит ваш вектор в символьном векторе.

Теперь альтернативные ответы предлагают lapply или vapply.Это примерно то же самое, что циклически повторять каждое значение в векторе и использовать функцию для каждого значения.Таким образом, это часто медленный (но очень читаемый!) Способ векторизации функции.Однако, если этого можно избежать, вы часто можете увеличить скорость.Для циклов и применять это не обязательно плохо, но в целом это намного медленнее по сравнению с векторизованными функциями.См. на этой странице, описывающей стековый поток , которая очень легко объясняет, почемуДополнительной альтернативой является использование предложенной функции 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

Как видно, R_factorial примерно в 11-12 раз быстрее по сравнению с vapply или lapply (2428.8 / 212.96= 11,4).Это довольно огромный прирост скорости.Дополнительные улучшения могут быть сделаны, чтобы ускорить его еще больше (например, используя алгоритмы факториальной аппроксимации, Rcpp и другие опции), но для этого примера этого может быть достаточно.

0 голосов
/ 24 февраля 2019

Добавляя к комментарию lapply выше, вы также можете использовать vapply или sapply для возврата вектора, а не списка:

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

[1] 1 2 6
0 голосов
/ 24 февраля 2019

Кажется, это идеальный случай для Vectorize: просто используйте Vectorize вокруг определения вашей функции factorial, чтобы сделать ее векторизованной для ее ввода.

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
0 голосов
/ 24 февраля 2019

Использовать функцию lapply

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

[[2]]
[1] 2

[[3]]
[1] 6

R Документация для функции lapply

...