3
module.exports = SortedSet;
5
var Shim = require("./shim");
6
var GenericCollection = require("./generic-collection");
7
var GenericSet = require("./generic-set");
8
var ObservableObject = require("./observable-object");
9
var ObservableRange = require("./observable-range");
10
var Iterator = require("./iterator");
11
var TreeLog = require("./tree-log");
13
function SortedSet(values, equals, compare, getDefault) {
14
if (!(this instanceof SortedSet)) {
15
return new SortedSet(values, equals, compare, getDefault);
17
this.contentEquals = equals || Object.equals;
18
this.contentCompare = compare || Object.compare;
19
this.getDefault = getDefault || Function.noop;
25
// hack so require("sorted-set").SortedSet will work in MontageJS
26
SortedSet.SortedSet = SortedSet;
28
Object.addEach(SortedSet.prototype, GenericCollection.prototype);
29
Object.addEach(SortedSet.prototype, GenericSet.prototype);
30
Object.addEach(SortedSet.prototype, ObservableObject.prototype);
31
Object.addEach(SortedSet.prototype, ObservableRange.prototype);
33
SortedSet.prototype.isSorted = true;
35
SortedSet.prototype.constructClone = function (values) {
36
return new this.constructor(
44
SortedSet.prototype.has = function (value) {
47
return this.contentEquals(value, this.root.value);
53
SortedSet.prototype.get = function (value) {
56
if (this.contentEquals(value, this.root.value)) {
57
return this.root.value;
60
return this.getDefault(value);
63
SortedSet.prototype.add = function (value) {
64
var node = new this.Node(value);
67
if (!this.contentEquals(value, this.root.value)) {
68
var comparison = this.contentCompare(value, this.root.value);
69
if (comparison === 0) {
70
throw new Error("SortedSet cannot contain incomparable but inequal values: " + value + " and " + this.root.value);
72
if (this.dispatchesRangeChanges) {
73
this.dispatchRangeWillChange([value], [], this.root.index);
83
node.right = this.root;
84
node.left = this.root.left;
85
this.root.left = null;
95
node.left = this.root;
96
node.right = this.root.right;
97
this.root.right = null;
103
if (this.dispatchesRangeChanges) {
104
this.dispatchRangeChange([value], [], this.root.index);
109
if (this.dispatchesRangeChanges) {
110
this.dispatchRangeWillChange([value], [], 0);
114
if (this.dispatchesRangeChanges) {
115
this.dispatchRangeChange([value], [], 0);
122
SortedSet.prototype['delete'] = function (value) {
125
if (this.contentEquals(value, this.root.value)) {
126
var index = this.root.index;
127
if (this.dispatchesRangeChanges) {
128
this.dispatchRangeWillChange([], [value], index);
130
if (!this.root.left) {
131
this.root = this.root.right;
133
// remove the right side of the tree,
134
var right = this.root.right;
135
this.root = this.root.left;
136
// the tree now only contains the left side of the tree, so all
137
// values are less than the value deleted.
138
// splay so that the root has an empty right child
140
// put the right side of the tree back
141
this.root.right = right;
147
if (this.dispatchesRangeChanges) {
148
this.dispatchRangeChange([], [value], index);
156
SortedSet.prototype.indexOf = function (value) {
159
if (this.contentEquals(value, this.root.value)) {
160
return this.root.index;
166
SortedSet.prototype.findValue = function (value) {
169
if (this.contentEquals(value, this.root.value)) {
175
SortedSet.prototype.findGreatest = function (at) {
177
at = at || this.root;
185
SortedSet.prototype.findLeast = function (at) {
187
at = at || this.root;
195
SortedSet.prototype.findGreatestLessThanOrEqual = function (value) {
198
// assert root.value <= value
203
SortedSet.prototype.findGreatestLessThan = function (value) {
206
// assert root.value <= value
207
return this.root.getPrevious();
211
SortedSet.prototype.findLeastGreaterThanOrEqual = function (value) {
214
// assert root.value <= value
215
var comparison = this.contentCompare(value, this.root.value);
216
if (comparison === 0) {
219
return this.root.getNext();
224
SortedSet.prototype.findLeastGreaterThan = function (value) {
227
// assert root.value <= value
228
var comparison = this.contentCompare(value, this.root.value);
229
return this.root.getNext();
233
SortedSet.prototype.pop = function () {
235
var found = this.findGreatest();
236
this["delete"](found.value);
241
SortedSet.prototype.shift = function () {
243
var found = this.findLeast();
244
this["delete"](found.value);
249
SortedSet.prototype.push = function () {
250
this.addEach(arguments);
253
SortedSet.prototype.unshift = function () {
254
this.addEach(arguments);
257
SortedSet.prototype.slice = function (start, end) {
260
end = end || this.length;
262
start += this.length;
269
this.splayIndex(start);
270
while (this.root.index < end) {
271
sliced.push(this.root.value);
272
if (!this.root.right) {
275
this.splay(this.root.getNext().value);
281
SortedSet.prototype.splice = function (at, length /*...plus*/) {
282
return this.swap(at, length, Array.prototype.slice.call(arguments, 2));
285
SortedSet.prototype.swap = function (start, length, plus) {
286
if (start === undefined && length === undefined) {
291
start += this.length;
293
if (length === undefined) {
301
this.splayIndex(start);
304
for (var i = 0; i < length; i++) {
305
swapped.push(this.root.value);
306
var next = this.root.getNext();
307
this["delete"](this.root.value);
311
this.splay(next.value);
321
// This is the simplified top-down splaying algorithm from: "Self-adjusting
322
// Binary Search Trees" by Sleator and Tarjan guarantees that the root.value <=
323
// value if root exists
324
// - as described in https://github.com/hij1nx/forest
325
SortedSet.prototype.splay = function (value) {
326
var stub, left, right, temp, root, history;
332
// Create a stub node. The use of the stub node is a bit
333
// counter-intuitive: The right child of the stub node will hold the L tree
334
// of the algorithm. The left child of the stub node will hold the R tree
335
// of the algorithm. Using a stub node, left and right will always be
336
// nodes and we avoid special cases.
337
// - http://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/splay-tree-inl.h
338
stub = left = right = new this.Node();
339
// The history is an upside down tree used to propagate new tree sizes back
340
// up the left and right arms of a traversal. The right children of the
341
// transitive left side of the tree will be former roots while linking
342
// left. The left children of the transitive walk to the right side of the
343
// history tree will all be previous roots from linking right. The last
344
// node of the left and right traversal will each become a child of the new
346
history = new this.Node();
350
var comparison = this.contentCompare(value, root.value);
351
if (comparison < 0) {
353
if (this.contentCompare(value, root.left.value) < 0) {
361
root.left = temp.right;
370
// remember former root for repropagating length
373
temp.left = history.left;
383
} else if (comparison > 0) {
385
if (this.contentCompare(value, root.right.value) > 0) {
393
root.right = temp.left;
402
// remember former root for repropagating length
405
temp.right = history.right;
406
history.right = temp;
415
} else { // equal or incomparable
421
left.right = root.left;
423
right.left = root.right;
425
root.left = stub.right;
426
root.right = stub.left;
428
// propagate new lengths
429
while (history.left) {
430
history.left.right.touch();
431
history.left = history.left.left;
433
while (history.right) {
434
history.right.left.touch();
435
history.right = history.right.right;
442
// an internal utility for splaying a node based on its index
443
SortedSet.prototype.splayIndex = function (index) {
446
var atIndex = this.root.index;
448
while (atIndex !== index) {
449
if (atIndex > index && at.left) {
451
atIndex -= 1 + (at.right ? at.right.length : 0);
452
} else if (atIndex < index && at.right) {
454
atIndex += 1 + (at.left ? at.left.length : 0);
460
this.splay(at.value);
462
return this.root.index === index;
467
SortedSet.prototype.reduce = function (callback, basis, thisp) {
469
basis = this.root.reduce(callback, basis, 0, thisp, this);
474
SortedSet.prototype.reduceRight = function (callback, basis, thisp) {
476
basis = this.root.reduceRight(callback, basis, this.length - 1, thisp, this);
481
SortedSet.prototype.min = function (at) {
482
var least = this.findLeast(at);
488
SortedSet.prototype.max = function (at) {
489
var greatest = this.findGreatest(at);
491
return greatest.value;
495
SortedSet.prototype.one = function () {
499
SortedSet.prototype.clear = function () {
501
if (this.dispatchesRangeChanges) {
502
minus = this.toArray();
503
this.dispatchRangeWillChange([], minus, 0);
507
if (this.dispatchesRangeChanges) {
508
this.dispatchRangeChange([], minus, 0);
512
SortedSet.prototype.iterate = function (start, end) {
513
return new this.Iterator(this, start, end);
516
SortedSet.prototype.Iterator = SortedSetIterator;
518
SortedSet.prototype.summary = function () {
520
return this.root.summary();
526
SortedSet.prototype.log = function (charmap, logNode, callback, thisp) {
527
charmap = charmap || TreeLog.unicodeRound;
528
logNode = logNode || this.logNode;
530
callback = console.log;
534
// Bind is unavailable in PhantomJS, the only environment of consequence
535
// that does not implement it yet.
536
var originalCallback = callback;
537
callback = function () {
538
return originalCallback.apply(thisp, arguments);
542
this.root.log(charmap, logNode, callback, callback);
546
SortedSet.prototype.logNode = function (node, log, logBefore) {
547
log(" " + node.value);
550
SortedSet.logCharsets = TreeLog;
552
SortedSet.prototype.Node = Node;
554
function Node(value) {
561
// TODO case where no basis is provided for reduction
563
Node.prototype.reduce = function (callback, basis, index, thisp, tree, depth) {
566
// prerecord length to be resistant to mutation
567
var length = this.left.length;
568
basis = this.left.reduce(callback, basis, index, thisp, tree, depth + 1);
571
basis = callback.call(thisp, basis, this.value, index, tree, this, depth);
574
basis = this.right.reduce(callback, basis, index, thisp, tree, depth + 1);
579
Node.prototype.reduceRight = function (callback, basis, index, thisp, tree, depth) {
582
basis = this.right.reduce(callback, basis, index, thisp, tree, depth + 1);
583
index -= this.right.length;
585
basis = callback.call(thisp, basis, this.value, this.value, tree, this, depth);
588
basis = this.left.reduce(callback, basis, index, thisp, tree, depth + 1);
593
Node.prototype.touch = function () {
595
(this.left ? this.left.length : 0) +
596
(this.right ? this.right.length : 0);
597
this.index = this.left ? this.left.length : 0;
600
Node.prototype.checkIntegrity = function () {
602
length += this.left ? this.left.checkIntegrity() : 0;
603
length += this.right ? this.right.checkIntegrity() : 0;
604
if (this.length !== length)
605
throw new Error("Integrity check failed: " + this.summary());
609
// get the next node in this subtree
610
Node.prototype.getNext = function () {
621
// get the previous node in this subtree
622
Node.prototype.getPrevious = function () {
633
Node.prototype.summary = function () {
634
var value = this.value || "-";
635
value += " <" + this.length;
636
if (!this.left && !this.right) {
637
return "(" + value + ")";
639
return "(" + value + " " + (
640
this.left ? this.left.summary() : "()"
642
this.right ? this.right.summary() : "()"
646
Node.prototype.log = function (charmap, logNode, log, logAbove) {
650
if (this.left && this.right) {
651
branch = charmap.intersection;
652
} else if (this.left) {
653
branch = charmap.branchUp;
654
} else if (this.right) {
655
branch = charmap.branchDown;
657
branch = charmap.through;
661
this.left && this.left.log(
664
function innerWrite(line) {
668
logAbove(charmap.fromBelow + charmap.through + line);
671
logAbove(charmap.strafe + " " + line);
674
function innerWriteAbove(line) {
676
logAbove(" " + line);
683
function innerWrite(line) {
688
log((self.right ? charmap.strafe : " ") + line);
691
function innerWriteAbove(line) {
692
logAbove((self.left ? charmap.strafe : " ") + line);
697
this.right && this.right.log(
700
function innerWrite(line) {
703
log(charmap.fromAbove + charmap.through + line);
708
function innerWriteAbove(line) {
709
log(charmap.strafe + " " + line);
714
function SortedSetIterator(set, start, end) {
719
var next = this.set.findLeastGreaterThanOrEqual(start);
721
this.set.splay(next.value);
722
this.prev = next.getPrevious();
728
SortedSetIterator.prototype = Object.create(SortedSetIterator.prototype);
729
SortedSetIterator.prototype.constructor = SortedSetIterator;
731
SortedSetIterator.prototype.next = function () {
734
next = this.set.findLeastGreaterThan(this.prev.value);
736
next = this.set.findLeast();
739
return Iterator.done;
742
this.end !== undefined &&
743
this.set.contentCompare(next.value, this.end) >= 0
745
return Iterator.done;
748
return new Iterator.Iteration(