Факториальная функция Ruby

88

Я схожу с ума: где функция Ruby для факториала? Нет, мне не нужны учебные реализации, мне просто нужна функция из библиотеки. Это не по математике!

Я начинаю сомневаться, это стандартная библиотечная функция?

Rutger
источник
63
Мне это нравится6.downto(1).inject(:*)
mckeed
43
@mckeed: Или, (1..6).inject(:*)что более лаконично.
sepp2k
8
почему вы ожидаете, что он будет?
Президент Джеймс К. Полк
4
Интересно, каков статус математических и научных библиотек для Ruby.
Эндрю Гримм
5
Просто обратите внимание на предоставленные примеры с использованием inject. (1..num).inject(:*)не работает в случае, когда num == 0. (1..(num.zero? ? 1 : num)).inject(:*)дает правильный ответ для случая 0 и возвращает nilдля отрицательных параметров.
Йог

Ответы:

136

В стандартной библиотеке нет факториальной функции.

sepp2k
источник
7
У Ruby есть Math.gammaметод, например stackoverflow.com/a/37352690/407213
Дориан
Какая безумная логика! У нас есть (n-1)! функции и не иметь простой n! !!!
Александр Горг
111

Как это лучше

(1..n).inject(:*) || 1
Александр Ревуцкий
источник
34
Или задать начальное значение непосредственно: (1..n).reduce(1, :*).
Эндрю Маршалл
77

Его нет в стандартной библиотеке, но вы можете расширить класс Integer.

class Integer
  def factorial_recursive
    self <= 1 ? 1 : self * (self - 1).factorial
  end
  def factorial_iterative
    f = 1; for i in 1..self; f *= i; end; f
  end
  alias :factorial :factorial_iterative
end

NB. Итерационный факториал - лучший выбор по очевидным причинам производительности.

Пьер-Антуан ЛаФайет
источник
8
Он прямо сказал, что не хочет реализации.
sepp2k
117
Он не может; но люди, ищущие ТАК по запросу "Рубиновый факториал", могли бы.
Пьер-Антуан Лафайет,
1
rosettacode.org/wiki/Factorial#Ruby ошибается. Нет аргументов в пользу 0
Дуглас Г. Аллен
Действительно ли рекурсивная версия медленнее? Это может зависеть от того, выполняет ли Ruby хвостовую рекурсивную оптимизацию.
Лекс Линдси
24

Беззастенчиво скопировано с http://rosettacode.org/wiki/Factorial#Ruby , мой личный фаворит -

class Integer
  def fact
    (1..self).reduce(:*) || 1
  end
end

>> 400.fact
=> 64034522846623895262347970319503005850702583026002959458684445942802397169186831436278478647463264676294350575035856810848298162883517435228961988646802997937341654150838162426461942352307046244325015114448670890662773914918117331955996440709549671345290477020322434911210797593280795101545372667251627877890009349763765710326350331533965349868386831339352024373788157786791506311858702618270169819740062983025308591298346162272304558339520759611505302236086810433297255194852674432232438669948422404232599805551610635942376961399231917134063858996537970147827206606320217379472010321356624613809077942304597360699567595836096158715129913822286578579549361617654480453222007825818400848436415591229454275384803558374518022675900061399560145595206127211192918105032491008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Эта реализация также оказалась самой быстрой среди вариантов, перечисленных в Rosetta Code.

обновление # 1

Добавлено || 1 для обработки нулевого случая.

обновление # 2

С благодарностью и признательностью Марку Томасу , вот версия, которая немного более эффективна, элегантна и неясна:

class Integer
  def fact
    (2..self).reduce(1,:*)
  end
end
бесстрашный_фолл
источник
1
что, черт возьми, это значит ?! да, это быстро, но очень неудобно
niccolo m.
3
это также неверно для 0! - должно быть что-то вроде: if self <= 1; 1; еще; (1.. себя) .reduce (: *); конец
Тармо
8
@allen - Не обвиняйте язык, если вы его не понимаете. Это просто означает: взять диапазон от 1 до себя, а затем удалить из него первый элемент (1) (то есть это то, что означает сокращение в функциональном программировании). Затем удалите первый элемент того, что осталось (2), и умножьте (: *) их вместе. Теперь удалите первый элемент из оставшегося (3) и умножьте его на промежуточную сумму. Продолжайте, пока ничего не останется (т.е. вы обработали весь диапазон). Если сокращение не удалось (потому что в случае 0 массив пуст!), То в любом случае просто верните 1.
SDJMcHattie
Вы также можете обращаться с нулевым случаем, указав начальное значение в reduce: (1..self).reduce(1,:*).
Марк Томас
3
На самом деле вы можете использовать (2..self).reduce(1,:*), если вам
Марк Томас
16

