Executando verificação de segurança...
3

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.

3

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.

1

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:

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;
};
2

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
1