How to reduce number of computations during d3.js transition? - javascript

So right now, I'm trying to implement a search bar function into my d3.js plot. Right now it doesn't do anything, but that's not the issue at the moment. The problem is that when I type/delete something from the bar, there's visible lag/choppiness in the characters appearing/disappearing. I believe the issue is stemming from my plot. I have 140+ dots moving around the screen, and their position is being interpolated. So from the beginning to the end of the transition, my code has to compute 140 positions thousands of times over.
I've looked into trying to reduce the cardinality of the d3.interpolateNumber function, but it appears that there isn't a third argument to change the number of terms like in a linspace command. Right now I have an array of 1000 numbers for my function to run through, but I don't know how to pass the array to my other functions.
Below are the pertinent functions for this issue. The commented line in tweenPatch is the original code I had that made my code run, but gave my plot computational issues. Variables arr, curr, and step were my attempt to fix the situation, but I haven't been able to figure out how to pass the array into displayPatch().
function tweenPatch() {
var patch = d3.interpolateNumber(1, 26);
var arr = [];
var curr = 1;
var step = (26 - 1) / (1000 - 1);
for (var i = 0; i < 1000; i++) {
arr.push(curr + (step * i));
}
return arr.forEach(function(d) {
console.log(arr[d]);
displayPatch(arr[d]);
});
//return function(t) { displayPatch(t); };
}
function displayPatch(patch) {
dots.data(interpolateData(patch), function(d) { return d.name; }).call(position).sort(order);
var inter = Math.floor(patch);
var seas = 8;
var patc = 1;
if (inter > 24) {
seas = 9;
patc = inter - 24;
} else {
patc = inter;
}
label.text("Patch " + seas + "." + patc);
}
function interpolateValues(values, number) {
old = Math.floor(number);
upd = Math.ceil(number);
var old_data = values.filter(function(d) {return d.internal == old;});
var new_data = values.filter(function(d) {return d.internal == upd;});
var oobj = old_data[0];
var nobj = new_data[0];
var onum = oobj[Object.keys(oobj)[4]];
var nnum = nobj[Object.keys(nobj)[4]];
var difint = number - old;
var difdis = 0;
var newnum = nnum;
if (nnum > onum) {
difdis = nnum - onum;
newnum = ((difint) * difdis) + onum;
} else if (onum > nnum) {
difdis = onum - nnum;
newnum = onum - ((difint) * difdis);
}
return newnum;
}
I believe switching my SVG to a canvas may help things, but since I have no knowledge of canvas I'd rather leave that as a last resort.

Related

Print target labels on xAxis with labels.formatter

I have some plotLines and I just need to print labels corresponding to those plotLines on my xAxis.
My data is a random value around 100 (yAxis) and dates that increments by 10 days (xAxis).
var getDaysArray = function(start, end) {
for (var arr = [], dt = start; dt <= end; dt.setDate(dt.getDate() + 10)) {
arr.push(new Date(dt));
}
return arr;
};
function generateDataPoints(noOfDps) {
var yVal = 100;
var dps = [];
var xDate = getDaysArray(new Date("2006-01-01"), new Date("2019-12-30"));
for (var i = 0; i < xDate.length; i++) {
yVal = yVal + Math.round(5 + Math.random() * (-5 - 5));
dps.push({ x: xDate[i], y: yVal });
}
return dps;
}
let dataPoints = generateDataPoints(100);
There is also a function that gets the min, max, first and last values of y and returns an array of markers. Those are the points where I have plotLines.
function setHighestLowest(dtPoints) {
let highestIndex = -1;
let minimumIndex = -1;
let highestValue = 0;
let lowestValue = 0;
for (let i = 0; i < dtPoints.length; i++) {
let obj = dtPoints[i];
if (obj.y > highestValue) {
highestIndex = i;
highestValue = obj.y;
}
if (obj.y < lowestValue || i === 0) {
minimumIndex = i;
lowestValue = obj.y;
}
}
dtPoints[0].indexLabel = dtPoints[0].y.toString();
dtPoints[dtPoints.length - 1].indexLabel = dtPoints[
dtPoints.length - 1
].y.toString();
if (highestIndex > -1) {
dtPoints[highestIndex].indexLabel = dtPoints[highestIndex].y.toString();
}
if (minimumIndex > -1) {
dtPoints[minimumIndex].indexLabel = dtPoints[minimumIndex].y.toString();
}
//returns -> [max[0], min[1], first[2], last[3]]
let dateMarker = [
dtPoints[highestIndex].x,
dtPoints[minimumIndex].x,
dtPoints[1].x,
dtPoints[dtPoints.length - 1].x
];
//console.log(dateMarker);
return dateMarker;
}
My approach is to define labels.formatter functions for the xAxis.
First issue I found was the increment steps, I tried xAxis.labels.step but it didn't work, than I found xAxis.tickIntervals it seemed to work at first, the only problem was that to prevent labels from being printed outside of xAxis area the formatter function value started from a previous point from my dataset, there was some kind of offset, formatter function increment would never fit my data (offset problem). To solve this offset problem I tried the xAxis.tickPositions and called my markers array, from that point I was able to acces the points on formatter function, the only problem is that it returns nothing, I get inside the if loop that does the checking but nothing is printed on xAxis.labels.
The final result I want in my plot is something like that:
This is my code:
What do you think about use the plotLines.label feature to render those labels? I think that it will be easier to implement this logic there.
Demo: https://codesandbox.io/s/highcharts-react-xaxis-label-formatter-8xdcg
API: https://api.highcharts.com/highcharts/xAxis.plotLines.label

