I'm building a tool to join multiple streams by the nearest timestamp. The streams may not be in sync so I store the last n
(probably >=500) items in a fixed size circular buffer. I'd like to use a sortedIndex
(not search
) to find where an item would be place in the buffer. I need this index to find the stream item just before and just after the time stamp.
The handling of edge cases of around the corner issues isn't important, I don't care if you return an index outside the array or 0
for the largest value. I was playing around with implementing this last night and couldn't figure out a working implementation.
Function contract is below and is based on _.sortedIndex
(implementation)
/**
* Binary search for finding the closest computed (by iterator) value
* in some sorted circular array
*
* @param {Array} array a circular array-like
* @param {Object} value to search for an index
* @param {Function} iterator to compute the compare value of an item
*/
function sortedIndex(array, value, iterator) {
var low = 0,
high = array.length;
while (low != high && iterator(array[low]) > iterator(array[high])) {
// The binary search I failed to implement
}
return low;
}
Some test cases (again feel free to handle the around a corner cases differently): http://jsbin.com/yusilaba/1/edit
function identity(x) {
return x;
}
function property(prop) {
return function(x) {
return x[prop];
};
}
test('sortedIndex should work on simple case', function(t) {
var array = [1, 2, 3, 4];
equal(sortedIndex(array, 2, identity), 1, 'equal case sorts towards left');
equal(sortedIndex(array, 2.5, identity), 2);
equal(sortedIndex(array, 10, identity), 0);
equal(sortedIndex(array, -10, identity), 3);
array = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
equal(sortedIndex(array, {a: 2}, property('a')), 1);
equal(sortedIndex(array, {a: 2.5}, property('a')), 2);
equal(sortedIndex(array, {a: 10}, property('a')), 0);
equal(sortedIndex(array, {a: -10}, property('a')), 3);
});
test('sortedIndex should work on circular collections', function() {
var array = [2, 3, 4, 1, 1.5];
equal(sortedIndex(array, 2, identity), 0, 'equal case sorts towards left');
equal(sortedIndex(array, 2.5, identity), 1);
equal(sortedIndex(array, 10, identity), 3);
equal(sortedIndex(array, -10, identity), 2);
equal(sortedIndex(array, 5, identity), 4);
equal(sortedIndex(array, 3.5, identity), 3);
array = [{a: 2}, {a: 3}, {a: 4}, {a: 1}, {a: 1.5}];
equal(sortedIndex(array, {a: 2}, property('a')), 0, 'equal case sorts towards left');
equal(sortedIndex(array, {a: 2.5}, property('a')), 1);
equal(sortedIndex(array, {a: 10}, property('a')), 3);
equal(sortedIndex(array, {a: -10}, property('a')), 2);
});
Edit --- Here's my completed version https://github.com/trevnorris/cbuffer/pull/14
sortedIndex : function(value, comparitor, context) {
var low = this.start,
high = this.size - 1;
// Tricky part is finding if its before or after the pivot
// we can get this info by checking if the target is less than
// the last item. After that it's just a typical binary search.
if (low && comparitor.call(context, value, this.data[high]) > 0) {
low = 0, high = this.end;
}
while (low < high) {
var mid = (low + high) >>> 1;
if (comparitor.call(context, value, this.data[mid]) > 0) low = mid + 1;
else high = mid;
}
// http://stackoverflow.com/a/18618273/1517919
return (((low - this.start) % this.size) + this.size) % this.size;
}