implemented RBTree as a in-memory database for operations (in progress)

This commit is contained in:
Kevin Jahns 2015-07-07 18:11:27 +02:00
parent 02f2f6b0fe
commit c184cb961b
6 changed files with 284 additions and 14 deletions

View File

@ -26,6 +26,7 @@
"setTimeout": true,
"setInterval": true,
"Operation": true,
"getRandom": true
"getRandom": true,
"RBTree": true
}
}

View File

@ -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())

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

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

4
y.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long