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,
|
"setTimeout": true,
|
||||||
"setInterval": true,
|
"setInterval": true,
|
||||||
"Operation": true,
|
"Operation": true,
|
||||||
"getRandom": true
|
"getRandom": true,
|
||||||
|
"RBTree": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
gulpfile.js
28
gulpfile.js
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
/** Gulp Commands
|
/** Gulp Commands
|
||||||
|
|
||||||
gulp command* [--export ModuleType] [--name ModuleName] [--testport TestPort]
|
gulp command*
|
||||||
|
[--export ModuleType]
|
||||||
|
[--name ModuleName]
|
||||||
|
[--testport TestPort]
|
||||||
|
[--testfiles TestFiles]
|
||||||
|
|
||||||
Module name (ModuleName):
|
Module name (ModuleName):
|
||||||
Compile this to "y.js" (default)
|
Compile this to "y.js" (default)
|
||||||
@ -20,6 +24,9 @@
|
|||||||
Test port (TestPort):
|
Test port (TestPort):
|
||||||
Serve the specs on port 8888 (default)
|
Serve the specs on port 8888 (default)
|
||||||
|
|
||||||
|
Test files (TestFiles):
|
||||||
|
Specify which specs to use!
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
- build:
|
- build:
|
||||||
Build this library
|
Build this library
|
||||||
@ -52,22 +59,23 @@ var polyfills = [
|
|||||||
"./node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js"
|
"./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), {
|
var options = minimist(process.argv.slice(2), {
|
||||||
string: ["export", "name", "testport"],
|
string: ["export", "name", "testport", "testfiles"],
|
||||||
default: {
|
default: {
|
||||||
export: "ignore",
|
export: "ignore",
|
||||||
name: "y.js",
|
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 () {
|
gulp.task("build", function () {
|
||||||
return gulp.src(files.y)
|
return gulp.src(files.y)
|
||||||
.pipe(sourcemaps.init())
|
.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