FATORIAL DE VÁRIOS NÚMEROS DE UMA VEZ

O probleminha é simples, como você faria pra calcular o fatorial de vários números de uma vez? Por exemplo, lhe é cedido 2, 3, 4 números... Essa quantidade será variada, mas você precisa calcular e retornar, de maneira organizada, esse resultado.

Implementei utilizando JS: Exemplo de entrada: calculeFactorial(3, 4, 5); Saída: { '3': 6, '4': 24, '5': 120 }

É um problema simples, o intúito é avaliarmos todas as implementações, e encontrar a melhor entre elas.

Acho que a ideia principal é aproveitar o fato de não precisar recalcular tudo todas as vezes.

Por exemplo, vamos supor que eu já calculei $3!$ usando um loop (multiplicando todos os inteiros de 1 a 3). Depois, quando for calcular $4!$, não preciso fazer todo o loop de 1 a 4. Eu posso fazer apenas $4\times 3!$ usando o resultado de $3!$ que já havia sido calculado anteriormente. E depois, se tiver um número maior (por exemplo, 8), eu posso multiplicar até o 5 ($8\times 7\times 6\times 5$) e depois multiplicar isso por $4!$ que já foi calculado.

Eu posso inclusive usar o próprio objeto final para isso. Pois ele está justamente armazenando os fatoriais que eu já calculei. Algo assim:

function fat(n, result) {
    // caso haja números repetidos na entrada, não preciso recalcular
    if (n in result) {
        return;
    }
    var fat = n;
    for (var i = n - 1; i > 1; i--) {
        // se o fatorial de "i" já foi calculado, posso multiplicar direto e interromper o loop
        if (i in result) {
            // guarda o fatorial em result, que pode ser reusado em futuros cálculos
            result[n] = fat * result[i];
            return;
        }
        fat *= i;
    }
    result[n] = fat;
}

function calculeFactorial(...nums) {
    var result = {};
    for (var num of nums) {
        fat(num, result);
    }
    return result;
}

console.log(calculeFactorial(3, 4, 5)); // { '3': 6, '4': 24, '5': 120 }

Assim só tem retrabalho quando os primeiros números são maiores. Por exemplo, se calcular primeiro $20!$, somente este valor ficará em result. Depois, se tiver que calcular $4!$, não há resultado anterior calculado para reaproveitar, então vai ter que fazer o loop de 1 a 4.


Então outra forma seria pré-calcular todos os fatoriais até o maior dos números, e depois retornar apenas os que precisar:

function calculeFactorial(...nums) {
    var maior = Math.max(...nums);
    var fatoriais = [ 1 , 1 ]; // já começa com os fatoriais de 0 e 1 ("trapaça"?)
    // assumindo que nunca terá números menores que 1
    var fat = 1;
    for (var i = 2; i <= maior; i++) {
        fat *= i;
        fatoriais[i] = fat; // já deixa pré-calculado todos
    }
    // depois pega só o que precisa
    var result = {};
    for (var n of nums) {
        result[n] = fatoriais[n];
    }
    return result;
}

Por exemplo, se os números forem 3, 4 e 5, primeiro ele vai calcular todos os fatoriais até 5, que é o maior valor de todos. Afinal, uma hora vai precisar fazer o loop até 5, então eu faço apenas uma vez, e aproveito para ir guardando os resultados intermediários. No final, fatoriais terá todos os fatoriais de 1 a 5.

Depois, basta pegar os fatoriais dos números que foram passados e retornar.


Fiz uns testes com o Benchmark.js e a primeira solução se mostrou mais rápida. Quando os primeiros números são maiores, a diferença diminui um pouco, por causa do que já foi dito (precisa recalcular os seguintes), mas ainda sim continua sendo mais rápida.

Que isso! Sensacional sua abordagem e análise, não tinha pensado em tantos detalhes assim. Com base em alguns pontos que você evidenciou, fiz algumas melhorias no meu código: ```js const calculeFactorial = (...numbers) => { const factorials = {}; let factorial = BigInt(1); for (const number of numbers) { // para sequencias numéricas que possuem valores repetidos, evitamos // recalcular o mesmo valor, checando se ele já foi calculado. Caso não, seguimos. if (!(number in factorials)) { // checa se o valor atual que será calculado, possui um antecessor já calculado. // seguindo a lógica evidenciada por você: 4! = 4 * 3! if (number - 1 in factorials) { factorials[number] = BigInt(number) * factorials[number - 1]; continue; } for (let i = BigInt(number); i >= 1; i--) factorial *= i; factorials[number] = factorial; factorial = BigInt(1); // reseta a variável } } return factorials; }; ```

