Какая самая быстрая факториальная функция в JavaScript? [закрыто]


97

Ищете действительно быструю реализацию функции факториала в JavaScript. Есть предложения?


8
Каков возможный диапазон аргументов?
Никита Рыбак

5
Рассматривали ли вы предварительный расчет факториалов и сохранение значений в таблице поиска?
Валид Амджад

2
В чем применение такой функции? Другими словами, для чего вы собираетесь его использовать?
Pointy

@ Никита Рыбак, всего 1 агрумент (н). Если (n> 170) e = Infinity
Кен

@ Pointy, еще один сервис математических калькуляторов.
Кен

Ответы:


112

Вы можете искать (1 ... 100)! на Wolfram | Alpha для предварительного вычисления факториальной последовательности.

Первые 100 чисел:

1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000, 355687428096000, 6402373705728000, 121645100408832000, 2432902008176640000, 51090942171709440000, 1124000727777607680000, 25852016738884976640000, 620448401733239439360000, 15511210043330985984000000, 403291461126605635584000000, 10888869450418352160768000000, 304888344611713860501504000000, 8841761993739701954543616000000, 265252859812191058636308480000000, 8222838654177922817725562880000000, 263130836933693530167218012160000000, 8683317618811886495518194401280000000, 295232799039604140847618609643520000000, 10333147966386144929666651337523200000000, 371993326789901217467999448150835200000000, 13763753091226345046315979581580902400000000, 523022617466601111760007224100074291200000000, 20397882081197443358640281739902897356800000000, 815915283247897734345611269596115894272000000000, 33452526613163807108170062053440751665152000000000, 1405006117752879898543142606244511569936384000000000, 60415263063373835637355132068513997507264512000000000, 2658271574788448768043625811014615890319638528000000000, 119622220865480194561963161495657715064383733760000000000, 5502622159812088949850305428800254892961651752960000000000, 258623241511168180642964355153611979969197632389120000000000, 12413915592536072670862289047373375038521486354677760000000000, 608281864034267560872252163321295376887552831379210240000000000, 30414093201713378043612608166064768844377641568960512000000000000, 1551118753287382280224243016469303211063259720016986112000000000000, 80658175170943878571660636856403766975289505440883277824000000000000, 4274883284060025564298013753389399649690343788366813724672000000000000, 230843697339241380472092742683027581083278564571807941132288000000000000, 12696403353658275925965100847566516959580321051449436762275840000000000000, 710998587804863451854045647463724949736497978881168458687447040000000000000, 40526919504877216755680601905432322134980384796226602145184481280000000000000, 2350561331282878571829474910515074683828862318181142924420699914240000000000000, 138683118545689835737939019720389406345902876772687432540821294940160000000000000, 8320987112741390144276341183223364380754172606361245952449277696409600000000000000, 507580213877224798800856812176625227226004528988036003099405939480985600000000000000, 31469973260387937525653122354950764088012280797258232192163168247821107200000000000000, 1982608315404440064116146708361898137544773690227268628106279599612729753600000000000000, 126886932185884164103433389335161480802865516174545192198801894375214704230400000000000000, 8247650592082470666723170306785496252186258551345437492922123134388955774976000000000000000, 544344939077443064003729240247842752644293064388798874532860126869671081148416000000000000000, 36471110918188685288249859096605464427167635314049524593701628500267962436943872000000000000000, 2480035542436830599600990418569171581047399201355367672371710738018221445712183296000000000000000, 171122452428141311372468338881272839092270544893520369393648040923257279754140647424000000000000000, 11978571669969891796072783721689098736458938142546425857555362864628009582789845319680000000000000000, 850478588567862317521167644239926010288584608120796235886430763388588680378079017697280000000000000000, 61234458376886086861524070385274672740778091784697328983823014963978384987221689274204160000000000000000, 4470115461512684340891257138125051110076800700282905015819080092370422104067183317016903680000000000000000, 330788544151938641225953028221253782145683251820934971170611926835411235700971565459250872320000000000000000, 24809140811395398091946477116594033660926243886570122837795894512655842677572867409443815424000000000000000000, 1885494701666050254987932260861146558230394535379329335672487982961844043495537923117729972224000000000000000000, 145183092028285869634070784086308284983740379224208358846781574688061991349156420080065207861248000000000000000000, 11324281178206297831457521158732046228731749579488251990048962825668835325234200766245086213177344000000000000000000, 894618213078297528685144171539831652069808216779571907213868063227837990693501860533361810841010176000000000000000000, 71569457046263802294811533723186532165584657342365752577109445058227039255480148842668944867280814080000000000000000000, 5797126020747367985879734231578109105412357244731625958745865049716390179693892056256184534249745940480000000000000000000, 475364333701284174842138206989404946643813294067993328617160934076743994734899148613007131808479167119360000000000000000000, 39455239697206586511897471180120610571436503407643446275224357528369751562996629334879591940103770870906880000000000000000000, 3314240134565353266999387579130131288000666286242049487118846032383059131291716864129885722968716753156177920000000000000000000, 281710411438055027694947944226061159480056634330574206405101912752560026159795933451040286452340924018275123200000000000000000000, 24227095383672732381765523203441259715284870552429381750838764496720162249742450276789464634901319465571660595200000000000000000000, 2107757298379527717213600518699389595229783738061356212322972511214654115727593174080683423236414793504734471782400000000000000000000, 185482642257398439114796845645546284380220968949399346684421580986889562184028199319100141244804501828416633516851200000000000000000000, 16507955160908461081216919262453619309839666236496541854913520707833171034378509739399912570787600662729080382999756800000000000000000000, 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000, 135200152767840296255166568759495142147586866476906677791741734597153670771559994765685283954750449427751168336768008192000000000000000000000, 12438414054641307255475324325873553077577991715875414356840239582938137710983519518443046123837041347353107486982656753664000000000000000000000, 1156772507081641574759205162306240436214753229576413535186142281213246807121467315215203289516844845303838996289387078090752000000000000000000000, 108736615665674308027365285256786601004186803580182872307497374434045199869417927630229109214583415458560865651202385340530688000000000000000000000, 10329978488239059262599702099394727095397746340117372869212250571234293987594703124871765375385424468563282236864226607350415360000000000000000000000, 991677934870949689209571401541893801158183648651267795444376054838492222809091499987689476037000748982075094738965754305639874560000000000000000000000, 96192759682482119853328425949563698712343813919172976158104477319333745612481875498805879175589072651261284189679678167647067832320000000000000000000000, 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000, 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000, 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Если вы все еще хотите вычислить значения самостоятельно, вы можете использовать мемоизацию :

