add a bst for mempool.
This commit is contained in:
parent
ec5175ec9e
commit
eda31da3ea
754
lib/bcoin/bst.js
Normal file
754
lib/bcoin/bst.js
Normal file
@ -0,0 +1,754 @@
|
||||
/**
|
||||
* bst.js - iterative binary search tree for bcoin
|
||||
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
||||
* https://github.com/indutny/bcoin
|
||||
*/
|
||||
|
||||
var bcoin = require('../bcoin');
|
||||
var utils = require('./utils');
|
||||
var assert = utils.assert;
|
||||
var DUMMY = new Buffer([0]);
|
||||
|
||||
/**
|
||||
* BST
|
||||
*/
|
||||
|
||||
function BST(location, options) {
|
||||
if (!(this instanceof BST))
|
||||
return new BST(location, options);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.options = options;
|
||||
this.root = null;
|
||||
this.compare = options.compare || utils.cmp;
|
||||
}
|
||||
|
||||
BST.prototype.search = function search(key, options) {
|
||||
var current = this.root;
|
||||
var cmp;
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'ascii');
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
while (current) {
|
||||
cmp = this.compare(key, current.key);
|
||||
if (cmp === 0) {
|
||||
return options.asBuffer === false
|
||||
? current.value.toString('utf8')
|
||||
: current.value;
|
||||
}
|
||||
if (cmp < 0)
|
||||
current = current.left;
|
||||
else
|
||||
current = current.right;
|
||||
}
|
||||
};
|
||||
|
||||
BST.prototype.insert = function insert(key, value, options) {
|
||||
var current = this.root;
|
||||
var left = false;
|
||||
var parent, cmp;
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'ascii');
|
||||
|
||||
if (typeof value === 'string')
|
||||
value = new Buffer(value, 'utf8');
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
while (current) {
|
||||
cmp = this.compare(key, current.key);
|
||||
|
||||
if (cmp === 0) {
|
||||
current.value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmp < 0) {
|
||||
parent = current;
|
||||
left = true;
|
||||
current = current.left;
|
||||
} else {
|
||||
parent = current;
|
||||
left = false;
|
||||
current = current.right;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parent) {
|
||||
this.root = { key: key, value: value };
|
||||
return;
|
||||
}
|
||||
|
||||
if (left)
|
||||
parent.left = { key: key, value: value };
|
||||
else
|
||||
parent.right = { key: key, value: value };
|
||||
};
|
||||
|
||||
BST.prototype.remove = function remove(key, options) {
|
||||
var current = this.root;
|
||||
var left = false;
|
||||
var parent, use;
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'ascii');
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
while (current) {
|
||||
cmp = this.compare(key, current.key);
|
||||
|
||||
if (cmp === 0)
|
||||
break;
|
||||
|
||||
if (cmp < 0) {
|
||||
parent = current;
|
||||
left = true;
|
||||
current = current.left;
|
||||
} else {
|
||||
parent = current;
|
||||
left = false;
|
||||
current = current.right;
|
||||
}
|
||||
}
|
||||
|
||||
if (!current)
|
||||
return false;
|
||||
|
||||
if (!current.left && !current.right) {
|
||||
if (!parent) {
|
||||
this.root = null;
|
||||
} else {
|
||||
if (left)
|
||||
parent.left = null;
|
||||
else
|
||||
parent.right = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!current.left || !current.right) {
|
||||
if (current.left)
|
||||
child = current.left;
|
||||
else
|
||||
child = current.right;
|
||||
|
||||
if (!parent) {
|
||||
this.root = child;
|
||||
} else {
|
||||
if (left)
|
||||
parent.left = child;
|
||||
else
|
||||
parent.right = child;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
parent = current;
|
||||
use = current.left;
|
||||
left = true;
|
||||
while (use.right) {
|
||||
parent = use;
|
||||
use = use.right;
|
||||
left = false;
|
||||
}
|
||||
|
||||
current.key = use.key;
|
||||
current.value = use.value;
|
||||
|
||||
if (left)
|
||||
current.left = use.left;
|
||||
else
|
||||
parent.right = use.left;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
BST.prototype.snapshot = function snapshot() {
|
||||
var current = this.root;
|
||||
var stack = [];
|
||||
var left = true;
|
||||
var parent, copy, snapshot;
|
||||
|
||||
for (;;) {
|
||||
if (current) {
|
||||
if (left) {
|
||||
copy = clone(current);
|
||||
if (parent)
|
||||
parent.left = copy;
|
||||
else
|
||||
snapshot = copy;
|
||||
} else {
|
||||
copy = clone(current);
|
||||
if (parent)
|
||||
parent.right = copy;
|
||||
else
|
||||
snapshot = copy;
|
||||
}
|
||||
stack.push(copy);
|
||||
parent = copy;
|
||||
left = true;
|
||||
current = current.left;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stack.length === 0)
|
||||
break;
|
||||
|
||||
current = stack.pop();
|
||||
parent = current;
|
||||
left = false;
|
||||
current = current.right;
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
};
|
||||
|
||||
BST.prototype.traverse = function traverse(options, test) {
|
||||
var current = this.root;
|
||||
var stack = [];
|
||||
var items = [];
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
for (;;) {
|
||||
if (current) {
|
||||
if (test(current)) {
|
||||
items.push({
|
||||
key: options.keyAsBuffer === false
|
||||
? current.key.toString('ascii')
|
||||
: current.key,
|
||||
value: options.valueAsBuffer === false
|
||||
? current.value.toString('utf8')
|
||||
: current.value
|
||||
});
|
||||
}
|
||||
stack.push(current);
|
||||
current = current.left;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stack.length === 0)
|
||||
break;
|
||||
|
||||
current = stack.pop();
|
||||
current = current.right;
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
BST.prototype.dump = function dump(options) {
|
||||
return this.traverse(options, function() { return true; });
|
||||
};
|
||||
|
||||
BST.prototype.range = function range(gte, lte, options) {
|
||||
var current = this.root;
|
||||
var stack = [];
|
||||
var items = [];
|
||||
var cmp;
|
||||
|
||||
if (typeof gte === 'string')
|
||||
gte = new Buffer(gte, 'ascii');
|
||||
|
||||
if (typeof lte === 'string')
|
||||
lte = new Buffer(lte, 'ascii');
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
for (;;) {
|
||||
if (current) {
|
||||
cmp = this.rangeCompare(current.key, gte, lte);
|
||||
if (cmp === 0) {
|
||||
items.push({
|
||||
key: options.keyAsBuffer === false
|
||||
? current.key.toString('ascii')
|
||||
: current.key,
|
||||
value: options.valueAsBuffer === false
|
||||
? current.value.toString('utf8')
|
||||
: current.value
|
||||
});
|
||||
stack.push(current);
|
||||
}
|
||||
if (cmp <= 0)
|
||||
current = current.left;
|
||||
else
|
||||
current = current.right;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stack.length === 0)
|
||||
break;
|
||||
|
||||
current = stack.pop();
|
||||
current = current.right;
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
BST.prototype.rangeCompare = function rangeCompare(key, gteKey, lteKey) {
|
||||
var gte, lte;
|
||||
|
||||
if (gteKey)
|
||||
gte = this.compare(key, gteKey);
|
||||
else
|
||||
gte = 0;
|
||||
|
||||
if (lteKey)
|
||||
lte = this.compare(key, lteKey);
|
||||
else
|
||||
lte = 0;
|
||||
|
||||
if (gte >= 0 && lte <= 0)
|
||||
return 0;
|
||||
|
||||
if (lte > 0)
|
||||
return -1;
|
||||
|
||||
if (gte < 0)
|
||||
return 1;
|
||||
|
||||
assert(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leveldown Methods
|
||||
*/
|
||||
|
||||
BST.prototype.open = function open(options, callback) {
|
||||
this.options = options;
|
||||
return utils.nextTick(callback);
|
||||
};
|
||||
|
||||
BST.prototype.close = function close(callback) {
|
||||
return utils.nextTick(callback);
|
||||
};
|
||||
|
||||
BST.prototype.get = function get(key, options, callback) {
|
||||
var item, err;
|
||||
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
item = this.search(key, options);
|
||||
|
||||
if (!item) {
|
||||
err = new Error('BST_NOTFOUND: Key not found.');
|
||||
err.notFound = true;
|
||||
err.type = 'NotFoundError';
|
||||
return utils.asyncify(callback)(err);
|
||||
}
|
||||
|
||||
return utils.asyncify(callback)(null, item);
|
||||
};
|
||||
|
||||
BST.prototype.put = function put(key, value, options, callback) {
|
||||
var item;
|
||||
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.insert(key, value, options);
|
||||
|
||||
return utils.nextTick(callback);
|
||||
};
|
||||
|
||||
BST.prototype.del = function del(key, options, callback) {
|
||||
var item;
|
||||
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.remove(key, options);
|
||||
|
||||
return utils.nextTick(callback);
|
||||
};
|
||||
|
||||
BST.prototype.batch = function batch(ops, options, callback) {
|
||||
var batch;
|
||||
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
batch = new Batch(this, options);
|
||||
|
||||
if (ops) {
|
||||
batch.ops = ops;
|
||||
return batch.write(callback);
|
||||
}
|
||||
|
||||
return batch;
|
||||
};
|
||||
|
||||
BST.prototype.iterator = function iterator(options) {
|
||||
return new Iterator(this, options);
|
||||
};
|
||||
|
||||
BST.prototype.getProperty = function getProperty(name) {
|
||||
return null;
|
||||
};
|
||||
|
||||
BST.prototype.approximateSize = function approximateSize(start, end, callback) {
|
||||
var size = this.range(start, end).reduce(function(total, item) {
|
||||
return total + item.key.length + item.value.length;
|
||||
}, 0);
|
||||
return utils.asyncify(callback)(null, size);
|
||||
};
|
||||
|
||||
BST.destroy = function destroy(location, callback) {
|
||||
return utils.nextTick(callback);
|
||||
};
|
||||
|
||||
BST.repair = function repair(location, callback) {
|
||||
return utils.asyncify(callback)(new Error('Cannot repair.'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Batch
|
||||
*/
|
||||
|
||||
function Batch(tree, options) {
|
||||
this.options = options || {};
|
||||
this.ops = [];
|
||||
this.tree = tree;
|
||||
}
|
||||
|
||||
Batch.prototype.put = function(key, value) {
|
||||
assert(this.tree, 'Already written.');
|
||||
this.ops.push({ type: 'put', key: key, value: value });
|
||||
return this;
|
||||
};
|
||||
|
||||
Batch.prototype.del = function del(key) {
|
||||
assert(this.tree, 'Already written.');
|
||||
this.ops.push({ type: 'del', key: key });
|
||||
return this;
|
||||
};
|
||||
|
||||
Batch.prototype.write = function write(callback) {
|
||||
var self = this;
|
||||
|
||||
if (!this.tree)
|
||||
return utils.asyncify(callback)(new Error('Already written.'));
|
||||
|
||||
this.ops.forEach(function(op) {
|
||||
if (op.type === 'put')
|
||||
self.tree.insert(op.key, op.value);
|
||||
else if (op.type === 'del')
|
||||
self.tree.remove(op.key);
|
||||
else
|
||||
assert(false);
|
||||
});
|
||||
|
||||
this.ops.length = 0;
|
||||
delete this.ops;
|
||||
delete this.options;
|
||||
delete this.tree;
|
||||
|
||||
utils.nextTick(callback);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterator
|
||||
*/
|
||||
|
||||
function Iterator(tree, options) {
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
assert(!options.lt, 'LT is not implemented.');
|
||||
assert(!options.gt, 'GT is not implemented.');
|
||||
|
||||
this.options = {
|
||||
keys: options.keys,
|
||||
values: options.values,
|
||||
gte: options.gte || options.start,
|
||||
lte: options.lte || options.end,
|
||||
keyAsBuffer: options.keyAsBuffer,
|
||||
valueAsBuffer: options.valueAsBuffer,
|
||||
reverse: options.reverse,
|
||||
limit: options.limit
|
||||
};
|
||||
|
||||
this.tree = tree;
|
||||
this.ended = false;
|
||||
this.items = this.tree.range(this.options.gte, this.options.lte);
|
||||
this.index = this.options.reverse ? this.items.length - 1 : 0;
|
||||
this.total = 0;
|
||||
}
|
||||
|
||||
Iterator.prototype.next = function(callback) {
|
||||
var item, key, value;
|
||||
|
||||
if (this.ended)
|
||||
return utils.asyncify(callback)(new Error('Cannot call next after end.'));
|
||||
|
||||
if (this.options.reverse)
|
||||
item = this.items[this.index--];
|
||||
else
|
||||
item = this.items[this.index++];
|
||||
|
||||
if (this.options.limit != null) {
|
||||
if (this.total++ >= this.options.limit) {
|
||||
this._end();
|
||||
return utils.nextTick(callback);
|
||||
}
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
this._end();
|
||||
return utils.nextTick(callback);
|
||||
}
|
||||
|
||||
key = item.key;
|
||||
value = item.value;
|
||||
|
||||
if (this.options.keys === false)
|
||||
key = DUMMY;
|
||||
|
||||
if (this.options.values === false)
|
||||
value = DUMMY;
|
||||
|
||||
if (this.options.keyAsBuffer === false)
|
||||
key = key.toString('ascii');
|
||||
|
||||
if (this.options.valueAsBuffer === false)
|
||||
value = value.toString('utf8');
|
||||
|
||||
return utils.asyncify(callback)(null, key, value);
|
||||
};
|
||||
|
||||
Iterator.prototype.seek = function seek(key) {
|
||||
assert(!this.ended, 'Already ended.');
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'ascii');
|
||||
|
||||
this.index = binarySearch(this.items, key, true, function(a, b) {
|
||||
return self.compare(a.key, b);
|
||||
});
|
||||
};
|
||||
|
||||
Iterator.prototype._end = function end(callback) {
|
||||
if (!this.tree)
|
||||
return;
|
||||
|
||||
delete this.tree;
|
||||
this.items.length = 0;
|
||||
delete this.items;
|
||||
};
|
||||
|
||||
Iterator.prototype.end = function end(callback) {
|
||||
if (this.ended)
|
||||
return utils.asyncify(callback)(new Error('Already ended.'));
|
||||
|
||||
this.ended = true;
|
||||
this._end();
|
||||
|
||||
return utils.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function binarySearch(items, key, insert, compare) {
|
||||
var start = 0;
|
||||
var end = items.length - 1;
|
||||
var pos, cmp;
|
||||
|
||||
if (!compare)
|
||||
compare = utils.cmp;
|
||||
|
||||
while (start <= end) {
|
||||
pos = (start + end) >>> 1;
|
||||
cmp = compare(items[pos], key);
|
||||
|
||||
if (cmp === 0)
|
||||
return pos;
|
||||
|
||||
if (cmp < 0)
|
||||
start = pos + 1;
|
||||
else
|
||||
end = pos - 1;
|
||||
}
|
||||
|
||||
if (!insert)
|
||||
return -1;
|
||||
|
||||
return start - 1;
|
||||
}
|
||||
|
||||
function clone(node) {
|
||||
return {
|
||||
key: node.key,
|
||||
value: node.value,
|
||||
left: node.left,
|
||||
right: node.right
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
module.exports = BST;
|
||||
|
||||
if (module.parent)
|
||||
return;
|
||||
|
||||
var tree = new BST();
|
||||
|
||||
function bench(tree, name) {
|
||||
var start = Date.now();
|
||||
var istart = Date.now();
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
tree.insert('foo ' + i, 'bar ' + i);
|
||||
}
|
||||
console.log('%s insert: %d', name, Date.now() - istart);
|
||||
var sstart = Date.now();
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
tree.search('foo ' + i);
|
||||
}
|
||||
console.log('%s search: %d', name, Date.now() - sstart);
|
||||
var rstart = Date.now();
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
if (i > 50000)
|
||||
tree.remove('foo ' + i);
|
||||
}
|
||||
console.log('%s remove: %d', name, Date.now() - rstart);
|
||||
var itstart = Date.now();
|
||||
tree.range('foo 700', 'foo 80000', { keyAsBuffer: false, valueAsBuffer: false });
|
||||
console.log('%s iter: %d', name, Date.now() - itstart);
|
||||
console.log('%s total: %d', name, Date.now() - start);
|
||||
}
|
||||
|
||||
bench(tree, 'tree');
|
||||
|
||||
var tree = new BST();
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
tree.insert('foo ' + i, 'bar ' + i);
|
||||
}
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
assert(tree.search('foo ' + i).toString('utf8') === 'bar ' + i);
|
||||
if (i > 900)
|
||||
tree.remove('foo ' + i);
|
||||
}
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
if (i > 900)
|
||||
assert(tree.search('foo ' + i) == null);
|
||||
else
|
||||
assert(tree.search('foo ' + i).toString('utf8') === 'bar ' + i);
|
||||
}
|
||||
|
||||
var items = tree.range('foo 700', 'foo 800', { keyAsBuffer: false, valueAsBuffer: false });
|
||||
|
||||
//utils.print(items);
|
||||
|
||||
tree.open(function() {
|
||||
var batch = tree.batch();
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var key = 'foo ' + i;
|
||||
var val = new Buffer('bar ' + i, 'ascii');
|
||||
batch.put(key, val);
|
||||
}
|
||||
batch.write(function(err) {
|
||||
if (err)
|
||||
throw err;
|
||||
var batch = tree.batch();
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var key = 'foo ' + i;
|
||||
var val = new Buffer('bar ' + i, 'ascii');
|
||||
if (i > 950)
|
||||
batch.del(key);
|
||||
else
|
||||
batch.put(key, val);
|
||||
}
|
||||
batch.write(function(err) {
|
||||
if (err)
|
||||
throw err;
|
||||
utils.forRangeSerial(0, 1000, function(i, next) {
|
||||
var key = 'foo ' + i;
|
||||
var val = new Buffer('bar ' + i, 'ascii');
|
||||
tree.get(key, function(err, value) {
|
||||
if (i > 950) {
|
||||
assert(err);
|
||||
assert(err.type === 'NotFoundError');
|
||||
return next();
|
||||
}
|
||||
if (err)
|
||||
return next(err);
|
||||
assert(utils.isEqual(value, val));
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
if (err)
|
||||
throw err;
|
||||
var iter = tree.iterator({
|
||||
gte: 'foo 900',
|
||||
lte: 'foo 999',
|
||||
keyAsBuffer: false,
|
||||
fillCache: false
|
||||
});
|
||||
(function next() {
|
||||
iter.next(function(err, key, value) {
|
||||
if (err) {
|
||||
return iter.end(function(e) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
if (key === undefined) {
|
||||
return iter.end(function(e) {
|
||||
if (e)
|
||||
throw e;
|
||||
utils.print(tree.dump());
|
||||
tree.approximateSize(null, null, function(err, size) {
|
||||
utils.print(size);
|
||||
return console.log('done');
|
||||
});
|
||||
});
|
||||
}
|
||||
console.log(key + ' : ' + value);
|
||||
next();
|
||||
});
|
||||
})();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -54,6 +54,9 @@ function getLocation(name) {
|
||||
}
|
||||
|
||||
function getBackend(backend) {
|
||||
if (backend === 'bst')
|
||||
return require('./bst');
|
||||
|
||||
if (bcoin.isBrowser)
|
||||
return require('level-js');
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user