bielgalvao,

no seu caso, os fatoriais (simples, não os duplos fatoriais) são as respostas que você espera. Supondo que você

1) não está utilizando a classe BigNumber do Javascript, precisando de poucos fatoriais que caibam no tipo de dados disponível para armazená-lo.

2) deseja apenas os fatoriais de números inteiros (existem fatoriais para números decimais também);

Look Up Tables (LUT) podem ser eficientes para recuperação de valores para algumas funções matemáticas com um custo. Se necessita de poucos valores, é útil mantê-los calculados em uma LUT, segundo também sugere kht. A recuperação é mais rápida do que o cálculo envolvendo várias operações de multiplicação de inteiros. Na prática, basta armazená-los em um vetor e recuperá-los pela posição (0 a 50, por exemplo).

Segue o script (não ótimo) para calculá-los com precisão arbitrária e gerar uma tabela simples com os primeiros fatoriais de inteiros. Devido a limitações de caracteres nesta publicação ("body" deve conter no máximo 20000 caracteres.), a tabela maior está em https://pastebin.com/raw/Yk0rdtC9 com um código não otimizado. Caso lhe interesse ver, use o modo raw, pois o Pastebin esconde o texto não interpretado com syntax highlight ativo.

#!/bin/bash
# Set the max length for bc results with no breaks
export BC_LINE_LENGTH=5000;

factorial() {
  if [ $1 -eq 0 ]; then echo 1;
  else bc -l <<< "define factorial(n) {if (n <= 0) return 1; return n * factorial(n - 1);} factorial($1)"
  fi
}

for number in {0..1000}; do
    echo "$number! = $(factorial $number)"
done
# Factorial from 0! to 1000! It took 4.7 seconds.

Por curiosidade encontrei um código mais otimizado em https://onlinemschool.com/math/formula/factorial_table. Levou pouco menos de 5s para calcular 99.999! com 456.569 dígitos.

PS: Acho que entendi mal sua pergunta, mas já havia respondido visando apenas o cálculo dos fatoriais, esquecendo-me da função que retorna os valores ordenados como pediu. : \

0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000
16! = 20922789888000
17! = 355687428096000
18! = 6402373705728000
19! = 121645100408832000
20! = 2432902008176640000
21! = 51090942171709440000
22! = 1124000727777607680000
23! = 25852016738884976640000
24! = 620448401733239439360000
25! = 15511210043330985984000000
26! = 403291461126605635584000000
27! = 10888869450418352160768000000
28! = 304888344611713860501504000000
29! = 8841761993739701954543616000000
30! = 265252859812191058636308480000000
31! = 8222838654177922817725562880000000
32! = 263130836933693530167218012160000000
33! = 8683317618811886495518194401280000000
34! = 295232799039604140847618609643520000000
35! = 10333147966386144929666651337523200000000
36! = 371993326789901217467999448150835200000000
37! = 13763753091226345046315979581580902400000000
38! = 523022617466601111760007224100074291200000000
39! = 20397882081197443358640281739902897356800000000
40! = 815915283247897734345611269596115894272000000000
41! = 33452526613163807108170062053440751665152000000000
42! = 1405006117752879898543142606244511569936384000000000
43! = 60415263063373835637355132068513997507264512000000000
44! = 2658271574788448768043625811014615890319638528000000000
45! = 119622220865480194561963161495657715064383733760000000000
46! = 5502622159812088949850305428800254892961651752960000000000
47! = 258623241511168180642964355153611979969197632389120000000000
48! = 12413915592536072670862289047373375038521486354677760000000000
49! = 608281864034267560872252163321295376887552831379210240000000000
50! = 30414093201713378043612608166064768844377641568960512000000000000
Mandou bem demais, ainda não me aventurei na recursividade.