var f = [];
function factorial (n) {
  if (n == 0 || n == 1)
    return 1;
  if (f[n] > 0)
    return f[n];
  return f[n] = factorial(n-1) * n;
}

Изменить: 21.08.2014

Решение 2

Я подумал, что было бы полезно добавить рабочий пример ленивой итеративной факториальной функции, которая использует большие числа для получения точного результата с мемоизацией и кешем для сравнения.

var f = [new BigNumber("1"), new BigNumber("1")];
var i = 2;
function factorial(n)
{
  if (typeof f[n] != 'undefined')
    return f[n];
  var result = f[i-1];
  for (; i <= n; i++)
      f[i] = result = result.multiply(i.toString());
  return result;
}
var cache = 100;
// Due to memoization, following line will cache first 100 elements.
factorial(cache);

Я предполагаю, что вы использовали бы какое-то закрытие, чтобы ограничить видимость имени переменной.

Ссылка : BigNumber Sandbox : JsFiddle


Значения после 6402373705728000 будут усечены, поэтому, если вы собираетесь использовать этот подход, убедитесь, что преобразовали в экспоненциальный, прежде чем использовать вышеупомянутую таблицу.
Дэвид Скотт Кирби

1
@DavidScottKirby Javascript автоматически преобразует эти числа в их ближайшее 64-битное представление с плавающей запятой. Настоящее преимущество отсутствия в коде чисел полной точности - уменьшение размера файла.
le_m 09

Ваше второе решение можно упростить, чтобы function factorial (n) { for (var i = f.length; i <= n; i++) f.push(f[i - 1].multiply(i.toString())); return f[n]; }увидеть также мой ответ, в котором используется более поздняя встроенная, BigIntа не сторонняя библиотека.
Патрик Робертс,

Итак, 100-е число состоит из 158 символов
Barbu Barbu

99

Вам следует использовать петлю.

Вот две версии, протестированные путем вычисления факториала 100 в 10.000 раз.

Рекурсивный

function rFact(num)
{
    if (num === 0)
      { return 1; }
    else
      { return num * rFact( num - 1 ); }
}

Итеративный

function sFact(num)
{
    var rval=1;
    for (var i = 2; i <= num; i++)
        rval = rval * i;
    return rval;
}

Жить в : http://jsfiddle.net/xMpTv/

Мои результаты показывают:
- Рекурсивный ~ 150 миллисекунд
- Итеративный ~ 5 миллисекунд ..


+1 Отличный ответ! Хотя мемоизация может быть разумной, когда есть несколько вызовов для вычисления факториалов для больших чисел.
Tadeck

@Tadeck, спасибо. Действительно, мемоизация очень полезна в этом случае, и поэтому ответ Маргуса выбран как правильный :)
Габриэле Petrioli

Однострочная версия рекурсии: function factorial (num) {return (num == 1)? число: число * аргументы. callee (число-1); }
jbyrd 03

