Função fatorial Ruby

87

Estou ficando louco: Onde está a função Ruby para fatorial? Não, não preciso de implementações de tutorial, só quero a função da biblioteca. Não está na matemática!

Estou começando a duvidar, é uma função de biblioteca padrão?

rutger
fonte
63
Eu faço como6.downto(1).inject(:*)
mckeed
42
@mckeed: Ou (1..6).inject(:*)que é um pouco mais sucinto.
sepp2k
8
por que você esperaria que houvesse um?
Presidente James K. Polk,
4
Eu me pergunto qual é o status das bibliotecas de matemática e ciências para Ruby.
Andrew Grimm
5
Apenas uma nota sobre os exemplos fornecidos usando injetar. (1..num).inject(:*)falha para o caso em que num == 0. (1..(num.zero? ? 1 : num)).inject(:*)fornece a resposta correta para o caso 0 e retorna nilpara os parâmetros negativos.
Yogh

Respostas:

135

Não há função fatorial na biblioteca padrão.

sepp2k
fonte
7
Ruby tem o Math.gammamétodo, por exemplo, stackoverflow.com/a/37352690/407213
Dorian
Que lógica maluca! Temos (n-1)! função e não tem n simples! !!!
Alexander Gorg de
110

Assim é melhor

(1..n).inject(:*) || 1
Alexander Revutsky
fonte
33
Ou especificar o valor inicial diretamente: (1..n).reduce(1, :*).
Andrew Marshall
77

Não está na biblioteca padrão, mas você pode estender a classe 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 Fatorial iterativo é a melhor escolha por razões óbvias de desempenho.

Pierre-Antoine LaFayette
fonte
8
Ele disse explicitamente que não quer uma implementação.
sepp2k
115
Ele não pode; mas as pessoas que procuram por "fatorial Ruby" podem.
Pierre-Antoine LaFayette
1
rosettacode.org/wiki/Factorial#Ruby está simplesmente errado. Não há caso para 0
Douglas G. Allen
A versão recursiva é realmente mais lenta? Pode depender se Ruby faz otimização recursiva de cauda.
Lex Lindsey
23

Descaradamente copiado de http://rosettacode.org/wiki/Factorial#Ruby , meu favorito pessoal é

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

>> 400.fact
=> 64034522846623895262347970319503005850702583026002959458684445942802397169186831436278478647463264676294350575035856810848298162883517435228961988646802997937341654150838162426461942352307046244325015114448670890662773914918117331955996440709549671345290477020322434911210797593280795101545372667251627877890009349763765710326350331533965349868386831339352024373788157786791506311858702618270169819740062983025308591298346162272304558339520759611505302236086810433297255194852674432232438669948422404232599805551610635942376961399231917134063858996537970147827206606320217379472010321356624613809077942304597360699567595836096158715129913822286578579549361617654480453222007825818400848436415591229454275384803558374518022675900061399560145595206127211192918105032491008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Essa implementação também é a mais rápida entre as variantes listadas no Rosetta Code.

atualização # 1

Adicionado || 1para lidar com o caso zero.

atualização # 2

Com agradecimento e apreço a Mark Thomas , aqui está uma versão um pouco mais eficiente, elegante e obscura:

class Integer
  def fact
    (2..self).reduce(1,:*)
  end
end
destemido
fonte
1
Que diabos isso significa?! sim, é rápido, mas é muito incomum
niccolo m.
3
também é incorreto para 0! - deve ser algo como: if self <= 1; 1; outro; (1..se) .reduzir (: *); fim
Tarmo
8
@allen - Não culpe a linguagem se você não consegue entendê-la. Significa simplesmente pegar o intervalo 1 para self e, em seguida, remover o primeiro elemento (1) dele (ou seja, isso é o que significa reduzir na programação funcional). Em seguida, remova o primeiro elemento do que resta (2) e multiplique (: *) todos juntos. Agora remova o primeiro elemento do que resta (3) e multiplique-o pelo total corrente. Continue até que não haja mais nada (ou seja, você manipulou toda a gama). Se a redução falhar (porque o array está vazio no caso de 0!), Então apenas retorne 1 de qualquer maneira.
SDJMcHattie
Você também pode lidar com o caso de zero especificando o valor inicial em reduce: (1..self).reduce(1,:*).
Mark Thomas
3
Na verdade, você pode usar (2..self).reduce(1,:*), se micro-eficiência for o seu lugar :)
Mark Thomas
14

Você também pode usar a Math.gammafunção que se resume a fatorial para parâmetros inteiros.

