Javascript ES6 Rechen- / Zeitkomplexität von Sammlungen

87

Welche zeitliche Komplexität (in Big-O-Notation) bietet die ES6-Spezifikation für die Schlüsselsammlungen (Set, Map, WeakSet und WeakMap)?

Meine Erwartung, und ich erwarte , dass die meisten Entwickler, ist , dass die Spezifikationen und Implementierungen verwenden würden weitgehend akzeptiert performant Algorithmen, wobei in diesem Fall Set.prototype.has, addund deletefür alle O (1) im durchschnittlichen Fall sein. Gleiches gilt für die Mapund Weak–Äquivalente.

Es ist mir nicht ganz klar, ob die zeitliche Komplexität der Implementierungen vorgeschrieben war, z. B. in der ECMAScript 2015-Sprachspezifikation - 6. Ausgabe - 23.2 Objekte festlegen .

Sofern ich es nicht falsch verstehe (und es ist sicherlich sehr wahrscheinlich, dass ich es tue), sieht es so aus, als ob die ECMA-Spezifikation vorschreibt, dass die Implementierungen (z. B. Set.prototype.has) einen linearen Zeitalgorithmus ( O (n) ) verwenden sollen. Es würde mich außerordentlich überraschen, dass leistungsfähigere Algorithmen von der Spezifikation nicht vorgeschrieben oder sogar zugelassen würden, und ich wäre sehr an einer Erklärung interessiert, warum dies der Fall ist.

Brian M. Hunt
quelle
2
Alle O (1) -Algorithmen sind ebenfalls O (n) , sodass das Einlassen von weniger leistungsfähigen Implementierungen keinen Schaden anrichtet. Wahrscheinlich sind die weniger leistungsfähigen Implementierungen in Systemen mit eingeschränkten Ressourcen von Interesse, da sie höchstwahrscheinlich viel weniger Code / Speicherplatz benötigen.
Artur Grzesiak
@arturgrzesiak Die O (1) -Leistung der verschlüsselten Sammlungen wird im Allgemeinen mit einem O (1) -Hash plus einem O (n) -Kollisions-Bucket erreicht. Der O (n) Worst-Case ist für die meisten praktischen Zwecke astronomisch selten. Die räumliche Komplexität dieser Techniken beträgt im Allgemeinen O (n).
Brian M. Hunt
1
"Festgelegte Objekte müssen entweder mit Hash-Tabellen oder anderen Mechanismen implementiert werden, die im Durchschnitt Zugriffszeiten bieten, die sublinear zur Anzahl der Elemente in der Sammlung sind." - von genau dieser Seite.
Georg

Antworten:

61

Ab diesem Absatz sind Sie verlinkt mit:

Festgelegte Objekte müssen mithilfe von [Mechanismen] implementiert werden, die im Durchschnitt Zugriffszeiten bereitstellen, die sublinear zur Anzahl der Elemente in der Sammlung sind.

Sie finden den gleichen Satz für Maps , WeakMaps und WeakSets .

Es sieht so aus, als ob die ECMA-Spezifikation vorschreibt, dass die Implementierungen (z. B. Set.prototype.has) einen linearen time ( O(n)) -Algorithmus verwenden sollen.

Nein:

Die in dieser SetObjektspezifikation verwendeten Datenstrukturen sollen nur die erforderliche beobachtbare Semantik von Set-Objekten beschreiben. Es ist nicht beabsichtigt, ein tragfähiges Implementierungsmodell zu sein.

Die beobachtbare Semantik hängt hauptsächlich mit der vorhersagbaren Iterationsreihenfolge zusammen (die immer noch effizient und schnell implementiert werden kann ). Die Spezifikation erwartet in der Tat, dass eine Implementierung eine Hash-Tabelle oder ähnliches mit konstantem Zugriff verwendet, obwohl auch Bäume (mit logarithmischer Zugriffskomplexität) zulässig sind.

Bergi
quelle
2
Vielen Dank, dass Sie sich das ausgesucht haben. Meine Augen müssen glasig gewesen sein, als ich zu diesem Absatz kam. :) Also Algorithmen, die entweder O (log (n)) oder O (1) sind, aber nicht anderweitig vorgeschrieben sind (vorausgesetzt, sie stehen unter O (n))?
Brian M. Hunt
1
@ BrianM.Hunt: Richtig.
Bergi
32

Für alle, die neugierig sind, habe ich einen sehr schnellen Benchmark durchgeführt:

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);

Ich habe dies einige Male ausgeführt und die folgenden Ergebnisse erzielt:

(2017 MacBook Pro, 2,5 GHz i7 mit 16 G 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
Domdambrogia
quelle
3
@domdambrogia, wenn Sie die Einstellung vom Erhalten trennen, bekomme ich: Map Set = 124, Map Get = 40, Object Set = 26, Object Get = 1 (dies sind Verhältnisse, nicht ms)
AJP
@AJP Ich habe nicht darüber nachgedacht, es auch mit diesen Statistiken aufzuschlüsseln. Vielen Dank für Ihre Eingabe, das ist ein guter Beitrag. Ich werde sehen, ob ich das in meine Antwort aufnehmen kann, wenn ich eine Sekunde Zeit habe. Vielen Dank!
Domdambrogia
Es wäre interessant, die Aufgabe von der Lesung zu trennen, um auch zu erfahren, welche von beiden schneller zum Lesen ist.
fernandopasik
3
" 2017 MacBook Pro, 2,5 GHz i7 mit 16 G RAM " - ähm , das ist cool und alles, aber welche Javascript-Engine haben Sie bewertet?
Bergi
1
Interessanterweise ist die Leistung beim Hinzufügen von deleteOperationen und beim Vermischen von Operationen Mapviel besser. jsfiddle.net/23hrp0eq
Jorjon