2
@HWTech, вы никогда не вызываете методы. Ваш тест сравнивает скорость определения двух методов ... а не время, необходимое для их выполнения ... Это лучше тест (с использованием только факториала 15)
Габриэле Петриоли

4
Вместо rval = rval * i;вас можно было написатьrval *= i;
Райан

29

Я по-прежнему считаю, что ответ Маргуса лучший. Однако, если вы хотите вычислить факториалы чисел в диапазоне от 0 до 1 (т.е. гамма-функцию), вы не можете использовать этот подход, потому что таблица поиска должна содержать бесконечные значения.

Однако вы можете аппроксимировать значения факториалов, и это довольно быстро, быстрее, чем рекурсивный вызов самого себя или, по крайней мере, цикл (особенно, когда значения начинают увеличиваться).

Хороший метод приближения - метод Ланцоша.

Вот реализация на JavaScript (перенесенная с калькулятора, который я написал несколько месяцев назад):

function factorial(op) {
 // Lanczos Approximation of the Gamma Function
 // As described in Numerical Recipes in C (2nd ed. Cambridge University Press, 1992)
 var z = op + 1;
 var p = [1.000000000190015, 76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 1.208650973866179E-3, -5.395239384953E-6];

 var d1 = Math.sqrt(2 * Math.PI) / z;
 var d2 = p[0];

 for (var i = 1; i <= 6; ++i)
  d2 += p[i] / (z + i);

 var d3 = Math.pow((z + 5.5), (z + 0.5));
 var d4 = Math.exp(-(z + 5.5));

 d = d1 * d2 * d3 * d4;

 return d;
}

Теперь вы можете делать такие крутые вещи, как factorial(0.41)и т. Д., Но точность может быть немного неточной, в конце концов, это приблизительное значение результата.


довольно интересный подход, спасибо.
Кен

Просто сэкономил мне массу времени, большое спасибо :)
nicolaskruchten

Я рекомендую изменить часть под циклом for на var d3d4 = Math.exp((z + 0.5) * Math.log(z + 5.5) - z - 5.5); return d1 * d2 * d3d4;. Это позволяет вам вычислять факториалы до 169! вместо нынешних всего 140 !. Это довольно близко к максимально представимому факториалу с использованием Numberтипа данных, который равен 170 !.
le_m 09

18

Таблица поиска - очевидный путь, если вы работаете с натуральными числами. Чтобы вычислить любой факториал в реальном времени, вы можете ускорить его с помощью кеша, сохранив числа, которые вы вычисляли ранее. Что-то типа:

factorial = (function() {
    var cache = {},
        fn = function(n) {
            if (n === 0) {
                return 1;
            } else if (cache[n]) {
                return cache[n];
            }
            return cache[n] = n * fn(n -1);
        };
    return fn;
})();

Вы можете предварительно рассчитать некоторые значения, чтобы ускорить его еще больше.


3
На основе этого ответа я создал автоматическую запоминание для любой заданной функции (также немного быстрее :)), включая ограничение на размер кеша. stackoverflow.com/a/10031674/36537
Phil H

16

Вот мое решение:

function fac(n){
    return(n<2)?1:fac(n-1)*n;
}

Это самый простой способ (меньше символов / строк), который я нашел, только функция с одной строкой кода.


Изменить:
если вы действительно хотите сохранить несколько символов, вы можете использовать стрелки (21 байт) :

f=n=>(n<2)?1:f(n-1)*n

7
Сэкономьте еще больше с f=n=>n?f(n-1)*n:1...
le_m 09

К сожалению, даже если это приятно видеть и коротко по форме, это самый медленный способ сделать это.
Зибри 01

12

Всего одна линия с ES6

const factorial = n => !(n > 1) ? 1 : factorial(n - 1) * n;


factorial = n => n <= 1 ? 1 : factorial(n - 1) * n
Naramsim

10

короткая и простая рекурсивная функция (вы тоже можете сделать это с помощью цикла, но я не думаю, что это повлияет на производительность):

function factorial (n){
  if (n==0 || n==1){
    return 1;
  }
  return factorial(n-1)*n;
} 

для очень больших n вы можете использовать приближение Стиллингса, но это даст вам только приблизительное значение.

РЕДАКТИРОВАТЬ: комментарий о том, почему я получаю отрицательный голос за это, было бы неплохо ...

РЕДАКТИРОВАТЬ2: это было бы сутью с использованием цикла (что было бы лучшим выбором):

function factorial (n){
  j = 1;
  for(i=1;i<=n;i++){
    j = j*i;
  }
  return j;
}

Я думаю , что лучшим решением было бы использовать кэшированные значения, как уже упоминалось Маргус и использовать приближение stirlings для больших значений (предполагается , что вы должны быть действительно быстро и не должны быть , что точно на таких больших чисел).


