implemented RBTree as a in-memory database for operations (in progress)
This commit is contained in:
parent
02f2f6b0fe
commit
c184cb961b
@ -26,6 +26,7 @@
|
||||
"setTimeout": true,
|
||||
"setInterval": true,
|
||||
"Operation": true,
|
||||
"getRandom": true
|
||||
"getRandom": true,
|
||||
"RBTree": true
|
||||
}
|
||||
}
|
||||
|
28
gulpfile.js
28
gulpfile.js
@ -2,7 +2,11 @@
|
||||
|
||||
/** Gulp Commands
|
||||
|
||||
gulp command* [--export ModuleType] [--name ModuleName] [--testport TestPort]
|
||||
gulp command*
|
||||
[--export ModuleType]
|
||||
[--name ModuleName]
|
||||
[--testport TestPort]
|
||||
[--testfiles TestFiles]
|
||||
|
||||
Module name (ModuleName):
|
||||
Compile this to "y.js" (default)
|
||||
@ -20,6 +24,9 @@
|
||||
Test port (TestPort):
|
||||
Serve the specs on port 8888 (default)
|
||||
|
||||
Test files (TestFiles):
|
||||
Specify which specs to use!
|
||||
|
||||
Commands:
|
||||
- build:
|
||||
Build this library
|
||||
@ -52,22 +59,23 @@ var polyfills = [
|
||||
"./node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js"
|
||||
];
|
||||
|
||||
var files = {
|
||||
y: polyfills.concat(["src/y.js", "src/**/*.js", "!src/**/*.spec.js"]),
|
||||
lint: ["src/**/*.js", "gulpfile.js"],
|
||||
test: polyfills.concat(["src/y.js", "src/**/*.js"]),
|
||||
build_test: ["build_test/y.js"]
|
||||
};
|
||||
|
||||
var options = minimist(process.argv.slice(2), {
|
||||
string: ["export", "name", "testport"],
|
||||
string: ["export", "name", "testport", "testfiles"],
|
||||
default: {
|
||||
export: "ignore",
|
||||
name: "y.js",
|
||||
testport: "8888"
|
||||
testport: "8888",
|
||||
testfiles: "src/**/*.js"
|
||||
}
|
||||
});
|
||||
|
||||
var files = {
|
||||
y: polyfills.concat(["src/y.js", "src/**/*.js", "!src/**/*.spec.js"]),
|
||||
lint: ["src/**/*.js", "gulpfile.js"],
|
||||
test: polyfills.concat([options.testfiles]),
|
||||
build_test: ["build_test/y.js"]
|
||||
};
|
||||
|
||||
gulp.task("build", function () {
|
||||
return gulp.src(files.y)
|
||||
.pipe(sourcemaps.init())
|
||||
|
187
src/OperationStores/RedBlackTree.js
Normal file
187
src/OperationStores/RedBlackTree.js
Normal file
@ -0,0 +1,187 @@
|
||||
|
||||
|
||||
|
||||
class N {
|
||||
// A created node is always red!
|
||||
constructor (val) {
|
||||
this.val = val;
|
||||
this.color = true;
|
||||
this._left = null;
|
||||
this._right = null;
|
||||
this._parent = null;
|
||||
if (val.id == null) {
|
||||
throw new Error("You must define id!");
|
||||
}
|
||||
}
|
||||
isRed () { return this.color; }
|
||||
isBlack () { return !this.color; }
|
||||
redden () { this.color = true; return this; }
|
||||
blacken () { this.color = false; return this; }
|
||||
get grandparent () {
|
||||
return this.parent.parent;
|
||||
}
|
||||
get parent () {
|
||||
return this._parent;
|
||||
}
|
||||
get left () {
|
||||
return this._left;
|
||||
}
|
||||
get right () {
|
||||
return this._right;
|
||||
}
|
||||
set left (n) {
|
||||
if (n != null) {
|
||||
n._parent = this;
|
||||
}
|
||||
this._left = n;
|
||||
}
|
||||
set right (n) {
|
||||
if (n != null) {
|
||||
n._parent = this;
|
||||
}
|
||||
this._right = n;
|
||||
}
|
||||
rotateLeft (tree) {
|
||||
var parent = this.parent;
|
||||
var newParent = this.right;
|
||||
var newRight = this.right.left;
|
||||
newParent.left = this;
|
||||
this.right = newRight;
|
||||
if (parent == null) {
|
||||
tree.root = newParent;
|
||||
} else if (parent.left === this) {
|
||||
parent.left = newParent;
|
||||
} else if (parent.right === this) {
|
||||
parent.right = newParent;
|
||||
} else {
|
||||
throw new Error("The elements are wrongly connected!");
|
||||
}
|
||||
}
|
||||
rotateRight (tree) {
|
||||
var parent = this.parent;
|
||||
var newParent = this.left;
|
||||
var newLeft = this.left.right;
|
||||
newParent.right = this;
|
||||
this.left = newLeft;
|
||||
if (parent == null) {
|
||||
tree.root = newParent;
|
||||
} else if (parent.left === this) {
|
||||
parent.left = newParent;
|
||||
} else if (parent.right === this) {
|
||||
parent.right = newParent;
|
||||
} else {
|
||||
throw new Error("The elements are wrongly connected!");
|
||||
}
|
||||
}
|
||||
getUncle () {
|
||||
// we can assume that grandparent exists when this is called!
|
||||
if (this.parent === this.parent.parent.left) {
|
||||
return this.parent.parent.right;
|
||||
} else {
|
||||
return this.parent.parent.left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RBTree { //eslint-disable-line no-unused-vars
|
||||
constructor () {
|
||||
this.root = null;
|
||||
}
|
||||
find (id) {
|
||||
var o = this.root;
|
||||
if (o == null) {
|
||||
return false;
|
||||
} else {
|
||||
while (true) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
if (id < o.val.id) {
|
||||
o = o.left;
|
||||
} else if (o.val.id < id) {
|
||||
o = o.right;
|
||||
} else {
|
||||
return o.val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
add (v) {
|
||||
var node = new N(v);
|
||||
if (this.root != null) {
|
||||
var p = this.root; // p abbrev. parent
|
||||
while (true) {
|
||||
if (node.val.id < p.val.id) {
|
||||
if (p.left == null) {
|
||||
p.left = node;
|
||||
break;
|
||||
} else {
|
||||
p = p.left;
|
||||
}
|
||||
} else if (p.val.id < node.val.id) {
|
||||
if (p.right == null) {
|
||||
p.right = node;
|
||||
break;
|
||||
} else {
|
||||
p = p.right;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.fixInsert(node);
|
||||
} else {
|
||||
this.root = node;
|
||||
}
|
||||
this.root.blacken();
|
||||
}
|
||||
fixInsert (n) {
|
||||
if (n.parent == null) {
|
||||
n.blacken();
|
||||
return;
|
||||
} else if (n.parent.isBlack()) {
|
||||
return;
|
||||
}
|
||||
var uncle = n.getUncle();
|
||||
if (uncle != null && uncle.isRed()) {
|
||||
// Note: parend: red, uncle: red
|
||||
n.parent.blacken();
|
||||
uncle.blacken();
|
||||
n.grandparent.redden();
|
||||
this.fixInsert(n.grandparent);
|
||||
} else {
|
||||
// Note: parent: red, uncle: black or null
|
||||
// Now we transform the tree in such a way that
|
||||
// either of these holds:
|
||||
// 1) grandparent.left.isRed
|
||||
// and grandparent.left.left.isRed
|
||||
// 2) grandparent.right.isRed
|
||||
// and grandparent.right.right.isRed
|
||||
if (n === n.parent.right
|
||||
&& n.parent === n.grandparent.left) {
|
||||
n.parent.rotateLeft(this);
|
||||
// Since we rotated and want to use the previous
|
||||
// cases, we need to set n in such a way that
|
||||
// n.parent.isRed again
|
||||
n = n.left;
|
||||
} else if (n === n.parent.left
|
||||
&& n.parent === n.grandparent.right) {
|
||||
n.parent.rotateRight(this);
|
||||
// see above
|
||||
n = n.right;
|
||||
}
|
||||
// Case 1) or 2) hold from here on.
|
||||
// Now traverse grandparent, make parent a black node
|
||||
// on the highest level which holds two red nodes.
|
||||
n.parent.blacken();
|
||||
n.grandparent.redden();
|
||||
if (n === n.parent.left) {
|
||||
// Case 1
|
||||
n.grandparent.rotateRight(this);
|
||||
} else {
|
||||
// Case 2
|
||||
n.grandparent.rotateLeft(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
src/OperationStores/RedBlackTree.spec.js
Normal file
74
src/OperationStores/RedBlackTree.spec.js
Normal file
@ -0,0 +1,74 @@
|
||||
/* @flow */
|
||||
/*eslint-env browser,jasmine */
|
||||
|
||||
var numberOfTests = 1000;
|
||||
|
||||
describe("RedBlack Tree", function(){
|
||||
beforeEach(function(){
|
||||
this.tree = new RBTree();
|
||||
});
|
||||
it("can add&retrieve 5 elements", function(){
|
||||
this.tree.add({val: "four", id: 4});
|
||||
this.tree.add({val: "one", id: 1});
|
||||
this.tree.add({val: "three", id: 3});
|
||||
this.tree.add({val: "two", id: 2});
|
||||
this.tree.add({val: "five", id: 5});
|
||||
expect(this.tree.find(1).val).toEqual("one");
|
||||
expect(this.tree.find(2).val).toEqual("two");
|
||||
expect(this.tree.find(3).val).toEqual("three");
|
||||
expect(this.tree.find(4).val).toEqual("four");
|
||||
expect(this.tree.find(5).val).toEqual("five");
|
||||
});
|
||||
|
||||
describe(`After adding ${numberOfTests} random objects`, function () {
|
||||
var elements = [];
|
||||
var tree = new RBTree();
|
||||
for(var i = 0; i < numberOfTests; i++) {
|
||||
var obj = (Math.random() + 1).toString(36).substring(15);
|
||||
elements.push(obj);
|
||||
tree.add({id: obj});
|
||||
}
|
||||
it("root node is black", function(){
|
||||
expect(tree.root.isBlack()).toBeTruthy();
|
||||
});
|
||||
it("can find every object", function(){
|
||||
for(var id of elements) {
|
||||
expect(tree.find(id).id).toEqual(id);
|
||||
}
|
||||
});
|
||||
it("Red nodes do not have black children", function(){
|
||||
function traverse (n) {
|
||||
if (n == null) {
|
||||
return;
|
||||
}
|
||||
if (n.isRed()) {
|
||||
if (n.left != null) {
|
||||
expect(n.left.isRed()).not.toBeTruthy();
|
||||
}
|
||||
if (n.right != null) {
|
||||
expect(n.right.isRed()).not.toBeTruthy();
|
||||
}
|
||||
}
|
||||
traverse(n.left);
|
||||
traverse(n.right);
|
||||
}
|
||||
traverse(tree.root);
|
||||
});
|
||||
it("Black-height of sub-trees are equal", function(){
|
||||
function traverse (n) {
|
||||
if (n == null) {
|
||||
return 0;
|
||||
}
|
||||
var sub1 = traverse(n.left);
|
||||
var sub2 = traverse(n.right);
|
||||
expect(sub1).toEqual(sub2);
|
||||
if(n.isRed()) {
|
||||
return sub1;
|
||||
} else {
|
||||
return sub1 + 1;
|
||||
}
|
||||
}
|
||||
traverse(tree.root);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user