Javascript ES6计算/时间复杂的集合

Bri*_*unt 65 javascript time-complexity ecmascript-6

密钥集合(Set,Map,WeakSet和WeakMap)的ES6规范提供了什么时间复杂度(以大O表示法)?

我的期望,并且我希望大多数开发人员都希望,规范和实现将使用广泛接受的性能算法,在这种情况下Set.prototype.has,add并且delete在一般情况下都是O(1).相同的MapWeak–等价物.

对于我来说,实现的时间复杂性是否是强制性的,例如在ECMAScript 2015语言规范 - 第6版 - 23.2设置对象中,这一点并不完全清楚.

除非我误解它(我当然很可能),看起来ECMA规范要求实现(例如Set.prototype.has)使用线性时间(O(n))算法.令我非常惊讶的是,规范中不会强制要求甚至不允许使用更高性能的算法,我会对为什么会出现这种情况的解释非常感兴趣.

Ber*_*rgi 46

那段链接到你的链接:

必须使用[机制]来实现集合对象,这些机制平均提供对集合中元素数量的次线性访问时间.

您将找到Maps,WeakMapsWeakSets的相同句子.

看起来ECMA规范要求实现(例如Set.prototype.has)使用线性时间(O(n))算法.

没有:

Set对象规范中使用的数据结构仅用于描述Set对象所需的可观察语义.它不是一个可行的实施模型.

可观察的语义主要与可预测的迭代顺序相关(它仍然可以高效且快速实现).规范确实期望实现使用散列表或类似于常量访问的东西,尽管也允许树(具有对数访问复杂性).

  • @BrianM.Hunt:正确。 (3认同)
  • 谢谢你挑出来。当我看到那一段时,我的眼睛一定已经呆滞了。:) 那么算法是 O(log(n)) 还是 O(1),但没有其他强制要求(前提是它们在 O(n) 下)? (2认同)
  • @canbax 请再次查看我的答案中的第一句话。它不允许线性平均时间复杂度。 (2认同)

Lio*_*owe 28

问题是 Set.has() 方法 O(1) 和 Array.indexOf O(n) 吗?被列为与此重复,但事实并非如此(我已投票决定重新开放)。Set#has无论如何,我都会在这里添加这些基准,因为对该问题的答复的基准未能显示和之间性能的全部差异Array#indexOf

以下内容均适用于 Chrome 93:

您发现,对于较小的数据集,Array#indexOf实际上优于Set#hasMap#has;然而,对于更大的数据集,Set#has速度Map#has要快多个数量级。这与您对 O(n) 与 O(1) 操作的期望非常一致。

有趣的是,尽管两者都是 O(n),但比小数据集Array#includes慢得多,但对于大数据集表现非常相似。Array#indexOf据推测,Array#indexOf利用了一些Array#includes没有的优化。

同时,在所有情况下Object#hasOwnProperty都略微优于(至少在 Chrome 93 中)。Set#hasMap#has

基准测试代码

const [small, medium, large] = [1e3, 1e5, 1e7]

const configs = [
    { size: small, iterations: large },
    { size: medium, iterations: medium },
    { size: large, iterations: small },
]

for (const { size, iterations } of configs) {
    const arr = Array.from({ length: size }, (_, i) => String(i))
    const obj = Object.fromEntries(arr.map(k => [k, true]))
    const set = new Set(arr)
    const map = new Map(Object.entries(obj))

    const valsToTest = Array.from(
        { length: iterations },
        (_, i) => String(Math.floor(Math.random() * size)),
    )

    const title = `dataset size: ${size.toLocaleString()}; iterations: ${iterations.toLocaleString()}`

    console.log(`\n-> ${title}`)

    for (const [target, method] of [
        [arr, 'indexOf'],
        [arr, 'includes'],
        [set, 'has'],
        [map, 'has'],
        [obj, 'hasOwnProperty'],
    ]) {
        const subtitle = `${target.constructor.name}#${method}`

        console.time(subtitle)

        for (const val of valsToTest) {
            target[method](val)
        }

        console.timeEnd(subtitle)
    }
}
Run Code Online (Sandbox Code Playgroud)

我的结果(Chrome 93)


-> dataset size: 1,000; iterations: 10,000,000
Array#indexOf: 185.100ms
Array#includes: 11302.700ms
Set#has: 367.400ms
Map#has: 375.000ms
Object#hasOwnProperty: 252.800ms

-> dataset size: 100,000; iterations: 100,000
Array#indexOf: 10794.100ms
Array#includes: 10476.800ms
Set#has: 6.600ms
Map#has: 6.800ms
Object#hasOwnProperty: 1.900ms

-> dataset size: 10,000,000; iterations: 1,000
Array#indexOf: 12798.900ms
Array#includes: 12435.400ms
Set#has: 0.800ms
Map#has: 0.800ms
Object#hasOwnProperty: 0.300ms
Run Code Online (Sandbox Code Playgroud)

  • @Bergi它要求比较`Set#has`和`Array#indexOf`,而这个问题没有。如果我谷歌“set.has vs array.indexof 时间复杂度”,这个问题就是第一个结果。我在这里的答案是对这个问题的基于基准(而不是基于规范)的答案,并进行了一些其他比较以进行良好的衡量。不管这个问题是否被认为是重复的,希望有人发现这个答案有用。 (2认同)

dom*_*gia 18

对于任何好奇的人,我做了一个非常快速的基准测试:

const benchmarkMap = size => {
  console.time('benchmarkMap');
  var map = new Map();
  for (var i = 0; i < size; i++) map.set(i, i);
  for (var i = 0; i < size; i++) var x = map.get(i);
  console.timeEnd('benchmarkMap');
}

const benchmarkObj = size => {
  console.time('benchmarkObj');
  var obj = {};
  for (var i = 0; i < size; i++) obj[i] = i;
  for (var i = 0; i < size; i++) var x = obj[i];
  console.timeEnd('benchmarkObj');
}

var size = 1000000;

benchmarkMap(size);
benchmarkObj(size);
Run Code Online (Sandbox Code Playgroud)

我运行了几次,结果如下:

(2017 MacBook Pro,2.5 GHz i7带16G RAM)

benchmarkMap: 189.120ms
benchmarkObj: 44.214ms

benchmarkMap: 200.817ms
benchmarkObj: 38.963ms

benchmarkMap: 187.968ms
benchmarkObj: 41.633ms

benchmarkMap: 186.533ms
benchmarkObj: 35.850ms

benchmarkMap: 187.339ms
benchmarkObj: 44.515ms
Run Code Online (Sandbox Code Playgroud)

  • “*2017 MacBook Pro,2.5 GHz i7 w/ 16G RAM*”-呃,这很酷,但是**您对哪个 JavaScript 引擎进行了基准测试? (15认同)
  • @domdambrogia如果你将设置与获取分开,我得到:Map Set = 124,Map Get = 40,Object Set = 26,Object Get = 1(这些是比率而不是毫秒) (3认同)
  • 有趣的是,当添加“删除”操作和混合操作时,“Map”的性能要好得多。https://jsfiddle.net/23hrp0eq/ (2认同)