3
В языках без оптимизации хвостового вызова (то есть в наиболее широко используемых языках) лучше использовать нерекурсивную реализацию, где это легко сделать, хотя есть способы обойти это: paulbarry.com/articles/2009/08/30 / tail-call-
optimisation

это действительно определенно не так быстро, поскольку в нем даже не использовалась бы совокупная стоимость владения, если бы она была реализована. Но это просто, и я бы не стал его отрицать. Это точно не самый быстрый.
haylem

Оптимизация хвостового вызова даже невозможна для этой функции, так как рекурсивный вызов не находится в хвостовой позиции.
Фред Фу,

3
@Josh, ( не тот, кто проголосовал против ), самый быстрый цикл с
большим

7

Взгляните на мемоизатор, который принимает любую функцию с одним аргументом и запоминает ее. Оказывается, он немного быстрее, чем решение @ xPheRe , включая ограничение на размер кеша и связанную проверку, потому что я использую сокращение и так далее.

function memoize(func, max) {
    max = max || 5000;
    return (function() {
        var cache = {};
        var remaining = max;
        function fn(n) {
            return (cache[n] || (remaining-- >0 ? (cache[n]=func(n)) : func(n)));
        }
        return fn;
    }());
}

function fact(n) {
    return n<2 ? 1: n*fact(n-1);
}

// construct memoized version
var memfact = memoize(fact,170);

// xPheRe's solution
var factorial = (function() {
    var cache = {},
        fn = function(n) {
            if (n === 0) {
                return 1;
            } else if (cache[n]) {
                return cache[n];
            }
            return cache[n] = n * fn(n -1);
        };
    return fn;
}());

Примерно в 25 раз быстрее на моей машине в Chrome, чем рекурсивная версия, и на 10% быстрее, чем у xPheRe.


6

Самая быстрая факториальная функция

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

function factorial(n, r = 1) {
  while (n > 0) r *= n--;
  return r;
}

// Default parameters `r = 1`,
//   was introduced in ES6

И вот мои рассуждения:

  • Рекурсивные функции, даже с мемоизацией, имеют накладные расходы, связанные с вызовом функции (в основном, помещая функции в стек), что менее эффективно, чем использование цикла.
  • Хотя forциклы и whileциклы имеют одинаковую производительность, forцикл без выражения инициализации и конечного выражения выглядит странно; наверное лучше написать for(; n > 0;)какwhile(n > 0)
  • Используются только два параметра nи r, поэтому теоретически меньше параметров означает меньше времени, затрачиваемого на выделение памяти.
  • Использует декрементированный цикл, который проверяет, nравно ли нулю - я слышал теории о том, что компьютеры лучше проверяют двоичные числа (0 и 1), чем другие целые числа.

5

Я наткнулся на этот пост. Вдохновленный всеми представленными здесь материалами, я придумал свою собственную версию, в которой есть две функции, которые я раньше не видел: 1) Проверка, чтобы убедиться, что аргумент является неотрицательным целым числом 2) Создание единицы из кеша и функция, чтобы сделать его одним автономным битом кода. Ради интереса постарался сделать его максимально компактным. Некоторым это может показаться элегантным, другим - ужасно непонятным. Во всяком случае, вот оно:

var fact;
(fact = function(n){
    if ((n = parseInt(n)) < 0 || isNaN(n)) throw "Must be non-negative number";
    var cache = fact.cache, i = cache.length - 1;
    while (i < n) cache.push(cache[i++] * i);
    return cache[n];
}).cache = [1];

Вы можете либо предварительно заполнить кеш, либо разрешить ему заполняться по мере прохождения вызовов. Но начальный элемент (для факта (0) должен присутствовать, иначе он сломается.

Наслаждаться :)



4

Вот одно из решений:

function factorial(number) {
  total = 1
  while (number > 0) {
    total *= number
    number = number - 1
  }
  return total
}

4

Используя ES6, вы можете добиться этого быстро и быстро:

const factorial = n => [...Array(n + 1).keys()].slice(1).reduce((acc, cur) => acc * cur, 1)

3

Код для вычисления факториала зависит от ваших требований.

  1. Вас беспокоит переполнение?
  2. Какой диапазон входов у вас будет?
  3. Для вас важнее минимизировать размер или время?
  4. Что вы собираетесь делать с факториалом?

Что касается пунктов 1 и 4, часто бывает более полезно иметь функцию для оценки журнала факториала напрямую, чем иметь функцию для оценки самого факториала.

Вот сообщение в блоге, в котором обсуждаются эти проблемы. Вот некоторый код C # для вычисления лог-факториала, который было бы тривиально перенести на JavaScript. Но это может не подходить для ваших нужд в зависимости от ваших ответов на вопросы выше.


