So I've been scouring the web to find a way to do this but nothing I've found fits the exact solution we are looking for.
I have an app that stores float numbers in a circular buffer. The circular buffer class can be found here https://www.npmjs.com/package/circular-buffer.
In my app new numbers are coming in every few miliseconds and the buffer holds around 60K values.
For the purpose of this question though I created a circular buffer of 10 with a stream of 100 randomly generated numbers to simulate the incoming data. This is the line that generates the simulated stream:
for (let i = 0; i < valuesToEnqueueForDemoPurposes.length; i++) {
With my current setup, it is taking the cpu too much time to convert the circular buffer to an array and then calculate its min, max, min position / index number, max position / index number and standard deviations (but the stddev is not the focus of this question).
Here is my current code:
stats = require("stats-lite");
var CircularBuffer = require("circular-buffer");
var circularBuffer10 = new CircularBuffer(10);
var valuesToEnqueueForDemoPurposes = Array.from(Array(100)).map(x=>Math.random() * 1000)
for (let i = 0; i < valuesToEnqueueForDemoPurposes.length; i++) {
var newValue = valuesToEnqueueForDemoPurposes[i];
circularBuffer10.enq(newValue);
let valuesArray = circularBuffer10.toarray();
var maxIndex = valuesArray.reduce((iMax, x, i, arr) => x > arr[iMax] ? i : iMax, 0);
var minIndex = valuesArray.reduce((iMin, x, i, arr) => x < arr[iMin] ? i : iMin, 0);
var max = valuesArray[maxIndex];
var min = valuesArray[minIndex];
var standardDeviation = stats.stdev(valuesArray);
console.log(maxIndex);
console.log(max);
console.log(minIndex);
console.log(min);
console.log(standardDeviation + "\n\n");
}
So I was wondering if it was possible to optimize this code with different data structures.
The closest answer I've found to solve this issue is from this SO answer: https://stackoverflow.com/a/48610455
It uses:
a queue of N items
a Min / Max Heap to track the min / max item.
A hash map to track the frequency of each item.
But the problem with this solution is that the heap is always growing and with the amount of differing incoming data I receive, this would cause a serious problem. And it also only calculates the maximum.
Also found this c++ solution but it is only for a normal queue, a max (not min) and I wasn't able to reproduce in javascript:
https://www.geeksforgeeks.org/design-a-queue-data-structure-to-get-minimum-or-maximum-in-o1-time/
Does anyone know if it would be possible, using whatever combination of data structures, to find the Max or Min in O(1) for this type of scenario (with or without circular buffers)?
Thanks to #thomas's advice I was able to alter the circular buffer class I was using so that the sum, mean,max, min and standard deviations are calculated on average at O(1) but at the worst O(N). Worked like a charm for the performance I needed.
Please note that for my purposes I have only altered the "unshift" method of the CBuffer circular buffer class . So the other methods won't be updating the max, min and standard deviations correctly. Here is the link to the jsfiddle:
https://jsfiddle.net/29f4an7s/
And here is my testing code:
// A standard deviation object constructor. Running deviation (avoid growing arrays) which
// is round-off error resistant. Based on an algorithm found in a Knuth book.
class StandardDeviation {
constructor() {
this.v = 0;
this.w = 0;
this.S = 0;
this.count = 0;
}
// Add a measurement. Also calculates updates to stepwise parameters which are later used
// to determine sigma.
add(measurement) {
this.count += 1;
this.w = this.v;
this.v = this.v + (measurement - this.v) / this.count;
this.S = this.S + (measurement - this.w) * (measurement - this.v);
}
// Performs the final step needed to get the standard deviation and returns it.
get() {
if (this.count < 2) {
// There are less measurements accumulated than necessary to perform computation
return 0.0;
} else {
return Math.sqrt(this.S / (this.count));
}
}
// Replaces the value x currently present in this sample with the
// new value y. In a sliding window, x is the value that
// drops out and y is the new value entering the window. The sample
// count remains constant with this operation.
replace(x, y) {
const deltaYX = y - x;
const deltaX = x - this.v;
const deltaY = y - this.v;
this.v = this.v + deltaYX / this.count;
const deltaYp = y - this.v;
const countMinus1 = this.count - 1;
this.S = this.S - this.count / countMinus1 * (deltaX * deltaX - deltaY * deltaYp) - deltaYX * deltaYp / countMinus1;
}
// Remove a measurement. Also calculates updates to stepwise parameters which are later used
// to determine sigma.
remove(x) {
this.w = (this.count * this.v - x) / (this.count - 1);
this.S -= (x - this.v) * (x - this.w);
this.v = this.w;
this.count -= 1;
}
}
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 size');
// this is the same in either scenario
this.length = this.start = 0;
// set to callback fn if data is about to be overwritten
this.overflow = null;
// set to callback fn if data is about to be overwritten
this.maxIndex = null;
this.minIndex = null;
this.max = null;
this.min = null;
this.sum = null;
this.mean = null;
this.standardDeviation = new StandardDeviation();
// emulate Array based on passed arguments
if (arguments.length > 1 || typeof arguments[0] !== 'number') {
this.data = new Array(arguments.length);
this.end = (this.size = arguments.length) - 1;
this.push.apply(this, arguments);
} else {
this.data = new Array(arguments[0]);
this.end = (this.size = 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;
}
function mod(n, m) {
return ((n % m) + m) % m;
}
CBuffer.prototype = {
// properly set constructor
constructor : CBuffer,
/* mutator methods */
// pop last item
pop : function () {
var item;
if (this.length === 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.size) % this.size;
this.length--;
return item;
},
// push item to the end
push : function () {
var i = 0;
var returnOverflow = false;
// check if overflow is set, and if data is about to be overwritten
if (this.overflow && this.length + arguments.length > this.size) {
// call overflow function and send data that's about to be overwritten
for (; i < this.length + arguments.length - this.size; i++) {
returnOverflow = this.data[(this.end + i + 1) % this.size];
// this.overflow(this.data[(this.end + i + 1) % this.size], 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.size] = arguments[i];
}
// recalculate length
if (this.length < this.size) {
if (this.length + i > this.size) this.length = this.size;
else this.length += i;
}
// recalculate end
this.end = (this.end + i) % this.size;
// recalculate start
this.start = (this.size + this.end - this.length + 1) % this.size;
// return number current number of items in CBuffer
return returnOverflow;
},
// reverse order of the buffer
reverse : function () {
var i = 0,
tmp;
for (; i < ~~(this.length / 2); i++) {
tmp = this.data[(this.start + i) % this.size];
this.data[(this.start + i) % this.size] = this.data[(this.start + (this.length - i - 1)) % this.size];
this.data[(this.start + (this.length - i - 1)) % this.size] = 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.length === 0) return;
// store first item for return
item = this.data[this.start];
// recalculate start of CBuffer
this.start = (this.start + 1) % this.size;
// decrement length
this.length--;
return item;
},
// sort items
sort : function (fn) {
this.data.sort(fn || defaultComparitor);
this.start = 0;
this.end = this.length - 1;
return this;
},
// add item to beginning of buffer
unshift : function () {
var i = 0;
var returnOverflow = false;
if (this.length == this.size) {
returnOverflow = this.last();
}
for (i = 0; i < arguments.length; i++) {
this.data[(this.size + this.start - (i % this.size) - 1) % this.size] = arguments[i];
}
if (this.size - this.length - i < 0) {
this.end += this.size - this.length - i;
if (this.end < 0) this.end = this.size + (this.end % this.size);
}
if (this.length < this.size) {
if (this.length + i > this.size) this.length = this.size;
else this.length += i;
}
this.start -= arguments.length;
if (this.start < 0) this.start = this.size + (this.start % this.size);
this.recalculateMaxMin(arguments[0], returnOverflow);
this.sum += arguments[0];
if (returnOverflow) {
this.sum -= returnOverflow;
this.standardDeviation.replace(returnOverflow, arguments[0])
}
else {
this.standardDeviation.add(arguments[0]);
}
this.mean = this.sum / this.length;
return returnOverflow;
},
/* accessor methods */
// return index of first matched element
indexOf : function (arg, idx) {
if (!idx) idx = 0;
for (; idx < this.length; idx++) {
if (this.data[(this.start + idx) % this.size] === arg) return idx;
}
return -1;
},
// return last index of the first match
lastIndexOf : function (arg, idx) {
if (!idx) idx = this.length - 1;
for (; idx >= 0; idx--) {
if (this.data[(this.start + idx) % this.size] === 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 isFull = this.length === this.size,
low = this.start,
high = isFull ? this.length - 1 : this.length;
// 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;
}
return !isFull ? low :
// http://stackoverflow.com/a/18618273/1517919
(((low - this.start) % this.length) + this.length) % this.length;
},
/* iteration methods */
// check every item in the array against a test
every : function (callback, context) {
var i = 0;
for (; i < this.length; i++) {
if (!callback.call(context, this.data[(this.start + i) % this.size], 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.length; i++) {
callback.call(context, this.data[(this.start + i) % this.size], i, this);
}
},
// construct new CBuffer of same length, apply map function, and return new CBuffer
map : function (callback, context) {
var outCBuffer = new CBuffer(this.size);
for (var i = 0; i < this.length; i++) {
var n = (this.start + i) % this.size;
outCBuffer.push(callback.call(context, this.data[n], i, this));
}
return outCBuffer;
},
// check items agains test until one returns true
// TODO: figure out how to emulate Array use better
some : function (callback, context) {
var i = 0;
for (; i < this.length; i++) {
if (callback.call(context, this.data[(this.start + i) % this.size], i, this))
return true;
}
return false;
},
// calculate the average value of a circular buffer
avg : function () {
return this.length == 0 ? 0 : (this.sum() / this.length);
},
// loop through each item in buffer and calculate sum
sum : function () {
var index = this.length;
var s = 0;
while (index--) s += this.data[index];
return s;
},
// loop through each item in buffer and calculate sum
getMaxPosition : function () {
// return 0
return (this.start + this.start + this.maxIndex) % this.size;
},
// loop through each item in buffer and calculate sum
getStandardDeviation : function () {
// return 0
return this.standardDeviation.get();
},
// loop through each item in buffer and calculate sum
getMinPosition : function () {
// return 0
return (this.start + this.start + this.minIndex) % this.size;
},
recalculateMaxMin : function (newValue, returnOverflow) {
if (this.length == 1) {
this.max = newValue;
this.maxIndex = this.start;
this.min = newValue;
this.minIndex = this.start;
return;
}
// Max / Mins
if (newValue > this.max) {
this.max = newValue;
this.maxIndex = this.start;
}
if (newValue < this.min) {
this.min = newValue;
this.minIndex = this.start;
}
// If overflow max or min recalculate
if (
returnOverflow && (returnOverflow >= this.max || returnOverflow <= this.min)
) {
this.maxIndex = 0;
this.minIndex = 0;
this.max = this.data[0];
this.min = this.data[0];
for (let i = 0; i < this.length; i++) {
if (this.data[i] > this.max) {
this.maxIndex = i;
this.max = this.data[i];
}
if (this.data[i] < this.min) {
this.minIndex = i;
this.min = this.data[i];
}
}
}
},
// loop through each item in buffer and calculate median
median : function () {
if (this.length === 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.length = this.start = 0;
this.end = this.size - 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.size);
} else {
while(this.data[i] = arg, ++i < this.size);
}
// reposition start/end
this.start = 0;
this.end = this.size - 1;
this.length = this.size;
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[mod(this.start + arg, this.size)];
},
isFull : function (arg) {
return this.size === this.length;
},
// set value at specified index
set : function (idx, arg) {
return this.data[(this.start + idx) % this.size] = arg;
},
// return clean array of values
toArray : function () {
return this.slice();
},
// return a string based on the array
join : function(separator) {
if (!separator) separator = ',';
var outString = new String(this.data[0]);
for (var i = 1; i < this.length; i++) {
var n = (this.start + i) % this.size;
outString = outString.concat(separator, this.data[i]);
}
return outString;
},
// slice the buffer to an arraay
slice : function (start, end) {
var size = this.length;
start = +start || 0;
if (start < 0) {
if (start >= end)
return [];
start = (-start > size) ? 0 : size + start;
}
if (end == null || end > size)
end = size;
else if (end < 0)
end += size;
else
end = +end || 0;
size = start < end ? end - start : 0;
var result = Array(size);
for (var index = 0; index < size; index++) {
result[index] = this.data[(this.start + start + index) % this.size];
}
return result;
}
};
var bufferLength = 3;
var numbersToGenerate = 10;
var circularBufferN = new CBuffer(bufferLength);
var valuesToEnqueueForDemoPurposes = Array.from(Array(numbersToGenerate)).map(x=>Math.random() * 1000)
for (let i = 0; i < valuesToEnqueueForDemoPurposes.length; i++) {
var newValue = valuesToEnqueueForDemoPurposes[i];
console.log("\n\nNEW VALUE****************************************************************:");
console.log(newValue);
console.log("STARTING UNSHIFT:");
console.log(circularBufferN.unshift(newValue));
let valuesArray = circularBufferN.data;
var maxIndex = circularBufferN.maxIndex;
var minIndex = circularBufferN.minIndex;
var max = valuesArray[maxIndex];
var min = valuesArray[minIndex];
console.log("Max Index");
console.log(maxIndex);
console.log("Max:");
console.log(max);
console.log("Min Index:");
console.log(minIndex);
console.log("Min:");
console.log(min);
console.log("Start:");
console.log(circularBufferN.start);
console.log("ORDERED ARRAY:");
console.log(circularBufferN.toArray());
console.log("Max Position:");
console.log(circularBufferN.getMaxPosition());
console.log("Min Position:");
console.log(circularBufferN.getMinPosition());
console.log('Sum:');
console.log(circularBufferN.sum);
console.log("mean:");
console.log(circularBufferN.mean);
console.log("Derived Standard Deviation");
console.log(circularBufferN.getStandardDeviation());
}
function plusD(n) {
if (n > 9) {
let varies = 0;
let NewN = n.toString().split('');
console.log(NewN)
for (let i = 0; i < NewN.length; i++) {
varies = varies + parseInt(NewN[i])
}
if (varies > 9) {
plusD(varies)
}
return varies;
}
}
As you can see, in the end there is an extra if statement used , when true calls the plusD function again which results in another addition of the number or if false(only when the number has been successfully reduced to single digit), returns the number.
You are missing a return. Without that, the function return value will never be taken into account.
function plusD(n) {
if (n > 9) {
let varies = 0;
let NewN = n.toString().split('');
console.log(NewN)
for (let i = 0; i < NewN.length; i++) {
varies = varies + parseInt(NewN[i])
}
if (varies > 9) {
return plusD(varies)
}
return varies;
}
}
console.log(plusD(29));
function plusD(n) {
var varies;
if (n > 9) {
varies = 0;
let NewN = n.toString().split('');
for (let i = 0; i < NewN.length; i++) {
varies = varies + parseInt(NewN[i])
}
if (varies > 9) {
return plusD(varies)
}
}
return varies;
}
console.log(plusD(123456));
I want to output the staircase from the symbols "#". It should look like this:
but all I achieve is this:
What should I do to get right output?
var n = 6;
var rows=[];
var cols=[];
for(i=n-1;i>=0;i--) {
rows=[];
for(j=0;j<n;j++) {
if(j >= i) {
rows[j] = "#";
} else {
rows[j] = "";
}
}
cols.push(rows);
cols.splice(0, cols.length - 1);
console.log(cols.join(","));
}
Think of it as a coordinate system, loop through y and x and add the needed symbols.
Remember to increase max x with current y if you want it dual sided.
function ladder(size, dualSided, empty, occupied) {
if (dualSided === void 0) { dualSided = true; }
if (empty === void 0) { empty = " "; }
if (occupied === void 0) { occupied = "▲"; }
var str = "";
for (var y = 0; y < size; y++) {
for (var x = 0; x < size + y; x++) {
if (dualSided != true && x == size) {
break;
}
if (x >= size - y - 1) {
str += occupied;
}
else {
str += empty;
}
}
str += "\n";
}
return str;
}
console.log(ladder(20, false));
Ok, try this(last 3 lines);
cols.push(rows.join(""));
cols.splice(0, cols.length - 1);
console.log(cols.join(""));
The issue is you are pushing array(row) in cols, where row array itself contains comma. If you do cols.push(rows.join("")) , all comma will be removed.
Simple solution using Array.from
Live demo check below.
function staircase(n) {
let arr = Array.from({ length: n }).fill(0);
arr.map((v,i) => {
let dummyArr = Array.from({ length: i+1 }).fill('#');
let spaceArr = Array.from({ length: arr.length - (i+1) }).fill(' ');
console.log(`${spaceArr.join('')}${dummyArr.join('')}`);
});
}
staircase(6);
try this simple solution
for (let i = 1; i <= n; i++) {
console.log("#".repeat(i).padStart(n));
}
In my palindrome function an if-else-statement returns undefined. Basically, I am trying to find the biggest palindromic number with three digits. For example with two digits: 99 * 91 = 9009.
var palindromic = function(n) {
var save,
result,
counter = 900;
var checker = function(string) {
s = string.toString();
if(!(s)) {
return true;
} else if(s[0] !== s[s.length - 1]) {
return false;
}
checker(s.slice(1, -1));
}
var recursive = function() {
result = counter * n;
if(counter === n) {
return;
} else if(checker(result)) { // this line of code here, undefined.
save = result;
}
counter++;
recursive();
}
recursive();
return save;
};
What is wrong? Any help is welcome!
There are two problems in the code
checker() should have return checker(s.slice(1,-1)); as last line.
In recursive() when checker(result) is true recursive() should return.
Here's corrected code.
var palindromic = function (n) {
var save,
result,
counter = 900;
var checker = function (s) {
//s = string.toString();
if ( !(s) ) {
return true;
} else if ( s[0] !== s[s.length-1] ) {
return false;
}
return checker(s.slice(1,-1));
}
var recursive = function () {
result = counter * n;
if ( counter === n ) {
return;
} else if ( checker(result + "") ) { // this line of code here, undefined.
save = result;
return;
}
counter++;
recursive();
}
recursive();
return save;
};
Output:
palindromic(2)
2002
palindromic(3)
2772
palindromic(5)
5005
palindromic(6)
6006
palindromic(9)
8118
palindromic(23423)
188484881
A little spoiler and improving
You can do it without a recursion.
Usage of a reverse function to reverse a string.
Usage of two for loops, starting from the highest number.
function strReverse(str) {
var reverse = "";
for(var i = (str.length - 1); i >= 0; i -= 1) {
reverse += str[i];
}
return reverse;
}
function palindromic(n) {
var highestPalindrom = 0;
// Start with the highest number!
for(var i = n; i > 1; i -= 1) {
// Start also with the highest number!
for(var j = n; j > 1; j -= 1) {
// Get product
var product = i * j;
// Compare the string in reverse with the string itself
// If it is true, then it is a palindrom!
if(strReverse(product.toString()) === product.toString()) {
highestPalindrom = product;
// Break inner loop
break;
}
}
// Break outer loop
break;
}
return highestPalindrom;
}
var hP = palindromic(99);
console.log(hP);
I'm attempting to get better with optimizing algorithms and understanding big-o, etc.
I threw together the below function to calculate the n-th Fibonacci number. This works (for a reasonably high input). My question is, how can I improve this function? What are the drawbacks of calculating the Fibonacci sequence this way?
function fibo(n) {
var i;
var resultsArray = [];
for (i = 0; i <= n; i++) {
if (i === 0) {
resultsArray.push(0);
} else if (i === 1) {
resultsArray.push(1);
} else {
resultsArray.push(resultsArray[i - 2] + resultsArray[i - 1]);
}
}
return resultsArray[n];
}
I believe my big-o for time is O(n), but my big-o for space is O(n^2) due to the array I created. Is this correct?
If you don't have an Array then you save on memory and .push calls
function fib(n) {
var a = 0, b = 1, c;
if (n < 3) {
if (n < 0) return fib(-n);
if (n === 0) return 0;
return 1;
}
while (--n)
c = a + b, a = b, b = c;
return c;
}
Performance Fibonacci:
var memo = {};
var countInteration = 0;
var fib = function (n) {
if (memo.hasOwnProperty(n)) {
return memo[n];
}
countInteration++;
console.log("Interation = " + n);
if (n == 1 || n == 2) {
result = 1;
} else {
result = fib(n - 1) + fib(n - 2);
}
memo[n] = result;
return result;
}
//output `countInteration` = parameter `n`