look-alike
Version:
A simple-yet-powerful KD-tree library for NodeJS, with support for lightning-fast k-Nearest Neighbour queries. Supports normalization, weights, key and filter parameters
200 lines (191 loc) • 6.23 kB
JavaScript
// Generated by CoffeeScript 1.6.3
(function() {
var KDtree, util;
util = require('./util');
KDtree = (function() {
KDtree.prototype.getRoot = function() {
return this._tree;
};
function KDtree(objects, options) {
var k, v, _base, _helper,
_this = this;
this.objects = objects;
this.options = options;
if (!(arguments.length > 0)) {
throw new Error('Need at least 1 argument');
}
if (Array.isArray(this.objects)) {
if (this.objects.some(function(x) {
return x && x.toString() !== '[object Object]';
})) {
throw new Error('Expecting an array of objects as first argument');
}
} else {
throw new Error('Expecting an array of objects as first argument');
}
this.options = this.options || {};
if ((_base = this.options).attributes == null) {
_base.attributes = (function() {
var _ref, _results;
_ref = this.objects[0];
_results = [];
for (k in _ref) {
v = _ref[k];
_results.push(k);
}
return _results;
}).call(this);
}
if (Array.isArray(this.options.attributes)) {
if (this.options.attributes.some(function(x) {
return typeof x !== 'string';
})) {
throw new Error('Expecting an array of strings for attributes');
}
} else {
throw new Error('Expecting an array of strings for attributes');
}
if (!this.objects.every(function(x) {
return _this.options.attributes.every(function(k) {
if (_this.options.key) {
return _this.options.key(x).hasOwnProperty(k);
} else {
return x.hasOwnProperty(k);
}
});
})) {
throw new Error("Expecting all objects to have at least the same keys as first object or second parameter");
}
this.stdv = util.allStdvs(this.options.attributes, this.objects, this.options.key);
_helper = function(objects, depth) {
var attr, bounds, len, node, o, splits, temp;
if (!objects.length) {
return null;
}
len = _this.options.attributes.length;
attr = _this.options.attributes[depth % len];
objects.sort(function(a, b) {
var _ref;
if ((_ref = this.options) != null ? _ref.key : void 0) {
return this.options.key(a)[attr] - this.options.key(b)[attr];
} else {
return a[attr] - b[attr];
}
});
temp = objects;
if (_this.options.key) {
temp = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = objects.length; _i < _len; _i++) {
o = objects[_i];
_results.push(this.options.key(o));
}
return _results;
}).call(_this);
}
bounds = util.medianIndex((function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = temp.length; _i < _len; _i++) {
o = temp[_i];
_results.push(o[attr]);
}
return _results;
})());
splits = util.getSplit(objects, bounds, _this.options.attributes, attr, _this.options.key);
return node = {
val: splits.identicals,
left: _helper(splits.left, depth + 1),
right: _helper(splits.right, depth + 1)
};
};
this._tree = _helper(this.objects, 0);
}
KDtree.prototype.query = function(subject, options) {
var BPQ, Q, root, _helper,
_this = this;
if (!this.options.attributes.every(function(k) {
return subject.hasOwnProperty(k);
})) {
throw new Error("Subject does not have all keys");
}
if (options) {
if (!options.k) {
options.k = 1;
}
if (options.normalize !== false) {
options.normalize = true;
}
} else {
options = {
k: 1,
normalize: true
};
}
BPQ = require('./bpq');
Q = new BPQ(options.k);
_helper = function(node, depth) {
var attr, attr_dist, dist, goLeft, len, o, objectValues, _i, _len, _ref, _ref1, _ref2;
if (!node) {
return null;
}
len = _this.options.attributes.length;
attr = _this.options.attributes[depth % len];
objectValues = ((_ref = _this.options) != null ? _ref.key : void 0) ? (_ref1 = _this.options) != null ? _ref1.key(node.val[0]) : void 0 : node.val[0];
if (options.normalize) {
dist = util.distance(subject, objectValues, {
stdv: _this.stdv,
weights: options.weights
});
} else {
dist = util.distance(subject, objectValues, {
weights: options.weights
});
}
options = options || {};
if (options.filter) {
_ref2 = node.val;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
o = _ref2[_i];
if (options.filter(o)) {
Q.insert(o, dist);
}
}
} else {
Q.insert(node.val, dist);
}
goLeft = subject[attr] < objectValues[attr];
if (goLeft) {
_helper(node.left, depth + 1);
} else {
_helper(node.right, depth + 1);
}
if (options.normalize) {
attr_dist = Math.abs(objectValues[attr] - subject[attr]) / _this.stdv[attr];
} else {
attr_dist = Math.abs(objectValues[attr] - subject[attr]);
}
if (options.weights) {
attr_dist *= options.weights[attr];
}
if (options.k > Q.getSize() || attr_dist < Q.getMaxPriority()) {
if (goLeft) {
_helper(node.right, depth + 1);
} else {
_helper(node.left, depth + 1);
}
}
return Q.getObjects();
};
root = this.getRoot();
if (root === null) {
return [];
} else {
return _helper(root, 0);
}
};
return KDtree;
})();
module.exports = KDtree;
}).call(this);