Факториальная функция 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.