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


88

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

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


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для отрицательных параметров.
Йог

Ответы:




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,:*), если вам
Марк Томас


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


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

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.