Нумерованный список наверное стоит в комментариях. Все, что осталось, это две ссылки, и ответы только по ссылкам не приветствуются.
Barett

3

Это компактная версия на основе петель

function factorial( _n )
{
    var _p = 1 ;
    while( _n > 0 ) { _p *= _n-- ; }
    return _p ;
}

Или вы можете переопределить объект Math (рекурсивная версия):

Math.factorial = function( _x )  { return _x <= 1 ? 1 : _x * Math.factorial( --_x ) ; }

Или присоединитесь к обоим подходам ...


1
Я исправил это в приведенном выше коде. Спасибо!
Сандро Роза,

3

Воспользовавшись тем фактом, что Number.MAX_VALUE < 171!мы можем просто использовать полную таблицу поиска, состоящую всего из 171 элемента компактного массива, занимающего менее 1,4 килобайт памяти.

Тогда функция быстрого поиска со сложностью выполнения O (1) и минимальными накладными расходами на доступ к массиву будет выглядеть следующим образом:

// Lookup table for n! for 0 <= n <= 170:
const factorials = [1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600,6227020800,87178291200,1307674368e3,20922789888e3,355687428096e3,6402373705728e3,121645100408832e3,243290200817664e4,5109094217170944e4,1.1240007277776077e21,2.585201673888498e22,6.204484017332394e23,1.5511210043330986e25,4.0329146112660565e26,1.0888869450418352e28,3.0488834461171387e29,8.841761993739702e30,2.6525285981219107e32,8.222838654177922e33,2.631308369336935e35,8.683317618811886e36,2.9523279903960416e38,1.0333147966386145e40,3.7199332678990125e41,1.3763753091226346e43,5.230226174666011e44,2.0397882081197444e46,8.159152832478977e47,3.345252661316381e49,1.40500611775288e51,6.041526306337383e52,2.658271574788449e54,1.1962222086548019e56,5.502622159812089e57,2.5862324151116818e59,1.2413915592536073e61,6.082818640342675e62,3.0414093201713376e64,1.5511187532873822e66,8.065817517094388e67,4.2748832840600255e69,2.308436973392414e71,1.2696403353658276e73,7.109985878048635e74,4.0526919504877214e76,2.3505613312828785e78,1.3868311854568984e80,8.32098711274139e81,5.075802138772248e83,3.146997326038794e85,1.98260831540444e87,1.2688693218588417e89,8.247650592082472e90,5.443449390774431e92,3.647111091818868e94,2.4800355424368305e96,1.711224524281413e98,1.1978571669969892e100,8.504785885678623e101,6.1234458376886085e103,4.4701154615126844e105,3.307885441519386e107,2.48091408113954e109,1.8854947016660504e111,1.4518309202828587e113,1.1324281178206297e115,8.946182130782976e116,7.156945704626381e118,5.797126020747368e120,4.753643337012842e122,3.945523969720659e124,3.314240134565353e126,2.81710411438055e128,2.4227095383672734e130,2.107757298379528e132,1.8548264225739844e134,1.650795516090846e136,1.4857159644817615e138,1.352001527678403e140,1.2438414054641308e142,1.1567725070816416e144,1.087366156656743e146,1.032997848823906e148,9.916779348709496e149,9.619275968248212e151,9.426890448883248e153,9.332621544394415e155,9.332621544394415e157,9.42594775983836e159,9.614466715035127e161,9.90290071648618e163,1.0299016745145628e166,1.081396758240291e168,1.1462805637347084e170,1.226520203196138e172,1.324641819451829e174,1.4438595832024937e176,1.588245541522743e178,1.7629525510902446e180,1.974506857221074e182,2.2311927486598138e184,2.5435597334721877e186,2.925093693493016e188,3.393108684451898e190,3.969937160808721e192,4.684525849754291e194,5.574585761207606e196,6.689502913449127e198,8.094298525273444e200,9.875044200833601e202,1.214630436702533e205,1.506141741511141e207,1.882677176888926e209,2.372173242880047e211,3.0126600184576594e213,3.856204823625804e215,4.974504222477287e217,6.466855489220474e219,8.47158069087882e221,1.1182486511960043e224,1.4872707060906857e226,1.9929427461615188e228,2.6904727073180504e230,3.659042881952549e232,5.012888748274992e234,6.917786472619489e236,9.615723196941089e238,1.3462012475717526e241,1.898143759076171e243,2.695364137888163e245,3.854370717180073e247,5.5502938327393044e249,8.047926057471992e251,1.1749972043909107e254,1.727245890454639e256,2.5563239178728654e258,3.80892263763057e260,5.713383956445855e262,8.62720977423324e264,1.3113358856834524e267,2.0063439050956823e269,3.0897696138473508e271,4.789142901463394e273,7.471062926282894e275,1.1729568794264145e278,1.853271869493735e280,2.9467022724950384e282,4.7147236359920616e284,7.590705053947219e286,1.2296942187394494e289,2.0044015765453026e291,3.287218585534296e293,5.423910666131589e295,9.003691705778438e297,1.503616514864999e300,2.5260757449731984e302,4.269068009004705e304,7.257415615307999e306];

