mirror of
https://github.com/damouse/Drop64.git
synced 2024-06-12 09:07:34 -04:00
326 lines
9.5 KiB
JavaScript
326 lines
9.5 KiB
JavaScript
(function (global) {
|
|
|
|
function CBuffer() {
|
|
// handle cases where "new" keyword wasn't used
|
|
if (!(this instanceof CBuffer)) {
|
|
// multiple conditions need to be checked to properly emulate Array
|
|
if (arguments.length > 1 || typeof arguments[0] !== 'number') {
|
|
return CBuffer.apply(new CBuffer(arguments.length), arguments);
|
|
} else {
|
|
return new CBuffer(arguments[0]);
|
|
}
|
|
}
|
|
// if no arguments, then nothing needs to be set
|
|
if (arguments.length === 0)
|
|
throw new Error('Missing Argument: You must pass a valid buffer length');
|
|
// this is the same in either scenario
|
|
this.size = this.start = 0;
|
|
// set to callback fn if data is about to be overwritten
|
|
this.overflow = null;
|
|
// emulate Array based on passed arguments
|
|
if (arguments.length > 1 || typeof arguments[0] !== 'number') {
|
|
this.data = new Array(arguments.length);
|
|
this.end = (this.length = arguments.length) - 1;
|
|
this.push.apply(this, arguments);
|
|
} else {
|
|
this.data = new Array(arguments[0]);
|
|
this.end = (this.length = arguments[0]) - 1;
|
|
}
|
|
// need to `return this` so `return CBuffer.apply` works
|
|
return this;
|
|
}
|
|
|
|
function defaultComparitor(a, b) {
|
|
return a == b ? 0 : a > b ? 1 : -1;
|
|
}
|
|
|
|
CBuffer.prototype = {
|
|
// properly set constructor
|
|
constructor : CBuffer,
|
|
|
|
/* mutator methods */
|
|
// pop last item
|
|
pop : function () {
|
|
var item;
|
|
if (this.size === 0) return;
|
|
item = this.data[this.end];
|
|
// remove the reference to the object so it can be garbage collected
|
|
delete this.data[this.end];
|
|
this.end = (this.end - 1 + this.length) % this.length;
|
|
this.size--;
|
|
return item;
|
|
},
|
|
// push item to the end
|
|
push : function () {
|
|
var i = 0;
|
|
// check if overflow is set, and if data is about to be overwritten
|
|
if (this.overflow && this.size + arguments.length > this.length) {
|
|
// call overflow function and send data that's about to be overwritten
|
|
for (; i < this.size + arguments.length - this.length; i++) {
|
|
this.overflow(this.data[(this.end + i + 1) % this.length], this);
|
|
}
|
|
}
|
|
// push items to the end, wrapping and erasing existing items
|
|
// using arguments variable directly to reduce gc footprint
|
|
for (i = 0; i < arguments.length; i++) {
|
|
this.data[(this.end + i + 1) % this.length] = arguments[i];
|
|
}
|
|
// recalculate size
|
|
if (this.size < this.length) {
|
|
if (this.size + i > this.length) this.size = this.length;
|
|
else this.size += i;
|
|
}
|
|
// recalculate end
|
|
this.end = (this.end + i) % this.length;
|
|
// recalculate start
|
|
this.start = (this.length + this.end - this.size + 1) % this.length;
|
|
// return number current number of items in CBuffer
|
|
return this.size;
|
|
},
|
|
// reverse order of the buffer
|
|
reverse : function () {
|
|
var i = 0,
|
|
tmp;
|
|
for (; i < ~~(this.size / 2); i++) {
|
|
tmp = this.data[(this.start + i) % this.length];
|
|
this.data[(this.start + i) % this.length] = this.data[(this.start + (this.size - i - 1)) % this.length];
|
|
this.data[(this.start + (this.size - i - 1)) % this.length] = tmp;
|
|
}
|
|
return this;
|
|
},
|
|
// rotate buffer to the left by cntr, or by 1
|
|
rotateLeft : function (cntr) {
|
|
if (typeof cntr === 'undefined') cntr = 1;
|
|
if (typeof cntr !== 'number') throw new Error("Argument must be a number");
|
|
while (--cntr >= 0) {
|
|
this.push(this.shift());
|
|
}
|
|
return this;
|
|
},
|
|
// rotate buffer to the right by cntr, or by 1
|
|
rotateRight : function (cntr) {
|
|
if (typeof cntr === 'undefined') cntr = 1;
|
|
if (typeof cntr !== 'number') throw new Error("Argument must be a number");
|
|
while (--cntr >= 0) {
|
|
this.unshift(this.pop());
|
|
}
|
|
return this;
|
|
},
|
|
// remove and return first item
|
|
shift : function () {
|
|
var item;
|
|
// check if there are any items in CBuff
|
|
if (this.size === 0) return;
|
|
// store first item for return
|
|
item = this.data[this.start];
|
|
// recalculate start of CBuffer
|
|
this.start = (this.start + 1) % this.length;
|
|
// decrement size
|
|
this.size--;
|
|
return item;
|
|
},
|
|
// sort items
|
|
sort : function (fn) {
|
|
this.data.sort(fn || defaultComparitor);
|
|
this.start = 0;
|
|
this.end = this.size - 1;
|
|
return this;
|
|
},
|
|
// add item to beginning of buffer
|
|
unshift : function () {
|
|
var i = 0;
|
|
// check if overflow is set, and if data is about to be overwritten
|
|
if (this.overflow && this.size + arguments.length > this.length) {
|
|
// call overflow function and send data that's about to be overwritten
|
|
for (; i < this.size + arguments.length - this.length; i++) {
|
|
this.overflow(this.data[this.end - (i % this.length)], this);
|
|
}
|
|
}
|
|
for (i = 0; i < arguments.length; i++) {
|
|
this.data[(this.length + this.start - (i % this.length) - 1) % this.length] = arguments[i];
|
|
}
|
|
if (this.length - this.size - i < 0) {
|
|
this.end += this.length - this.size - i;
|
|
if (this.end < 0) this.end = this.length + (this.end % this.length);
|
|
}
|
|
if (this.size < this.length) {
|
|
if (this.size + i > this.length) this.size = this.length;
|
|
else this.size += i;
|
|
}
|
|
this.start -= arguments.length;
|
|
if (this.start < 0) this.start = this.length + (this.start % this.length);
|
|
return this.size;
|
|
},
|
|
|
|
/* accessor methods */
|
|
// return index of first matched element
|
|
indexOf : function (arg, idx) {
|
|
if (!idx) idx = 0;
|
|
for (; idx < this.size; idx++) {
|
|
if (this.data[(this.start + idx) % this.length] === arg) return idx;
|
|
}
|
|
return -1;
|
|
},
|
|
// return last index of the first match
|
|
lastIndexOf : function (arg, idx) {
|
|
if (!idx) idx = this.size - 1;
|
|
for (; idx >= 0; idx--) {
|
|
if (this.data[(this.start + idx) % this.length] === arg) return idx;
|
|
}
|
|
return -1;
|
|
},
|
|
|
|
// return the index an item would be inserted to if this
|
|
// is a sorted circular buffer
|
|
sortedIndex : function(value, comparitor, context) {
|
|
comparitor = comparitor || defaultComparitor;
|
|
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;
|
|
},
|
|
|
|
/* iteration methods */
|
|
// check every item in the array against a test
|
|
every : function (callback, context) {
|
|
var i = 0;
|
|
for (; i < this.size; i++) {
|
|
if (!callback.call(context, this.data[(this.start + i) % this.length], i, this))
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
// loop through each item in buffer
|
|
// TODO: figure out how to emulate Array use better
|
|
forEach : function (callback, context) {
|
|
var i = 0;
|
|
for (; i < this.size; i++) {
|
|
callback.call(context, this.data[(this.start + i) % this.length], i, this);
|
|
}
|
|
},
|
|
// check items agains test until one returns true
|
|
// TODO: figure out how to emuldate Array use better
|
|
some : function (callback, context) {
|
|
var i = 0;
|
|
for (; i < this.size; i++) {
|
|
if (callback.call(context, this.data[(this.start + i) % this.length], i, this))
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
// calculate the average value of a circular buffer
|
|
avg : function () {
|
|
return this.size == 0 ? 0 : (this.sum() / this.size);
|
|
},
|
|
// loop through each item in buffer and calculate sum
|
|
sum : function () {
|
|
var index = this.size;
|
|
var s = 0;
|
|
while (index--) s += this.data[index];
|
|
return s;
|
|
},
|
|
// loop through each item in buffer and calculate median
|
|
median : function () {
|
|
if (this.size === 0)
|
|
return 0;
|
|
var values = this.slice().sort(defaultComparitor);
|
|
var half = Math.floor(values.length / 2);
|
|
if(values.length % 2)
|
|
return values[half];
|
|
else
|
|
return (values[half-1] + values[half]) / 2.0;
|
|
},
|
|
/* utility methods */
|
|
// reset pointers to buffer with zero items
|
|
// note: this will not remove values in cbuffer, so if for security values
|
|
// need to be overwritten, run `.fill(null).empty()`
|
|
empty : function () {
|
|
var i = 0;
|
|
this.size = this.start = 0;
|
|
this.end = this.length - 1;
|
|
return this;
|
|
},
|
|
// fill all places with passed value or function
|
|
fill : function (arg) {
|
|
var i = 0;
|
|
if (typeof arg === 'function') {
|
|
while(this.data[i] = arg(), ++i < this.length);
|
|
} else {
|
|
while(this.data[i] = arg, ++i < this.length);
|
|
}
|
|
// reposition start/end
|
|
this.start = 0;
|
|
this.end = this.length - 1;
|
|
this.size = this.length;
|
|
return this;
|
|
},
|
|
// return first item in buffer
|
|
first : function () {
|
|
return this.data[this.start];
|
|
},
|
|
// return last item in buffer
|
|
last : function () {
|
|
return this.data[this.end];
|
|
},
|
|
// return specific index in buffer
|
|
get : function (arg) {
|
|
return this.data[(this.start + arg) % this.length];
|
|
},
|
|
isFull : function (arg) {
|
|
return this.length === this.size;
|
|
},
|
|
// set value at specified index
|
|
set : function (idx, arg) {
|
|
return this.data[(this.start + idx) % this.length] = arg;
|
|
},
|
|
// return clean array of values
|
|
toArray : function () {
|
|
return this.slice();
|
|
},
|
|
// slice the buffer to an arraay
|
|
slice : function (start, end) {
|
|
var length = this.size;
|
|
|
|
start = +start || 0;
|
|
|
|
if (start < 0) {
|
|
if (start >= end)
|
|
return [];
|
|
start = (-start > length) ? 0 : length + start;
|
|
}
|
|
|
|
if (end == null || end > length)
|
|
end = length;
|
|
else if (end < 0)
|
|
end += length;
|
|
else
|
|
end = +end || 0;
|
|
|
|
length = start < end ? end - start : 0;
|
|
|
|
var result = Array(length);
|
|
for (var index = 0; index < length; index++) {
|
|
result[index] = this.data[(this.start + start + index) % this.length];
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
|
|
if (typeof module === 'object' && module.exports) module.exports = CBuffer;
|
|
else global.CBuffer = CBuffer;
|
|
|
|
}(this));
|