Яка найшвидша функціональна функція в JavaScript? [зачинено]


94

Шукаємо дійсно швидкої реалізації факторіальної функції в JavaScript. Будь-яка пропозиція?


8
Який діапазон аргументів можливий?
Микита Рибак,

5
Ви розглядали можливість попереднього обчислення факторіалів та збереження значень у таблиці пошуку?
Waleed Amjad

2
Яке застосування такої функції? Іншими словами, для чого ви збираєтеся його використовувати?
Пойнті

@ Нікіта Рибак, лише 1 аргумент (n). Якщо (n> 170) e = Нескінченність
Кен

@ Pointy, ще одна послуга з математичного калькулятора.
Ken

Відповіді:


110

Ви можете шукати (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);

Припускаю, ви скористаєтесь яким- небудь закриттям, щоб обмежити видимість імен змінних.

Ref : BigNumber пісочниці : JsFiddle


Значення після 6402373705728000 будуть усічені, тому, якщо ви збираєтеся використовувати цей підхід, обов’язково перетворіть в експоненційне перед використанням згаданої таблиці.
Девід Скотт Кірбі,

1
@DavidScottKirby Javascript автоматично перетворює ці числа в їх найближче 64-розрядне плаваюче представлення. Реальною перевагою відсутності в коді повної точності цифр є зменшений розмір файлу.
le_m

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

97

Ви повинні використовувати цикл.

Ось дві версії, порівняльні за допомогою обчислення факторіалу 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 19.03.12

@Tadeck, дякую. Дійсно запам'ятовуванням дуже корисно в цьому випадку , і саме тому Маргус відповідь обраний як один правильний :)
Габріеле Petrioli

Однорядкова версія рекурсивного: функція факторіал (num) {return (num == 1)? num: num * argument.callee (num-1); }
jbyrd

2
@HWTech, ти ніколи не викликаєш методи. Ваш тест порівнює швидкість визначення двох методів .. не час, який вони потребують для виконання .. Це кращий тест (пробуючи лише факторіал з 15)
Габріеле Петріолі

3
Замість цього 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)тощо, проте точність може бути трохи меншою, врешті-решт, це наближення результату.


досить цікавий підхід, дякую.
Ken

Просто врятував мені
купу

Я рекомендую змінити частину нижче циклу 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

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

на жаль, навіть якщо це приємно бачити і короткий за формою, це найповільніший спосіб це зробити.
Zibri

11

Лише один рядок з ES6

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


factorial = n => n <= 1 ? 1 : factorial(n - 1) * n
Нарамсім,

10

коротка і легка рекурсивна функція (ви також можете це зробити за допомогою циклу, але я не думаю, що це буде мати різницю в продуктивності):

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

для дуже великого n ви можете скористатися наближенням Стірлінга - але це дасть вам лише приблизне значення.

РЕДАГУВАТИ: коментар щодо того, чому я отримую проти цього, було б непогано ...

EDIT2: це буде роздум за допомогою циклу (що буде кращим вибором):

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-optimization
Даніель Ервікер

це насправді не настільки швидко, оскільки він навіть не використовував би TCO, якби його впроваджували. Але це просто, і я не хотів би проти цього. Це не найшвидше точно.
haylem

Оптимізація хвостового виклику навіть неможлива для цієї функції, оскільки рекурсивний виклик не знаходиться в положенні хвоста.
Fred Foo

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

Це дуже просто за допомогою ES6

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

Дивіться приклад тут


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 підтримує TCO, однак ця функція ще не активна за замовчуванням у будь-якому великому двигуні
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 програми ... і, не турбуючись про те, щоб писати його в декількох просторах імен (оскільки це завдання, яке, ймовірно, не потребує великого збільшення). Я включив 2 імена методів (залежно від уподобань), але обидва можна використовувати, оскільки це лише посилання.

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

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


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

2

Ось мій код

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

1
Якщо (n> 170) e = нескінченність. І ваш код створить величезну кількість. не буде переповнення?
прем'єра

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

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), щоб дозволити швидкий перехід між записаним та нормальним виконанням (Ви просто ніколи не знаєте, коли хочете поміняти місцями на своїй сторінці без необхідності змінювати "стиль") Як я вже говорив до запам'ятована змінна факторів встановлюється з 3 вихідних позицій, займаючи 4 символи, і мінімізуючи марнотратні розрахунки. Все, що минуло третю ітерацію, ви обробляєте двозначні математичні плюси. Я вважаю, що якщо б у вас було достатньо цього, ви б працювали на факторіальній таблиці (як реалізовано). беручи 4 символи, і мінімізуючи марнотратні розрахунки. Все, що минуло третю ітерацію, ви обробляєте двозначні математичні плюси. Я вважаю, що якщо б у вас було достатньо цього, ви б працювали на факторіальній таблиці (як реалізовано). беручи 4 символи, і мінімізуючи марнотратні розрахунки. Все, що минуло третю ітерацію, ви обробляєте двозначну математику плюс. Я вважаю, що якщо б у вас було достатньо цього, ви б працювали на факторіальній таблиці (як реалізовано).

Що я запланував після цього? локальне сховище & | сеансу, щоб забезпечити кешування кожного випадку необхідних ітерацій, по суті, вирішуючи проблему "таблиці", про яку говорилося вище. Це також значно заощадить простір на базі даних та на сервері. Однак, якщо ви користуєтеся localStorage, ви, по суті, висмоктуєте місце на комп’ютері користувачів, щоб просто зберегти список номерів та зробити їх екран швидшим, але протягом тривалого періоду часу з величезною потребою це буде повільно. Я думаю, sessionStorage (очищення після виходу з вкладки) буде набагато кращим маршрутом. Можливо, поєднати це із самобалансуючим сервером / локальним кешем? Користувачеві А потрібні X ітерації. Користувачеві В потрібні ітерації 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);
});

Ця редакція реалізує ще одну пропозицію стека і дозволяє мені називати функцію факторіальною (true) (5), що було однією з моїх цілей. : 3 Я також видалив непотрібне присвоєння та скоротив деякі непублічні імена змінних.


Повертає undefinedза 0 !. ES6 дозволяє замінити isNumericна Number.isInteger. Рядки на зразок factorials[0][factorials[1]]=factorials[0][factorial_index]*(factorials[1]+1);абсолютно нечитабельними.
le_m

2

Ось одна, яка використовує новіші функції JavaScript, заповнення , зіставлення , зменшення та конструктор (та синтаксис жирної стрілки):

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.