Ease time between firing specific number of timeouts in a specific period of time

It's kind of math problem. I want to fire specific number of setTimeout (the number is based on an array length) in a specific period of time (say, 5 seconds).
The first setTimeout should start at 0 sec. and the last at 5 sec.. All timeouts between should start with an ease-in effect, so that each timeout starts faster.
There's an example which ilustrates what I want to achieve exactly.
I'm struggling around this line:
next += timePeriod/3.52/(i+1);
which works almost perfect in demo example (for any timePeriod), but obviously it doesn't work for a different letters.length as I have used static number 3.52.
How do I calculate next?
var letters = [
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T'
];
var div = $('#container');
var timePeriod = 5000; // 5 seconds;
var perLetter = timePeriod/(letters.length-1); // it gives equal time between letters
var next = 0;
for(var i=0; i<letters.length; i++){
setTimeout(function(letter){
//div.append('<span class="letter">' + letter + '</span>');
// Used "|" instead of letter, For better redability:
div.append('<span class="letter">|</span>');
}, next, letters[i]);
// Can't find the logic here:
next += timePeriod/3.52/(i+1);
};
///////////////// FOR DEMO: ///////////////
var sec = timePeriod/1000;
var secondsInterval = setInterval(seconds, 1000);
var demoInterval = setInterval(function(){
sec >= 0 || clearInterval(demoInterval);
div.append('\'');
}, 30);
function seconds(){
sec || clearInterval(secondsInterval);
$('#sec').text(sec-- || 'DONE');
}
seconds();
.letter{
color : red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id=container></span>
<span id=sec class=letter></span>
var steps = letters.length;
var target = timePeriod;
function easeOutQuad(t, b, c, d) {
t /= d;
return -c * t*(t-2) + b;
};
var arrayOfTimeouts = new Array(steps);
var n;
var prev = 0;
for(var i = 1; i <= steps; i++){
n = easeOutQuad(i, 0.0, target, steps);
arrayOfTimeouts[i-1] = n-prev;
prev = n;
}
This one should work with any input value.
fiddle
Note that the graph appears to be slightly too fast but I believe that discrepancy to be a product of timing imperfections, as the sum of my array equals the timePeriod exactly.
more on easing equations
Here's a solution based on a geometric series. It's a bit goofy but it works. It generates an array with your timeout values.
Steps = size of your array.
Target = the total time.
var steps = 50;
var target = 5000;
var fraction = 1.5 + steps / 7;
var ratio = (fraction-1) / fraction;
var n = target / fraction;
var sum = 0;
var arrayOfTimeouts = new Array(steps);
for(var i = 0; i < steps; i++){
sum += n;
arrayOfTimeouts[i] = n;
n *= ratio;
}
console.log(arrayOfTimeouts, sum);

Creating a slider between two numbers

So I've been working on re-producing the slider found here https://www.skylight.io/ ( Scroll down to find the price slider ).
So far Ive managed to create something similiar, but some numbers are hard coded, making it difficult to change and not very re-usable.
I've been researching around and I think I need to use Math.log() and Math.exp() together to achieve something like in the link above but I'm not sure.
Heres a jsfiddle of what I have so far https://jsfiddle.net/7wrvpb34/.
I feel that its the maths part of this problem that is halting me I think, so any help would be greatly appreciated.
Javascript code below:
var slider = document.getElementById("slider")
var sliderFill = document.getElementById("slider-fill")
var knob = document.getElementById("knob")
var mouseDown;
var mousePos = {x:0};
var knobPosition;
var minPrice = 20;
var price = 0;
var minRequests = 50;
var requests = 50 + ",000";
var incrementSpeed = 2;
var incrementModifier = 20;
var incrementValue = 1;
var minMillionCount = 1;
var millionCount = 1;
var previousRequestAmount = 0;
document.getElementById("price").innerHTML = price;
document.getElementById("requests").innerHTML = requests;
highlightTable(1);
document.addEventListener('mousemove', function(e) {
if(mouseDown) {
updateSlider(e);
}
})
function updateSlider(event) {
mousePos.x = event.clientX - slider.getBoundingClientRect().left;
mousePos.x -= knob.offsetWidth / 2;
console.log(mousePos.x);
if(mousePos.x < 0) {
knob.style.left = "0px";
sliderFill.style.width = "0px";
price = 0;
requests = 50 + ",000";
document.getElementById("price").innerHTML = price;
document.getElementById("requests").innerHTML = requests;
return
}
if(mousePos.x > slider.offsetWidth - 20) {
return
}
sliderFill.style.width = mousePos.x + 10 + "px";
knob.style.left = mousePos.x + "px";
//Increase requests by using X position of mouse
incrementSpeed = mousePos.x / incrementModifier;
requests = minRequests + (mousePos.x * incrementSpeed);
//Round to nearest 1
requests = Math.round(requests / incrementValue) * incrementValue;
if (requests >= 1000){
var m = requests/ 1000;
m = Math.round(m / 1) * 1;
//Problem, lower the modifier depending on requests
incrementModifier = 20 * 0.95;
document.getElementById("requests").innerHTML = m + " million";
//Adjust Prices
if(( requests >= 1000) && (requests < 10000)) {
var numOfMillions = requests / 100;
//Round to closest 10.
//10 * number of millions
var rounded = Math.round(numOfMillions / 10) * 10;
price = minPrice + rounded;
highlightTable(3);
}
//Adjust Prices
if(requests >= 10000) {
var numOfMillions = requests / 1000;
var rounded = Math.round(numOfMillions / 1) * 1;
var basePrice = minPrice * 6;
price = basePrice + rounded;
highlightTable(4);
}
} else {
incrementModifier = 20;
document.getElementById("requests").innerHTML = requests + ",000"
if(requests < 100) {
highlightTable(1);
price = 0;
} else {
highlightTable(2);
price = 20;
}
}
previousRequestAmount = requests;
document.getElementById("price").innerHTML = price;
}
knob.addEventListener('mousedown', function() {
mouseDown = true;
});
document.addEventListener('mouseup', function() {
mouseDown = false;
});
function highlightTable(rowNum) {
var table = document.getElementById("payment-table")
for(var i = 0; i < table.rows.length; ++i) {
var row = table.rows[i]
if(i == rowNum) {
row.style.background = "grey"
} else {
row.style.background = "white";
}
}
}
Thank you for your time.
If you want it to be reusable you need to create a mathematical function that assigns a result to the number of requests. I will give you a very easy example.
If you want a different result for 1,10,100,100,10000 etc
var d = Math.log10(requests);
if(d<1){
doSomething();
}else if(d<2){
doSomethingElse();
} //etc
This way if you want to change the specific values that create certain results, all you need to do is change the function.
This only works if your tiers of requests follow a math function, if they don't you need to hard code it.
However if say they don't follow a math function, but you know how you would like them to change based on a value then you can do this.
var changingValue = 4;
if(requests < 400*changingValue){
doSomthing();
}else if(requests <= 400*changingValue*changingValue){
doSomethingElse();
}else{// the requests is greater than any of the above
doTheOtherThing();
}
Edit:
For the second one you need to make sure that each condition if always larger than the other from top to bottom.
The description "increasingly increasing" matches an arbitrary number of functions. I assume you also want it to be continuous, since you already have a non-continuous solution.
TL;DR
Use an exponential function.
Generic approach
Assuming imin and imax are the minimal and maximal values of the slider (i for input) and omin and omax are the minimal and maximal values to be displayed, the simplest thing I can think of would be a multiplication by something based on the input value:
f(x)
{
return omin + (omax - omin) * g((x - imin) / (imax - imin));
}
This will pass 0 to g if x == imin and 1 if x == imax.
The return value r of g(y) should be
r == 0 for y == 0
r == 1 for y == 1
0 < r < y for 0 < y < 1
The simplest function that I can think of that fulfills this is an exponential function with exponent > 1.
An exponent of 1 would be a linear function.
An exponent of 2 would be make the middle of the slider display one fourth of the maximum price instead of half of it.
But you really need to find that exponent yourself, based on your needs.

Solving Linear Equations & similar Algebra Problems with JavaScript

I'm new to JavaScript and I am trying to write a simple script that solves linear equations. So far my script solves linear equations that are plus and minus only such as "2x + 28 - 18x = 36 - 4x + 10". I want it to also be able to solve linear equations/algebra problems that contain multiplication and division such as "2x * 3x = 4 / 2x".
I kind of have an idea of what to do next but I think the script I have right now maybe overly complex and it's only going to make it more complicated to add the multiplication and division.
Below is my script. I'm hoping for a few pointers on how I could improve and simplify what I already have and what the best way to add multiplication and division?
My script on JS Bin: http://jsbin.com/ufekug/1/edit
My script:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Problem Solver</title>
<script>
window.onload = function() {
// Total Xs on each side of equation
// Example problem: 5x + 2 = 10 - 2x
var leftSideXTotal = 0; // 5
var rightSideXTotal = 0; // -2
// Total integers on each side of equation
// Example problem: 5x + 2 = 10 - 2x
var leftSideIntTotal = 0; // 2
var rightSideIntTotal = 0; // 10
// Enter a math problem to solve
var problem = "5x + 2 = 10 - 2x";
// Remove all spaces in problem
// Example problem: 5x + 2 = 10 - 2x
problem = problem.replace(/\s/g,''); // 5x+2=10-2x
// Add + signs in front of all - signs
// Example problem: 5x + 2 = 10 - 2x
problem = problem.replace(/-/gi, "+-"); // 5x+2=10+-2x
// Split problem into left and right sides
// Example problem: 5x + 2 = 10 - 2x
var problemArray = problem.split("=");
var problemLeftSide = problemArray[0]; // 5x+2
var problemRightSide = problemArray[1]; // 10+-2x
// Split values on each side into an array
var problemLeftSideValues = problemLeftSide.split("+");
var problemRightSideValues = problemRightSide.split("+");
// Go through the left side values and add them up
for (var i = 0; i < problemLeftSideValues.length; i++) {
// Current value
var currentValue = problemLeftSideValues[i];
// Length of current value
var currentValueLength = currentValue.length;
if (currentValue.charAt(currentValueLength - 1) == "x") { //Check if current value is a X value
// Remove X from end of current value
currentValue = currentValue.split("x");
// Add to total Xs on left side
leftSideXTotal = Number(leftSideXTotal) + Number(currentValue[0]);
} else {
// Add to total integers on left side
leftSideIntTotal = Number(leftSideIntTotal) + Number(problemLeftSideValues[i]);
}
}
// Go through the right side values and add them up
for (var i = 0; i < problemRightSideValues.length; i++) {
// Current value
var currentValue = problemRightSideValues[i];
// Length of current value
var currentValueLength = currentValue.length;
if (currentValue.charAt(currentValueLength - 1) == "x") { //Check if current value is a X value
// Remove X from end of current value
currentValue = currentValue.split("x");
// Add to total Xs on right side
rightSideXTotal = Number(rightSideXTotal) + Number(currentValue[0]);
} else {
// Add to total integers on right side
rightSideIntTotal = Number(rightSideIntTotal) + Number(problemRightSideValues[i]);
}
}
// Compute
var totalXs = (leftSideXTotal - rightSideXTotal)
var totalIntegers = (rightSideIntTotal - leftSideIntTotal)
var solution = (totalIntegers / totalXs)
// Display solution
document.getElementById("divSolution").innerText = solution;
}
</script>
</head>
<body>
<div id="divSolution"></div>
</body>
</html>
You need to write (or use) an operator-precedence parser.
The idea is to turn the equation into a tree, e.g.
x + 3 = 3x - 2
Is really the structure
=
/ \
+ -
/ \ / \
x 3 * 2
/ \
3 x
Where each operator describes an operation between two "branches" of the tree. Using a javascript object it shouldn't be difficult to create the structure:
function tree(lterm,op,rterm) {
t.operator = op;
t.left = lterm;
t.right = rterm;
return t;
}
expression = tree("x", "/", tree("x","+",3) ); // x / (x+3)
Then by manipulating the tree you can resolve the equation, or carry out calculations. To evaluate an expression (with no unknowns), you run through the tree starting at the terminals, and upwards from intersection to intersection. You can replace a section of the tree with a result, or annotate it with a result - add a result variable to the tree object.
Here are some useful methods to include in a tree class:
getLeft
getRight
prettyPrint
evaluate
evaluate("x",5) // x=5, now evaluate
...
It's not just linear operations that can be "parsed" this way. Better parsers will have a list of operators that includes =*/+- but also unary operators: - ( ) sin cos...
I haven't used an operator-precedence parser in javascript, but some must exist prewritten. Surely a kind soul on this site will add a good link or two to my answer.
BTW, the tree approach has many applications. In a spreadsheet:
A2 = A1+B1
In a boolean solver:
A = not (B or C)
C = true
In XML parsing:
<main>
<part>A</part>
<part>B</part>
</main>
I have defined two functions:
getTotalX() : It will give you the count of x for any input string.
getTotalScalars() : It will give you the total of scalars (numbers).
And finally, your updated code (it still does only addition and subtraction):
<script>
window.onload = function() {
// Total Xs on each side of equation
// Example problem: 5x + 2 = 10 - 2x
var leftSideXTotal = 0; // 5
var rightSideXTotal = 0; // -2
// Total integers on each side of equation
// Example problem: 5x + 2 = 10 - 2x
var leftSideIntTotal = 0; // 2
var rightSideIntTotal = 0; // 10
// Enter a math problem to solve
var problem = "5x + 2 = 10 - 2x";
// Remove all spaces in problem
// Example problem: 5x + 2 = 10 - 2x
problem = problem.replace(/\s/g,''); // 5x+2=10-2x
// Add + signs in front of all - signs
// Example problem: 5x + 2 = 10 - 2x
problem = problem.replace(/-/gi, "+-"); // 5x+2=10+-2x
// Split problem into left and right sides
// Example problem: 5x + 2 = 10 - 2x
var problemArray = problem.split("=");
var problemLeftSide = problemArray[0]; // 5x+2
var problemRightSide = problemArray[1]; // 10+-2x
leftSideXTotal = getTotalX(problemLeftSide);
leftSideIntTotal = getTotalScalars(problemLeftSide);
rightSideXTotal = getTotalX(problemRightSide);
rightSideIntTotal = getTotalScalars(problemRightSide);
// Compute
var totalXs = (leftSideXTotal - rightSideXTotal)
var totalIntegers = (rightSideIntTotal - leftSideIntTotal)
var solution = (totalIntegers / totalXs)
// Display solution
document.getElementById("divSolution").innerText = solution;
// Find the total number of X in the string
function getTotalX(data) {
data = data.replace(/\s/g,'');
xCount = 0;
if(data.indexOf('x') != -1) {
if (data.indexOf('+') != -1) {
data = data.split('+');
for(var i = 0; i < data.length; i++) {
xCount += getTotalX(data[i]);
}
} else if (data.indexOf('-') != -1) {
data = data.split('-');
// Single negative
if(data[0] == "") {
xCount -= getTotalX(data[1]);
} else {
xCount += getTotalX(data[0]);
for(var i = 1; i < data.length; i++) {
xCount -= getTotalX(data[i]);
}
}
} else {
xCount = parseInt(data.split('x')[0]);
}
}
return xCount;
}
// Find the total of scalars
function getTotalScalars(data) {
data = data.replace(/\s/g,'');
intCount = 0;
if (data.indexOf('+') != -1) {
data = data.split('+');
for(var i = 0; i < data.length; i++) {
intCount += getTotalScalars(data[i]);
}
} else if (data.indexOf('-') != -1) {
data = data.split('-');
// Single negative
if(data[0] == "") {
intCount -= getTotalScalars(data[1]);
} else {
intCount += getTotalScalars(data[0]);
for(var i = 1; i < data.length; i++) {
intCount -= getTotalScalars(data[i]);
}
}
} else {
if(data.indexOf('x') == -1) {
intCount = parseInt(data.split('x')[0]);
} else {
intCount = 0;
}
}
return intCount;
}
}
</script>

Javascript Fibonacci nth Term Optimization

I've become interested in algorithms lately, and the fibonacci sequence grabbed my attention due to its simplicity.
I've managed to put something together in javascript that calculates the nth term in the fibonacci sequence in less than 15 milliseconds after reading lots of information on the web. It goes up to 1476...1477 is infinity and 1478 is NaN (according to javascript!)
I'm quite proud of the code itself, except it's an utter monster.
So here's my question:
A) is there a faster way to calculate the sequence?
B) is there a faster/smaller way to multiply two matrices?
Here's the code:
//Fibonacci sequence generator in JS
//Cobbled together by Salty
m = [[1,0],[0,1]];
odd = [[1,1],[1,0]];
function matrix(a,b) {
/*
Matrix multiplication
Strassen Algorithm
Only works with 2x2 matrices.
*/
c=[[0,0],[0,0]];
c[0][0]=(a[0][0]*b[0][0])+(a[0][1]*b[1][0]);
c[0][1]=(a[0][0]*b[0][1])+(a[0][1]*b[1][1]);
c[1][0]=(a[1][0]*b[0][0])+(a[1][1]*b[1][0]);
c[1][1]=(a[1][0]*b[0][1])+(a[1][1]*b[1][1]);
m1=(a[0][0]+a[1][1])*(b[0][0]+b[1][1]);
m2=(a[1][0]+a[1][1])*b[0][0];
m3=a[0][0]*(b[0][1]-b[1][1]);
m4=a[1][1]*(b[1][0]-b[0][0]);
m5=(a[0][0]+a[0][1])*b[1][1];
m6=(a[1][0]-a[0][0])*(b[0][0]+b[0][1]);
m7=(a[0][1]-a[1][1])*(b[1][0]+b[1][1]);
c[0][0]=m1+m4-m5+m7;
c[0][1]=m3+m5;
c[1][0]=m2+m4;
c[1][1]=m1-m2+m3+m6;
return c;
}
function fib(n) {
mat(n-1);
return m[0][0];
}
function mat(n) {
if(n > 1) {
mat(n/2);
m = matrix(m,m);
}
m = (n%2<1) ? m : matrix(m,odd);
}
alert(fib(1476)); //Alerts 1.3069892237633993e+308
The matrix function takes two arguments: a and b, and returns a*b where a and b are 2x2 arrays.
Oh, and on a side note, a magical thing happened...I was converting the Strassen algorithm into JS array notation and it worked on my first try! Fantastic, right? :P
Thanks in advance if you manage to find an easier way to do this.
Don't speculate, benchmark:
edit: I added my own matrix implementation using the optimized multiplication functions mentioned in my other answer. This resulted in a major speedup, but even the vanilla O(n^3) implementation of matrix multiplication with loops was faster than the Strassen algorithm.
<pre><script>
var fib = {};
(function() {
var sqrt_5 = Math.sqrt(5),
phi = (1 + sqrt_5) / 2;
fib.round = function(n) {
return Math.floor(Math.pow(phi, n) / sqrt_5 + 0.5);
};
})();
(function() {
fib.loop = function(n) {
var i = 0,
j = 1;
while(n--) {
var tmp = i;
i = j;
j += tmp;
}
return i;
};
})();
(function () {
var cache = [0, 1];
fib.loop_cached = function(n) {
if(n >= cache.length) {
for(var i = cache.length; i <= n; ++i)
cache[i] = cache[i - 1] + cache[i - 2];
}
return cache[n];
};
})();
(function() {
//Fibonacci sequence generator in JS
//Cobbled together by Salty
var m;
var odd = [[1,1],[1,0]];
function matrix(a,b) {
/*
Matrix multiplication
Strassen Algorithm
Only works with 2x2 matrices.
*/
var c=[[0,0],[0,0]];
var m1=(a[0][0]+a[1][1])*(b[0][0]+b[1][1]);
var m2=(a[1][0]+a[1][1])*b[0][0];
var m3=a[0][0]*(b[0][1]-b[1][1]);
var m4=a[1][1]*(b[1][0]-b[0][0]);
var m5=(a[0][0]+a[0][1])*b[1][1];
var m6=(a[1][0]-a[0][0])*(b[0][0]+b[0][1]);
var m7=(a[0][1]-a[1][1])*(b[1][0]+b[1][1]);
c[0][0]=m1+m4-m5+m7;
c[0][1]=m3+m5;
c[1][0]=m2+m4;
c[1][1]=m1-m2+m3+m6;
return c;
}
function mat(n) {
if(n > 1) {
mat(n/2);
m = matrix(m,m);
}
m = (n%2<1) ? m : matrix(m,odd);
}
fib.matrix = function(n) {
m = [[1,0],[0,1]];
mat(n-1);
return m[0][0];
};
})();
(function() {
var a;
function square() {
var a00 = a[0][0],
a01 = a[0][1],
a10 = a[1][0],
a11 = a[1][1];
var a10_x_a01 = a10 * a01,
a00_p_a11 = a00 + a11;
a[0][0] = a10_x_a01 + a00 * a00;
a[0][1] = a00_p_a11 * a01;
a[1][0] = a00_p_a11 * a10;
a[1][1] = a10_x_a01 + a11 * a11;
}
function powPlusPlus() {
var a01 = a[0][1],
a11 = a[1][1];
a[0][1] = a[0][0];
a[1][1] = a[1][0];
a[0][0] += a01;
a[1][0] += a11;
}
function compute(n) {
if(n > 1) {
compute(n >> 1);
square();
if(n & 1)
powPlusPlus();
}
}
fib.matrix_optimised = function(n) {
if(n == 0)
return 0;
a = [[1, 1], [1, 0]];
compute(n - 1);
return a[0][0];
};
})();
(function() {
var cache = {};
cache[0] = [[1, 0], [0, 1]];
cache[1] = [[1, 1], [1, 0]];
function mult(a, b) {
return [
[a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1]],
[a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1]]
];
}
function compute(n) {
if(!cache[n]) {
var n_2 = n >> 1;
compute(n_2);
cache[n] = mult(cache[n_2], cache[n_2]);
if(n & 1)
cache[n] = mult(cache[1], cache[n]);
}
}
fib.matrix_cached = function(n) {
if(n == 0)
return 0;
compute(--n);
return cache[n][0][0];
};
})();
function test(name, func, n, count) {
var value;
var start = Number(new Date);
while(count--)
value = func(n);
var end = Number(new Date);
return 'fib.' + name + '(' + n + ') = ' + value + ' [' +
(end - start) + 'ms]';
}
for(var func in fib)
document.writeln(test(func, fib[func], 1450, 10000));
</script></pre>
yields
fib.round(1450) = 4.8149675025003456e+302 [20ms]
fib.loop(1450) = 4.81496750250011e+302 [4035ms]
fib.loop_cached(1450) = 4.81496750250011e+302 [8ms]
fib.matrix(1450) = 4.814967502500118e+302 [2201ms]
fib.matrix_optimised(1450) = 4.814967502500113e+302 [585ms]
fib.matrix_cached(1450) = 4.814967502500113e+302 [12ms]
Your algorithm is nearly as bad as uncached looping. Caching is your best bet, closely followed by the rounding algorithm - which yields incorrect results for big n (as does your matrix algorithm).
For smaller n, your algorithm performs even worse than everything else:
fib.round(100) = 354224848179263100000 [20ms]
fib.loop(100) = 354224848179262000000 [248ms]
fib.loop_cached(100) = 354224848179262000000 [6ms]
fib.matrix(100) = 354224848179261900000 [1911ms]
fib.matrix_optimised(100) = 354224848179261900000 [380ms]
fib.matrix_cached(100) = 354224848179261900000 [12ms]
There is a closed form (no loops) solution for the nth Fibonacci number.
See Wikipedia.
There may well be a faster way to calculate the values but I don't believe it's necessary.
Calculate them once and, in your program, output the results as the fibdata line below:
fibdata = [1,1,2,3,5,8,13, ... , 1.3069892237633993e+308]; // 1476 entries.
function fib(n) {
if ((n < 0) || (n > 1476)) {
** Do something exception-like or return INF;
}
return fibdata[n];
}
Then, that's the code you ship to your clients. That's an O(1) solution for you.
People often overlook the 'caching' solution. I once had to write trigonometry routines for an embedded system and, rather than using infinite series to calculate them on the fly, I just had a few lookup tables, 360 entries in each for each of the degrees of input.
Needless to say, it screamed along, at the cost of only about 1K of RAM. The values were stored as 1-byte entries, [actual value (0-1) * 16] so we could just do a lookup, multiply and bit shift to get the desired value.
My previous answer got a bit crowded, so I'll post a new one:
You can speed up your algorithm by using vanilla 2x2 matrix multiplication - ie replace your matrix() function with this:
function matrix(a, b) {
return [
[a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1]],
[a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1]]
];
}
If you care for accuracy and speed, use the caching solution. If accuracy isn't a concern, but memory consumption is, use the rounding solution. The matrix solution only makes sense if you want results for big n fast, don't care for accuracy and don't want to call the function repeatedly.
edit: You can even further speed up the computation if you use specialised multiplication functions, eliminate common subexpressions and replace the values in the existing array instead of creating a new array:
function square() {
var a00 = a[0][0],
a01 = a[0][1],
a10 = a[1][0],
a11 = a[1][1];
var a10_x_a01 = a10 * a01,
a00_p_a11 = a00 + a11;
a[0][0] = a10_x_a01 + a00 * a00;
a[0][1] = a00_p_a11 * a01;
a[1][0] = a00_p_a11 * a10;
a[1][1] = a10_x_a01 + a11 * a11;
}
function powPlusPlus() {
var a01 = a[0][1],
a11 = a[1][1];
a[0][1] = a[0][0];
a[1][1] = a[1][0];
a[0][0] += a01;
a[1][0] += a11;
}
Note: a is the name of the global matrix variable.
Closed form solution in JavaScript: O(1), accurate up for n=75
function fib(n){
var sqrt5 = Math.sqrt(5);
var a = (1 + sqrt5)/2;
var b = (1 - sqrt5)/2;
var ans = Math.round((Math.pow(a, n) - Math.pow(b, n))/sqrt5);
return ans;
}
Granted, even multiplication starts to take its expense when dealing with huge numbers, but this will give you the answer. As far as I know, because of JavaScript rounding the values, it's only accurate up to n = 75. Past that, you'll get a good estimate, but it won't be totally accurate unless you want to do something tricky like store the values as a string then parse those as BigIntegers.
How about memoizing the results that where already calculated, like such:
var IterMemoFib = function() {
var cache = [1, 1];
var fib = function(n) {
if (n >= cache.length) {
for (var i = cache.length; i <= n; i++) {
cache[i] = cache[i - 2] + cache[i - 1];
}
}
return cache[n];
}
return fib;
}();
Or if you want a more generic memoization function, extend the Function prototype:
Function.prototype.memoize = function() {
var pad = {};
var self = this;
var obj = arguments.length > 0 ? arguments[i] : null;
var memoizedFn = function() {
// Copy the arguments object into an array: allows it to be used as
// a cache key.
var args = [];
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
// Evaluate the memoized function if it hasn't been evaluated with
// these arguments before.
if (!(args in pad)) {
pad[args] = self.apply(obj, arguments);
}
return pad[args];
}
memoizedFn.unmemoize = function() {
return self;
}
return memoizedFn;
}
//Now, you can apply the memoized function to a normal fibonacci function like such:
Fib = fib.memoize();
One note to add is that due to technical (browser security) constraints, the arguments for memoized functions can only be arrays or scalar values. No objects.
Reference: http://talideon.com/weblog/2005/07/javascript-memoization.cfm
To expand a bit on Dreas's answer:
1) cache should start as [0, 1]
2) what do you do with IterMemoFib(5.5)? (cache[5.5] == undefined)
fibonacci = (function () {
var FIB = [0, 1];
return function (x) {
if ((typeof(x) !== 'number') || (x < 0)) return;
x = Math.floor(x);
if (x >= FIB.length)
for (var i = FIB.length; i <= x; i += 1)
FIB[i] = FIB[i-1] + FIB[i-2];
return FIB[x];
}
})();
alert(fibonacci(17)); // 1597 (FIB => [0, 1, ..., 1597]) (length = 17)
alert(fibonacci(400)); // 1.760236806450138e+83 (finds 18 to 400)
alert(fibonacci(1476)); // 1.3069892237633987e+308 (length = 1476)
If you don't like silent errors:
// replace...
if ((typeof(x) !== 'number') || (x < 0)) return;
// with...
if (typeof(x) !== 'number') throw new TypeError('Not a Number.');
if (x < 0) throw new RangeError('Not a possible fibonacci index. (' + x + ')');
Here is a very fast solution of calculating the fibonacci sequence
function fib(n){
var start = Number(new Date);
var field = new Array();
field[0] = 0;
field[1] = 1;
for(var i=2; i<=n; i++)
field[i] = field[i-2] + field[i-1]
var end = Number(new Date);
return 'fib' + '(' + n + ') = ' + field[n] + ' [' +
(end - start) + 'ms]';
}
var f = fib(1450)
console.log(f)
I've just written my own little implementation using an Object to store already computed results. I've written it in Node.JS, which needed 2ms (according to my timer) to calculate the fibonacci for 1476.
Here's the code stripped down to pure Javascript:
var nums = {}; // Object that stores already computed fibonacci results
function fib(n) { //Function
var ret; //Variable that holds the return Value
if (n < 3) return 1; //Fib of 1 and 2 equal 1 => filtered here
else if (nums.hasOwnProperty(n)) ret = nums[n]; /*if the requested number is
already in the object nums, return it from the object, instead of computing */
else ret = fib( n - 2 ) + fib( n - 1 ); /* if requested number has not
yet been calculated, do so here */
nums[n] = ret; // add calculated number to nums objecti
return ret; //return the value
}
//and finally the function call:
fib(1476)
EDIT: I did not try running this in a Browser!
EDIT again: now I did. try the jsfiddle: jsfiddle fibonacci Time varies between 0 and 2ms
Much faster algorithm:
const fib = n => fib[n] || (fib[n-1] = fib(n-1)) + fib[n-2];
fib[0] = 0; // Any number you like
fib[1] = 1; // Any number you like

Categories

Resources