// Lookup function:
function factorial(n) {
  return factorials[n] || (n > 170 ? Infinity : NaN);
}

// Test cases:
console.log(factorial(NaN));       // NaN
console.log(factorial(-Infinity)); // NaN
console.log(factorial(-1));        // NaN
console.log(factorial(0));         // 1
console.log(factorial(170));       // 7.257415615307999e+306 < Number.MAX_VALUE
console.log(factorial(171));       // Infinity > Number.MAX_VALUE
console.log(factorial(Infinity));  // Infinity

Это так же точно и быстро, как и при использовании Numberтипа данных. Вычисление таблицы поиска в Javascript - как предлагают некоторые другие ответы - снизит точность, когда n! > Number.MAX_SAFE_INTEGER.

Сжатие таблицы времени выполнения с помощью gzip уменьшает ее размер на диске примерно с 3,6 до 1,8 килобайт.


3

Однострочный ответ:

const factorial = (num, accumulator) => num <= 1 ? accumulator || 1 : factorial(--num, num * (accumulator || num + 1));

factorial(5); // 120
factorial(10); // 3628800
factorial(3); // 6
factorial(7); // 5040
// et cetera


3

Итерационный факториал с BigIntдля безопасности

Решение использует BigIntфункцию ES 2018 + / 2019.

Это рабочий пример использования BigInt, потому что многие ответы здесь почти сразу выходят за безопасную границу Number(MDN). Это не самый быстрый, но простой и, следовательно, более понятный способ адаптации других оптимизаций (например, кеш первых 100 чисел).

function factorial(nat) {
   let p = BigInt(1)
   let i = BigInt(nat)

   while (1 < i--) p *= i

   return p
}

Пример использования

// 9.332621544394415e+157
Number(factorial(100))

// "933262154439441526816992388562667004907159682643816214685929638952175999
//  932299156089414639761565182862536979208272237582511852109168640000000000
//  00000000000000"
String(factorial(100))

// 9332621544394415268169923885626670049071596826438162146859296389521759999
// 3229915608941463976156518286253697920827223758251185210916864000000000000
// 000000000000n
factorial(100)
  • Знак nв конце числового литерала как 1303nуказывает на BigIntтип.
  • Помните, что вы не должны смешивать BigIntс, Numberесли вы явно не принуждаете их, и это может привести к потере точности.

3

Используя функции ES6, можно писать код в ОДНОЙ строке и без рекурсии :

var factorial=(n)=>Array.from({length: n},(v, k) => k+1).reduce((a, b) => a*b, 1)


2

Просто для полноты, вот рекурсивная версия, которая позволяет оптимизировать хвостовой вызов. Я не уверен, что оптимизация хвостовых вызовов выполняется в JavaScript ...

function rFact(n, acc)
{
    if (n == 0 || n == 1) return acc; 
    else return rFact(n-1, acc*n); 
}

Чтобы назвать это:

rFact(x, 1);

ES6 поддерживает совокупную стоимость владения, но, по-видимому, эта функция по умолчанию еще не активна ни на одном из основных движков
le_m

2

Это итеративное решение, использующее меньше места в стеке и сохраняющее ранее вычисленные значения с возможностью самопоминания:

Math.factorial = function(n){
    if(this.factorials[n]){ // memoized
        return this.factorials[n];
    }
    var total=1;
    for(var i=n; i>0; i--){
        total*=i;
    }
    this.factorials[n] = total; // save
    return total;
};
Math.factorials={}; // store

Также обратите внимание, что я добавляю это к объекту Math, который является литералом объекта, поэтому прототипа нет. Скорее просто привязывайте их к функции напрямую.


На самом деле это не в полной мере использует преимущества мемоизации для подзадач - например, Math.factorial(100); Math.factorial(500);дважды вычисляет умножение 1..100.
Barett

2

Я считаю, что следующий фрагмент кода является наиболее устойчивым и эффективным из приведенных выше комментариев. Вы можете использовать это в своей глобальной архитектуре js приложения ... и не беспокойтесь о том, чтобы писать его в нескольких пространствах имен (поскольку это задача, которая, вероятно, не требует значительного дополнения). Я включил два имени метода (в зависимости от предпочтений), но оба могут использоваться, поскольку они просто ссылки.