В математике factorial of nэто просто gamma function of n+1
(см. Http://en.wikipedia.org/wiki/Gamma_function )

В Ruby Math.gamma()так просто используйте Math.gamma(n+1)и при желании верните его к целому числу.

Альберт Реншоу
источник
14

Вы также можете использовать Math.gammaфункцию, которая сводится к факториалу для целочисленных параметров.

Кришна Прасад Читрапура
источник
3
Из документов: «Обратите внимание, что гамма (n) такая же, как факт (n-1) для целого числа n> 0. Однако gamma (n) возвращает float и может быть приближением». Если принять это во внимание, это работает, но решение для сокращения кажется намного более простым.
Майкл Коль
Спасибо за это! Моя интуиция говорит, что лучше использовать стандартную библиотеку вместо написанного на заказ сокращения, когда это возможно. Профилирование может подсказать иное.
Дэвид Дж.
2
Примечание. Это O (1) и точно для 0..22: MRI Ruby фактически выполняет поиск этих значений (см. static const double fact_table[]В источнике ). Кроме того, это приближение. 23 !, например, требуется 56-битная мантисса, которую невозможно точно представить с помощью двойника IEEE 754, который имеет 53-битную мантиссу.
fny
13
class Integer
  def !
    (1..self).inject(:*)
  end
end

Примеры

!3  # => 6
!4  # => 24
Джейсонлеонхард
источник
Что не так class Integer ; def ! ; (1..self).inject(:*) ; end ; end?
Алексей Матюшкин
@mudasobwa Мне это нравится, для простоты я сделал рефакторинг.
jasonleonhard
4
Я бы со всем уважением предположил, что переопределение метода экземпляра, включенного во все объекты Ruby, для возврата истинного значения, а не ложного, может не сделать вас много друзей.
MatzFan 03
Может быть действительно опасно превращать оператор отрицания во что-то еще, когда aслучается Integerв случае !a... это может вызвать ошибку, которую очень трудно определить. Если aэто большое число, например, 357264543тогда процессор входит в большой цикл, и люди могут задаться вопросом, почему программа внезапно становится медленной
неполярность
Этот ответ был скорее классным, чем практическим примером для использования.
jasonleonhard 09
6

Я просто написал свое:

def fact(n)
  if n<= 1
    1
  else
    n * fact( n - 1 )
  end
end

Также вы можете определить падающий факториал:

def fall_fact(n,k)
  if k <= 0
    1
  else
    n*fall_fact(n - 1, k - 1)
  end
end
Джек Мун
источник
4

Просто вызовите эту функцию

def factorial(n=0)
  (1..n).inject(:*)
end

Примеры

factorial(3)
factorial(11)
Джейсонлеонхард
источник
3

Использование Math.gamma.floor- это простой способ получить приближение, а затем округлить его до правильного целочисленного результата. Должно работать для всех целых чисел, при необходимости включите проверку ввода.

Айарх
источник
6
Исправление: после того, как n = 22он перестает давать точный ответ и производит приближения.
Ayarch
2

С большим уважением ко всем, кто участвовал и потратили свое время, чтобы помочь нам, я хотел бы поделиться своими тестами решений, перечисленных здесь. Параметры:

итераций = 1000

п = 6

                                     user     system      total        real
Math.gamma(n+1)                   0.000383   0.000106   0.000489 (  0.000487)
(1..n).inject(:*) || 1            0.003986   0.000000   0.003986 (  0.003987)
(1..n).reduce(1, :*)              0.003926   0.000000   0.003926 (  0.004023)
1.upto(n) {|x| factorial *= x }   0.003748   0.011734   0.015482 (  0.022795)

Для n = 10

  user     system      total        real
0.000378   0.000102   0.000480 (  0.000477)
0.004469   0.000007   0.004476 (  0.004491)
0.004532   0.000024   0.004556 (  0.005119)
0.027720   0.011211   0.038931 (  0.058309)
Александр Горг
источник
1
Стоит отметить, что самый быстрый Math.gamma(n+1)также является приблизительным только для n> 22, поэтому может не подходить для всех случаев использования.
Нил Слейтер,
1

Просто еще один способ сделать это, хотя в этом нет необходимости.

class Factorial
   attr_reader :num
   def initialize(num)
      @num = num
   end

   def find_factorial
      (1..num).inject(:*) || 1
   end
end

number = Factorial.new(8).find_factorial
puts number
Нейт Бирс
источник
1

Возможно, вам будет полезен запрос на добавление функции Ruby . Он содержит нетривиальный патч , включающий демонстрационный скрипт Bash . Разница в скорости между наивным циклом и решением, представленным в пакете, может быть буквально в 100 раз. Написано все на чистом Ruby.

Мартин Вахи
источник
1

Вот моя версия кажется мне понятной, хотя она не такая чистая.

def factorial(num)
    step = 0
    (num - 1).times do (step += 1 ;num *= step) end
    return num
end

Это была моя тестовая линия irb, которая показывала каждый шаг.

num = 8;step = 0;(num - 1).times do (step += 1 ;num *= step; puts num) end;num
Клифф Телин
источник
0
class Integer
  def factorial
    return self < 0 ? false : self==0 ? 1 : self.downto(1).inject(:*)
    #Not sure what other libraries say, but my understanding is that factorial of 
    #anything less than 0 does not exist.
  end
end
Automatico
источник
0

И еще один способ (=

def factorial(number)
  number = number.to_i
  number_range = (number).downto(1).to_a
  factorial = number_range.inject(:*)
  puts "The factorial of #{number} is #{factorial}"
end
factorial(#number)
Скай Дэвис
источник
0

Еще один способ сделать это:

# fact(n) => Computes the Factorial of "n" = n!

def fact(n) (1..n).inject(1) {|r,i| r*i }end

fact(6) => 720
Робин Вуд
источник
0

Зачем стандартной библиотеке нужен факториальный метод, если есть встроенный итератор именно для этой цели? Это называетсяupto .

Нет, вам не нужно использовать рекурсию, как показывают все эти другие ответы.

def fact(n)
  n == 0 ? 1 : n * fact(n - 1)
end  

Скорее, встроенный итератор upto можно использовать для вычисления факториалов:

factorial = 1
1.upto(10) {|x| factorial *= x }
factorial
 => 3628800
Донато
источник