计算处理组合的时间javascript

时间:2019-03-18 13:51:12

标签: javascript

我具有以下功能:https://rosettacode.org/wiki/Combinations#ES6

在我的环境console.log(show(comb(3,15)));(与下面的代码段相同)中,使用aprox。需要4秒的时间

如果我尝试使用console.log(show(comb(3,16)));,则使用aprox。 16秒

如果我尝试使用console.log(show(comb(3,17)));,则使用aprox。 90秒

但是,如果我尝试过:console.log(show(comb(3,20)));,经过一个小时的处理仍未完成,我已将其停止。

问题是:

如何预先计算处理comb(3,50)comb(3,80)的时间?

(() => {
    'use strict';

 
    // COMBINATIONS -----------------------------------------------------------
 
    // comb :: Int -> Int -> [[Int]]
    const comb = (m, n) => combinations(m, enumFromTo(0, n - 1));
 
    // combinations :: Int -> [a] -> [[a]]
    const combinations = (k, xs) =>
        sort(filter(xs => k === xs.length, subsequences(xs)));
 
 
    // GENERIC FUNCTIONS -----------------------------------------------------
 
    // cons :: a -> [a] -> [a]
    const cons = (x, xs) => [x].concat(xs);
 
    // enumFromTo :: Int -> Int -> [Int]
    const enumFromTo = (m, n) =>
        Array.from({
            length: Math.floor(n - m) + 1
        }, (_, i) => m + i);
 
    // filter :: (a -> Bool) -> [a] -> [a]
    const filter = (f, xs) => xs.filter(f);
 
    // foldr (a -> b -> b) -> b -> [a] -> b
    const foldr = (f, a, xs) => xs.reduceRight(f, a);
 
    // isNull :: [a] -> Bool
    const isNull = xs => (xs instanceof Array) ? xs.length < 1 : undefined;
 
    // show :: a -> String
    const show = x => JSON.stringify(x) //, null, 2);
 
    // sort :: Ord a => [a] -> [a]
    const sort = xs => xs.sort();
 
    // stringChars :: String -> [Char]
    const stringChars = s => s.split('');
 
    // subsequences :: [a] -> [[a]]
    const subsequences = xs => {
 
        // nonEmptySubsequences :: [a] -> [[a]]
        const nonEmptySubsequences = xxs => {
            if (isNull(xxs)) return [];
            const [x, xs] = uncons(xxs);
            const f = (r, ys) => cons(ys, cons(cons(x, ys), r));
 
            return cons([x], foldr(f, [], nonEmptySubsequences(xs)));
        };
 
        return nonEmptySubsequences(
            (typeof xs === 'string' ? stringChars(xs) : xs)
        );
    };
 
    // uncons :: [a] -> Maybe (a, [a])
    const uncons = xs => xs.length ? [xs[0], xs.slice(1)] : undefined;
 
 
    // TEST -------------------------------------------------------------------
    // return show(
        // comb(3, 5)
    // );
    
    console.log(show(comb(3,15)));
})();

2 个答案:

答案 0 :(得分:10)

使用binomial coefficients。处理comb(3,n)的时间为n choose 3,这对n*(n-1)*(n-2)/6起作用,因此为O(n^3)。例如,将n增加10倍,运行时间将增加大约1000倍。

20 choose 3只有1140,所以如果要花一个多小时来生成它们,那么所讨论的算法并不是特别好。此外,20 choose 317 choose 3之间的差距并不太大,以至于不能真正解释时差。因此,渐进分析仅暗示正在发生的事情。实际的运行时间似乎要差得多。

答案 1 :(得分:2)

正如约翰·科尔曼(John Coleman)所说,二项式系数可以使您相对地了解各种运行需要多长时间。

在不分析代码的情况下,您给出的数字清楚地表明那里出了问题。

一个更简单的版本可能看起来像这样:

// combinations :: Int -> [a] -> [[a]]
const combinations = (m) => (ns) => (ns.length == 0 || m == 0)
  ? []
  : m == 1
    ? ns
    : combinations (m - 1) (ns .slice(1)) .map(xs => [ns[0]] .concat(xs))
        .concat (combinations (m) (ns.slice(1) ) )

// combinations (3) (['a', 'b', 'c', 'd', 'e'])
//   .map(ls => ls .join('') )
     //=> ["abc", "abd", "abe", "acd", "ace", "ade", "bcd", "bce", "bde", "cde"]

// range :: Int -> Int -> [Int]
const range = (lo) => (hi) => [...Array(hi - lo + 1)].map((_, i) => i + lo)

// comb :: Int -> Int -> [[Int]]
const comb = (m, n) => combinations (m) (range (0) (n - 1))

console.clear()
const now = new Date();
console.log(comb(3, 20).length);
console.log(`time: ${new Date() - now} ms`)
// ~> 1140
// ~> time 2 ms

combinationscombs与您的行为相同。我不进行任何排序,将结果组合与原始列表中的顺序保持一致。

递归的基本情况很简单。列表为空时,返回[],如果m为0,则返回列表。递归的情况只是简单地递归并组合了两种情况:那些组合包括列表的第一个元素,而没有组合。第二个很简单,只需返回combinations (m) (tail(ns))即可。第一个也递归,调用combinations (m - 1) (tail(ns)),但必须在每个前缀head(ns)之前进行。我实际上没有在这里使用headtail函数,但是我可能会在生产代码中使用。

请注意,comb(3, 20)仅花费一两毫秒。

当然,这种繁重的递归在JS中可能会有代价,而且我认为没有任何直接方法可以使它有资格消除尾声。但是对于实际要计算的组合而言,递归深度可能不会成为问题。

当然,如果您只想计算组合而不是枚举它们,那么生成二项式系数的代码就应该简单得多。