Math.factorial = Math.fact = function(n) {
    if (isNaN(n)||n<0) return undefined;
    var f = 1; while (n > 1) {
        f *= n--;
    } return f;
};

Начав умножение с n * (n-1) * (n-2) * ... * 1вместо наоборот, вы потеряете до 4 цифр в точности для n >> 20.
le_m 09

2
// if you don't want to update the Math object, use `var factorial = ...`
Math.factorial = (function() {
    var f = function(n) {
        if (n < 1) {return 1;}  // no real error checking, could add type-check
        return (f[n] > 0) ? f[n] : f[n] = n * f(n -1);
    }
    for (i = 0; i < 101; i++) {f(i);} // precalculate some values
    return f;
}());

factorial(6); // 720, initially cached
factorial[6]; // 720, same thing, slightly faster access, 
              // but fails above current cache limit of 100
factorial(100); // 9.33262154439441e+157, called, but pulled from cache
factorial(142); // 2.6953641378881614e+245, called
factorial[141]; // 1.89814375907617e+243, now cached

Это выполняет кэширование первых 100 значений на лету и не вводит внешнюю переменную в область действия кеша, сохраняя значения как свойства самого объекта функции, что означает, что, если вы знаете, factorial(n)что уже вычислено, вы можете просто назовите его factorial[n], что немного эффективнее. Запуск этих первых 100 значений в современных браузерах займет менее миллисекунды.


Я понял это после 21! цифры ненадежны.
AutoSponge

@AutoSponge Это потому 21! > Number.MAX_SAFE_INTEGER, что , таким образом, его нельзя безопасно представить как 64-битное число с плавающей запятой.
le_m 09


2

Вот один, который я сделал сам, не используйте числа больше 170 или меньше 2.

function factorial(x){
 if((!(isNaN(Number(x)))) && (Number(x)<=170) && (Number(x)>=2)){
  x=Number(x);for(i=x-(1);i>=1;--i){
   x*=i;
  }
 }return x;
}

Начав умножение с n * (n-1) * (n-2) * ... * 1, а не наоборот, вы потеряете до 4 цифр в точности для n >> 20. Кроме того, создается нежелательный глобальная переменная iи выполняет слишком много Numberпреобразований и дает неверные результаты для 0! (как вы сказали, но почему?).
le_m 09

2

Вот мой код

function factorial(num){
    var result = num;
    for(i=num;i>=2;i--){
        result = result * (i-1);
    }
    return result;
}

1
Если (n> 170) e = Бесконечность. И ваш код сгенерирует огромное количество. не будет ли переполнений?
Prime

Неверный результат для factorial(0). Кроме того, начав умножение с n * (n-1) * (n-2) * ... * 1, а не наоборот, вы потеряете до 4 цифр в точности для n >> 20. @prime: 170! > Number.MAX_VALUEи лучше всего представлен с помощью Infinity.
le_m 09

2

Кешированный цикл должен быть самым быстрым (по крайней мере, при многократном вызове)

var factorial = (function() {
  var x =[];

  return function (num) {
    if (x[num] >0) return x[num];
    var rval=1;
    for (var i = 2; i <= num; i++) {
        rval = rval * i;
        x[i] = rval;
    }
    return rval;
  }
})();

2
function isNumeric(n) {
    return !isNaN(parseFloat(n)) && isFinite(n)
}

Предоставляется http://javascript.info/tutorial/number-math как простой способ оценить, является ли объект правильным целым числом для расчета.

var factorials=[[1,2,6],3];

Простой набор мемоизированных факториалов, которые требуют избыточных вычислений, могут быть обработаны с помощью "умножения на 1" или представляют собой одну цифру, которая представляет собой простое уравнение, не стоящее обработки вживую.

var factorial = (function(memo,n) {
    this.memomize = (function(n) {
        var ni=n-1;
        if(factorials[1]<n) {
            factorials[0][ni]=0;
            for(var factorial_index=factorials[1]-1;factorials[1]<n;factorial_index++) {
                factorials[0][factorials[1]]=factorials[0][factorial_index]*(factorials[1]+1);
                factorials[1]++;
            }
        }
    });
    this.factorialize = (function(n) {
        return (n<3)?n:(factorialize(n-1)*n);
    });
    if(isNumeric(n)) {
        if(memo===true) {
            this.memomize(n);
            return factorials[0][n-1];
        }
        return this.factorialize(n);
    }
    return factorials;
});