Krishna Prasad Chitrapura
fonte
3
Dos documentos: "Observe que gamma (n) é igual a fact (n-1) para o inteiro n> 0. No entanto, gamma (n) retorna float e pode ser uma aproximação". Se levarmos isso em consideração, funciona, mas a solução de redução parece muito mais direta.
Michael Kohl
Obrigado por isso! Meu instinto diz para usar a biblioteca padrão em vez de um redução personalizada sempre que possível. A criação de perfil pode sugerir o contrário.
David J.
2
Nota: É O (1) e preciso para 0..22: MRI Ruby realmente executa uma pesquisa para esses valores (veja static const double fact_table[]na fonte ). Além disso, é uma aproximação. 23 !, por exemplo, requer uma mantissa de 56 bits que é impossível representar precisamente usando o duplo IEEE 754 que tem mantissas de 53 bits.
FNY
14

Em matemática, factorial of né apenas o gamma function of n+1
(veja: http://en.wikipedia.org/wiki/Gamma_function )

Ruby Math.gamma()então apenas usa Math.gamma(n+1)e converte de volta para um inteiro, se desejar.

Albert Renshaw
fonte
13
class Integer
  def !
    (1..self).inject(:*)
  end
end

exemplos

!3  # => 6
!4  # => 24
jasonleonhard
fonte
O que há de errado class Integer ; def ! ; (1..self).inject(:*) ; end ; end?
Aleksei Matiushkin
@mudasobwa Eu gosto, refatorei para simplificar.
jasonleonhard
4
Eu respeitosamente sugeriria que substituir um método de instância incorporado em todos os objetos Ruby para retornar um valor verdadeiro, ao invés de um falso, pode não fazer você muitos amigos.
MatzFan
Pode ser muito perigoso fazer com que o operador de negação se torne outra coisa quando aacontece de estar Integerno caso de !a... fazer isso pode causar a existência de um bug que é muito difícil de dizer. Se aacontecer de ser um grande número, como 357264543então o processador está entrando em um grande loop e as pessoas podem se perguntar por que o programa de repente fica lento
polaridade
Essa resposta foi mais apenas uma coisa legal de compartilhar do que um exemplo prático de usar.
jasonleonhard
9

eu faria

(1..n).inject(1, :*)
Santhosh
fonte
6

Acabei de escrever o meu próprio:

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

Além disso, você pode definir um fatorial de queda:

def fall_fact(n,k)
  if k <= 0
    1
  else
    n*fall_fact(n - 1, k - 1)
  end
end
Jack moon
fonte
4

Basta chamar esta função

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

exemplos

factorial(3)
factorial(11)
jasonleonhard
fonte
3

Usar Math.gamma.flooré uma maneira fácil de produzir uma aproximação e arredondar de volta para o resultado inteiro correto. Deve funcionar para todos os inteiros, inclua uma verificação de entrada se necessário.

Ayarch
fonte
6
Correção: Depois n = 22que deixa de dar uma resposta exata e produz aproximações.
Ayarch
2

Com muito respeito a todos que participaram e despenderam seu tempo nos ajudando, gostaria de compartilhar meus benchmarks das soluções listadas aqui. Params:

iterações = 1000

n = 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)

Para 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)
Alexander Gorg
fonte
1
É importante notar que o mais rápido Math.gamma(n+1)também é apenas aproximado para n> 22, portanto, pode não ser adequado para todos os casos de uso.
Neil Slater de
1

Apenas outra maneira de fazer isso, embora realmente não seja necessário.

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
Nate Beers
fonte
1

Você provavelmente achará útil uma solicitação de recurso Ruby . Ele contém um patch não trivial que inclui um script Bash de demonstração . A diferença de velocidade entre um loop ingênuo e a solução apresentada no lote pode ser literalmente 100x (cem vezes). Escrito tudo em Ruby puro.

Martin Vahi
fonte
1

Aqui está minha versão parece ser clara para mim, embora não seja tão limpa.

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

Esta foi a minha linha de teste irb que mostrou cada etapa.

num = 8;step = 0;(num - 1).times do (step += 1 ;num *= step; puts num) end;num
Cliff Thelin
fonte
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
fonte
0

E ainda outra forma (=

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)
Sky Davis
fonte
0

Só mais uma maneira de fazer isso:

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

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

fact(6) => 720
Robin Wood
fonte
0

Por que a biblioteca padrão exigiria um método fatorial, quando há um iterador embutido para esse propósito exato? É chamado upto.

Não, você não precisa usar recursão, como todas essas outras respostas mostram.

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

Em vez disso, o iterador integrado upto pode ser usado para calcular fatoriais:

factorial = 1
1.upto(10) {|x| factorial *= x }
factorial
 => 3628800
Donato
fonte