skiplist: Move point creation to a new `Point` class

pull/5003/head
Richard Hansen 2021-04-10 15:43:22 -04:00
parent 8ae40e80f9
commit 3c1be95e07
1 changed files with 45 additions and 42 deletions

View File

@ -25,6 +25,49 @@
const _entryWidth = (e) => (e && e.width) || 0;
const _getNodeAtPoint = (point) => point.nodes[0].downPtrs[0];
// A "point" object at index x allows modifications immediately after the first x elements of the
// skiplist, such as multiple inserts or deletes. After an insert or delete using point P, the point
// is still valid and points to the same index in the skiplist. Other operations with other points
// invalidate this point.
class Point {
constructor(skipList, loc) {
this._skipList = skipList;
this.loc = loc;
const numLevels = this._skipList._start.levels;
let lvl = numLevels - 1;
let i = -1;
let ws = 0;
const nodes = new Array(numLevels);
const idxs = new Array(numLevels);
const widthSkips = new Array(numLevels);
nodes[lvl] = this._skipList._start;
idxs[lvl] = -1;
widthSkips[lvl] = 0;
while (lvl >= 0) {
let n = nodes[lvl];
while (n.downPtrs[lvl] && (i + n.downSkips[lvl] < this.loc)) {
i += n.downSkips[lvl];
ws += n.downSkipWidths[lvl];
n = n.downPtrs[lvl];
}
nodes[lvl] = n;
idxs[lvl] = i;
widthSkips[lvl] = ws;
lvl--;
if (lvl >= 0) {
nodes[lvl] = n;
}
}
this.idxs = idxs;
this.nodes = nodes;
this.widthSkips = widthSkips;
}
toString() {
return `Point(${this.loc})`;
}
}
/**
* The skip-list contains "entries", JavaScript objects that each must have a unique "key"
* property that is a string.
@ -55,46 +98,6 @@ class SkipList {
this._end.upPtrs[0] = this._start;
}
// a "point" object at location x allows modifications immediately after the first
// x elements of the skiplist, such as multiple inserts or deletes.
// After an insert or delete using point P, the point is still valid and points
// to the same index in the skiplist. Other operations with other points invalidate
// this point.
_getPoint(targetLoc) {
const numLevels = this._start.levels;
let lvl = numLevels - 1;
let i = -1;
let ws = 0;
const nodes = new Array(numLevels);
const idxs = new Array(numLevels);
const widthSkips = new Array(numLevels);
nodes[lvl] = this._start;
idxs[lvl] = -1;
widthSkips[lvl] = 0;
while (lvl >= 0) {
let n = nodes[lvl];
while (n.downPtrs[lvl] && (i + n.downSkips[lvl] < targetLoc)) {
i += n.downSkips[lvl];
ws += n.downSkipWidths[lvl];
n = n.downPtrs[lvl];
}
nodes[lvl] = n;
idxs[lvl] = i;
widthSkips[lvl] = ws;
lvl--;
if (lvl >= 0) {
nodes[lvl] = n;
}
}
return {
nodes,
idxs,
loc: targetLoc,
widthSkips,
toString: () => `getPoint(${targetLoc})`,
};
}
_getNodeAtOffset(targetOffset) {
let i = 0;
let n = this._start;
@ -258,7 +261,7 @@ class SkipList {
atIndex(i) {
if (i < 0) console.warn(`atIndex(${i})`);
if (i >= this._numNodes) console.warn(`atIndex(${i}>=${this._numNodes})`);
return _getNodeAtPoint(this._getPoint(i)).entry;
return _getNodeAtPoint(new Point(this, i)).entry;
}
// differs from Array.splice() in that new elements are in an array, not varargs
@ -271,7 +274,7 @@ class SkipList {
}
if (!newEntryArray) newEntryArray = [];
const pt = this._getPoint(start);
const pt = new Point(this, start);
for (let i = 0; i < deleteCount; i++) {
this._deleteKeyAtPoint(pt);
}