Изучив ввод от других участников (за исключением рекомендаций журнала, хотя я могу реализовать это позже), я пошел дальше и составил довольно простой сценарий. Я начал с простого необразованного примера ООП на JavaScript и построил небольшой класс для обработки факториалов. Затем я реализовал свою версию мемоизации, предложенную выше. Я также реализовал сокращенную факториализацию, но сделал небольшую корректировку ошибок; Я изменил «n <2» на «n <3». «n <2» по-прежнему будет обрабатывать n = 2, что было бы пустой тратой, потому что вы бы выполняли итерацию для 2 * 1 = 2; на мой взгляд, это бесполезная трата. Я изменил его на «n <3»; потому что, если n равно 1 или 2, он просто вернет n, если он равен 3 или более, он будет оцениваться нормально. Конечно, поскольку применяются правила, я разместил свои функции в порядке убывания предполагаемого выполнения. Я добавил опцию bool (true | false), чтобы разрешить быстрое переключение между memo'ed и нормальным выполнением (вы просто никогда не знаете, когда вы захотите поменять местами на своей странице, не изменяя «стиль»). Как я уже говорил перед Переменная memoized factorials задается с 3 начальными позициями, принимает 4 символа и сводит к минимуму бесполезные вычисления. После третьей итерации вы обрабатываете двузначный математический плюс. Я полагаю, если вы достаточно привержены этому, вы бы использовали факториальную таблицу (как реализовано). взяв 4 символа и минимизируя бесполезные вычисления. После третьей итерации вы обрабатываете двузначный математический плюс. Я полагаю, если вы достаточно привержены этому, вы бы использовали факториальную таблицу (как реализовано). взяв 4 символа и минимизируя бесполезные вычисления. После третьей итерации вы обрабатываете двузначный математический плюс. Я полагаю, если вы достаточно привержены этому, вы бы использовали факториальную таблицу (как реализовано).

Что я планировал после этого? локальное & | хранилище сеансов, чтобы обеспечить возможность кэширования необходимых итераций в каждом конкретном случае, по существу решая проблему "таблицы", о которой говорилось выше. Это также значительно сэкономит место на стороне базы данных и сервера. Однако, если вы выберете localStorage, вы, по сути, будете занимать место на своем компьютере пользователя, просто чтобы сохранить список чисел и заставить их экран СМОТРЕТЬ быстрее, однако в течение длительного периода времени с огромной потребностью это будет медленным. Я думаю, что sessionStorage (очистка после выхода вкладки) будет намного лучшим способом. Возможно ли совместить это с самобалансирующимся сервером / локальным зависимым кешем? Пользователю A нужно X итераций. Пользователю B нужно Y итераций. X + Y / 2 = сумма, необходимая для локального кеширования. Затем просто обнаруживайте и играйте с тестами времени загрузки и выполнения в реальном времени для каждого пользователя, пока он не настроится на оптимизацию для самого сайта. Благодарность!

Изменить 3:

var f=[1,2,6];
var fc=3;
var factorial = (function(memo) {
    this.memomize = (function(n) {
        var ni=n-1;
        if(fc<n) {
            for(var fi=fc-1;fc<n;fi++) {
                f[fc]=f[fi]*(fc+1);
                fc++;
            }
        }
        return f[ni];
    });

    this.factorialize = (function(n) {
        return (n<3)?n:(factorialize(n-1)*n);
    });

    this.fractal = (function (functio) {
        return function(n) {
            if(isNumeric(n)) {
                return functio(n);
            }
            return NaN;
        }
    });

    if(memo===true) {
        return this.fractal(memomize);
    }
    return this.fractal(factorialize);
});

Это редактирование реализует другое предложение стека и позволяет мне вызывать функцию как factorial (true) (5), что было одной из моих целей. : 3 Я также удалил некоторые ненужные присвоения и сократил некоторые закрытые имена переменных.


Возврат undefinedза 0 !. ES6 позволяет заменить isNumericна Number.isInteger. Строки вроде factorials[0][factorials[1]]=factorials[0][factorial_index]*(factorials[1]+1);совершенно нечитаемы.
le_m 09

2

Вот один, использующий новые функции javascript fill , map , reduce и конструктор (и синтаксис жирной стрелки):

Math.factorial = n => n === 0 ? 1 : Array(n).fill(null).map((e,i)=>i+1).reduce((p,c)=>p*c)

Изменить: обновлено для обработки n === 0


2
Это одна ужасно уродливая нечитаемая строка кода.
jungledev

1
Отличная идея. Вместо того, чтобы дважды обходить длину, почему бы не преобразовать всю логику в функцию сокращения и не использовать ее начальное значение для обработки крайнего случая n === 0? Math.factorial = n => Array.from({ length: n }).reduce((product, _, i) => product * (i + 1), 1)
AlexSashaRegan

2
function computeFactorialOfN(n) {
  var output=1;
  for(i=1; i<=n; i++){
    output*=i;
  } return output;
}
computeFactorialOfN(5);

2
Добро пожаловать в StackOverflow и благодарим за помощь. Возможно, вы захотите улучшить свой ответ, добавив пояснения.
Elias MP
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.