Let's say I have this formula, for example:
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
for (var i = 1; i < 100; i++) {
console.log(`Level ${i}: ${getExperience(i)}`);
}
To get the experience needed for level 50, you'd do: getExperience(50).
But, how would you reverse that and get the LEVEL needed for experience? So, getLevel(20010272) would output 50.
Short answer
You can use 4.328085 * Math.log(0.00519842 * xp + 1.259921045) as a very good approximation of the corresponding level.
If you need an exact value, you could iterate over all levels until you find the desired range, as in this answer.
Long answer
Slightly modified function
I don't think it's possible to find an exact, closed-form expression for the inverse of this function. It should be possible if you modify getExperience(level) a bit, though.
First, you can notice that x grows much slower than 2 ** (x / 3).
Then, Math.floor doesn't have much influence over large numbers.
So let's remove them! Here's the slightly modified function:
function getExperienceEstimate(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += 200 * (2 ** (x / 3));
}
return a / 4;
}
The advantage of this method is that it's now a geometric series, so it's possible to calculate the sum directly, without any loop:
function getExperienceEstimate(level) {
let a = 50;
let r = 2 ** (1 / 3);
return a * (r**level - r) / (r - 1);
};
getExperienceEstimate(50) returns 20011971.993575357, which is only 0.0015% smaller than getExperience(50).
Inverse function
According to Wolfram Alpha, here's the inverse function of getExperienceEstimate:
function getLevelEstimate(xp){
let a = 50;
let r = 2 ** (1 / 3);
return Math.log(xp * (r - 1) / a + r) / Math.log(r);
};
With some minor precision loss, you can simplify it further:
function getLevelEstimate(xp){
return 4.328085 * Math.log(0.00519842 * xp + 1.259921045)
};
It's only an estimate, but it works pretty well and doesn't require any loop!
Test
For 20012272 XP, the approximate inverse function returns 50.00006263463371, which should be a good starting point if you want to find the exact result.
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
function getLevelEstimate(xp){
return 4.328085 * Math.log(0.00519842 * xp + 1.259921045)
};
for (var i = 1; i < 100; i++) {
console.log(`Level ${i} (XP = ${getExperience(i)}). Estimated level : ${getLevelEstimate(getExperience(i))}`);
}
You can use a binary search algorithm to avoid to loop over all possibilities.
Here is an example that I have adapted to your case.
You first need to create an array to map all your level => experience, this action should be done only ONCE, then you never have to do it again.
As you can see in my example, even with 1000 levels, you never have to iterate more than 9 times whatever level you are trying to find.
// You first have to create an array with all your levels.
// This has to be done only ONCE because it's an expensive one!
const list = [];
for (let i = 1; i <= 1000; i++) {
list[i] = getExperience(i);
}
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
function getLevel(value) {
// initial values for start, middle and end
let start = 0
let stop = list.length - 1
let middle = Math.floor((start + stop) / 2)
let iterations = 0;
// While the middle is not what we're looking for and the list does not have a single item.
while (list[middle] !== value && start < stop) {
iterations++;
if (value < list[middle]) {
stop = middle - 1
} else {
start = middle + 1
}
// Recalculate middle on every iteration.
middle = Math.floor((start + stop) / 2)
}
console.log(`${value} is Level ${middle} (Result found after ${iterations} iterations)`);
return middle;
}
// Then you can search your level according to the experience
getLevel(0);
getLevel(72);
getLevel(20010272);
getLevel(getExperience(50));
getLevel(33578608987644589722);
A brute-force (but inelegant) solution would be to just call getExperience for levels until you reach a level that requires more experience than the passed exp:
function getLevel(exp) {
if (exp === 0) return 0;
let level = 0;
let calcExp = 0;
while (exp > calcExp) {
calcExp = getExperience(level);
if (calcExp > exp) break;
level++;
}
return level - 1;
}
console.log(getLevel(20012272)); // experience required for 50 on the dot
console.log(getLevel(20012270));
console.log(getLevel(20012274));
console.log(getLevel(0));
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
You can use binary search to locate level value faster - in 7 steps max.
(while I doubt that gain is significant for length 100 list)
Related
Given a number, say 1.2, is there a simple way in JavaScript, to get to -1.5 in steps of .1. Or from say -50.3 to 12.3.
I'm trying to figure out if there is an easier way of doing this then writing a bunch of complex if statements.
To avoid accumulating floating point inaccuracies (see Is floating point math broken?) it would be best to multiply your numbers by 10 and then use steps of 1. You can then divide by 10 when generating the results.
Then you just need two for loops: one for counting up, the other for counting down.
function range(start, end) {
start = Math.round(start * 10);
end = Math.round(end * 10);
result = [];
if (start > end) { // counting down
for (let i = start; i >= end; i--) {
result.push(i / 10);
}
} else { // counting up
for (let i = start; i <= end; i++) {
result.push(i / 10);
}
}
return result;
}
console.log(range(1.2, -1.5));
console.log(range(-50.3, 12.3));
for (let number = -10.5; number < 10.5; number += 0.1) {
realPart = number - (number - Math.floor(number));
decimalPart = Math.floor((number - Math.floor(number)) * 10) / 10;
console.log( realPart + decimalPart);
}
// or
for (let number = 10.5; number > -10.5; number -= 0.1) {
realPart = number - (number - Math.floor(number));
decimalPart = Math.floor((number - Math.floor(number)) * 10) / 10;
console.log( realPart + decimalPart);
}
Please help me to write a function to compute the square root of positive real numbers using the formula:
x i+1 = (1/2) * (xi + (A / x1)),
where 'A' - input real number.
On the zero iteration next statements have been taken x0 = A
The error should be at least 10-6
Output
sqrt (2) = 1.414
sqrt (9) = 3
sqrt (25) = 5
You could take xi (x) and the new value of xi + 1 (x1) and check if the values are equal. Then end the series and return that value.
For starting, you need an apporopriate value like the half of the given value.
function sqrt(a) {
var x,
x1 = a / 2;
do {
x = x1;
x1 = (x + (a / x)) / 2;
} while (x !== x1);
return x;
}
console.log(sqrt (2)); // 1.414
console.log(sqrt (9)); // 3
console.log(sqrt (25)); // 5
You can also use bisection - a more general method for solving problems:
var sqrt = function(n) {
if (n<0) {
throw "are you kidding?! we are REAL here.";
}
if (n === 0) {
return 0;
}
var bisect = function(l,r) {
var avg = (l+r)/2;
if (r-l<0.00000001) {
return (l+r)/2;
}
if (avg*avg > n) {
return bisect(l, avg);
} else if (avg*avg < n) {
return bisect(avg, r);
}
}
return bisect(0, n < 1 ? 1 : n);
}
While I was solving a question saying "add odd numbers from 1 to 20", I coded this:
var i, sum=0;
for (i=2; i<=20; i*2){
sum=sum+i;
}
document.write(sum);
When I launched it through a browser, it did not work. However, when I fixed i*2 into i+=2, it worked.
What am I missing? Am I not able to use *(multiplier) in For Loops?
If you need to add odd numbers from 1 to 20, then you need i+=2 as the third parameter of the for and need to initialize the variable to 1 to get the correct result:
var sum = 0;
for (var i = 1; i <= 20; i += 2) {
sum += i;
}
When you have
i += 2
2 is added to i and the result is stored into i. When you tried
var i, sum=0;
for (i=2; i<=20; i*2){
sum=sum+i;
}
i*2 calculates the value which is twice as big as i, but it will not change the value of i, so this would "work" instead:
var i, sum=0;
for (i=2; i<=20; i*=2){
sum=sum+i;
}
where
i *= 2
not only calculates the value twice as big as i, but stores the result into i as well. However, even though this will run, the result will not be correct, since you are using the wrong formula.
Also, you can calculate the result without using a for:
1 + 2 + ... + n = n * (n + 1) / 2
Assuming that n is pair: and since we know that we are "missing" half the numbers and all the pair numbers are bigger exactly with 1 than the previous impair numbers, we can subtract half of the sequence
n * (n + 1) / 2 - n / 2 = (n * (n + 1) - n) / 2 = (n * (n + 1 - 1)) /
2 = n * n / 2
and now we have exactly the double value of what we need, so the final formula is:
sum = n * n / 4;
Let's make this a function
function getOddSumUpTo(limit) {
if (limit % 2) limit ++;
return limit * limit / 4;
}
and then:
var sum = getOddSumUpTo(20);
Note that we increment limit if it is odd.
The issue is that you're not updating the value of the i in the for loop.
I want add odd numbers from 1 to 20
Then you need to change the initial value of i to 1.
var i, sum = 0;
for (i = 1; i <= 20; i += 2){
sum += i;
}
document.write(sum);
Also, you can find the sum of odd numbers from 1 to 20 by using a formula.
n = 20;
console.log(n % 2 == 0 ? (n * n)/ 4 : ((n + 1) * (n + 1))/4);
You can you just have to do it simillary to what you've written about sum.
You used there i += 2 and not i + 2.
The same way just change i * 2 to i *= 2.
Here is an working example
var i, sum = 0;
for (i = 2; i <= 20; i *= 2) {
console.log(i);
sum += i;
}
document.write(sum);
But a couple of things here.
First of all you wrote
add odd numbers from 1 to 20
and in all your examples you use sum on even numbers.
Secondly, by multiplying you will not achieve your desired goal (as you can see in a snippet above in a console)
So to actually
add odd numbers from 1 to 20
you should do it like this:
var i, sum = 0;
for (i = 1; i <= 20; i += 2) {
console.log(i);
sum += i;
}
document.write(sum);
EDIT
If you want to add even numbers you still can't use multiplying.
Why? Simply because you said yourself that you want a sum of numbers.
So let's say that we start with 2.
If we multiply it by 2 it has the value 4 which is fine.
But now look what happens in the next iteration. Our variable i which has the value 4 is multiplied by 2 and now its new value is 8. So what about 6?
Next iteration multiply 8 by 2 and its new value is 16.
Do you see where this is going?
And when you use i += 2 instead of i *= 2?
So if we start with 2 and than we add 2 its new value is 4.
In next iteration we add 2 to 4 and we have 6.
And so on.
If you want to test it, here is an example with multiplying and adding.
Pay attention to console logs
var i;
console.log("Multiplying");
for (i = 2; i <= 20; i *= 2) {
console.log("i value is: " + i);
}
console.log("Adding");
for (i = 2; i <= 20; i += 2) {
console.log("i value is: " + i);
}
What you are looking is this :
let sum = 0;
for(var i = 2; i <= 20; i += 2){
sum += i;
}
document.write(sum)
Another take on this :
// set to n (what you want). Set to n + 1
var N = 21;
// The main driver code create an array from 0-(N-1) and removes all even nums
let a = Array.apply(null, {length: N}).map(Number.call, _ => +_).filter(_=>_%2)
// console.log the array
console.log(a)
You can use whatever expression in loop header, even this is a valid for loop statement for (;;) which simply runs forever (equivalent to while(true)).
Problem is that you are not updating the i counter in for (i=2; i<=20; i*2) so the i will stays the same throughout the execution of the loop.
If you change it to for (i=2; i<=20; i = i*2) or for (i=2; i<=20; i *=2) then it will work.
It is the same as if you did
let i = 1;
i * 2;
console.log(i);
i = i * 2;
console.log(i);
The first i * 2 doesn't update the i while the second one does.
You can also translate the for loop into while loop to see the error more clearly.
// wrong
let i = 1;
while(i <= 20) {
i * 2;
// do something
}
// right
let i = 1;
while(i <= 20) {
i = i * 2 // or i *= 2
// do something
}
Just a side note, if you wanted to perform sum on more types of sequences efficiently than you could use a generator based approach and write your sum function and describe each type of a sequence with a generator function.
function *generateOdd(start, end) {
for (let i = start; i <= end; i++) {
if (i % 2 === 1) { yield i; }
}
}
function *generateEven(start, end) {
for (let i = start; i <= end; i++) {
if (i % 2 === 0) { yield i; }
}
}
function sumNums(gen, start, end) {
const generator = gen(start, end);
let res = 0;
let item = generator.next();
while (!item.done) {
res += item.value;
item = generator.next();
}
return res;
}
console.log(sumNums(generateOdd, 0, 20));
console.log(sumNums(generateEven, 0, 20));
/* sum of the Odd number using loop */
function sumOfOddNumbers(n){
let sum= 0;
for(let i = 1; i <= n; i++) {
if(i % 2 !== 0){
sum = sum + i;
}
}
return sum;
}
// 567 = 1+3+5+7+9+11+13+15+17+19+21+23+25+27+29+31+33+35+37+39+41+43+45+47
let n = 47;
let sum = sumOfOddNumbers(47);
alert('sumOfOddNumbers(' + n + ') = ' + sum);
I was given a quiz and I had gotten the answer wrong and It's been bugging me ever since so I thought I'd ask for your thoughts
I needed to optimise the following function
function sumOfEvenNumbers(n) {
var sum = 0;
for(let i = 2; i < n;i++){
if(i % 2 == 0) sum += i;
}
return sum;
}
console.log(sumOfEvenNumbers(5));
I came up with
function sumOfEvenNumbers(n) {
var sum = 0;
while(--n >= 2) sum += n % 2 == 0? n : 0
return sum;
}
console.log(sumOfEvenNumbers(5));
What other ways were there?
It's a bit of a math question. The sum appears to be the sum of an arithmitic sequence with a common difference of 2. The sum is:
sum = N * (last + first) / 2;
where N is the number of the numbers in the sequence, last is the last number of those numbers, and first is the first.
Translated to javascript as:
function sumOfEvenNumbers(n) {
return Math.floor(n / 2) * (n - n % 2 + 2) / 2;
}
Because the number of even numbers between 2 and n is Math.floor(n / 2) and the last even number is n - n % 2 (7 would be 7 - 7 % 2 === 6 and 8 would be 8 - 8 % 2 === 8). and the first is 2.
Sum of n numbers:
var sum = (n * (n+1)) / 2;
Sum of n even numbers:
var m = Math.floor(n/2);
var sum = 2 * (m * (m+1) /2);
You can compute these sums using an arithmetic sum formula in constant time:
// Return sum of positive even numbers < n:
function sumOfEvenNumbers(n) {
n = (n - 1) >> 1;
return n * (n + 1);
}
// Example:
console.log(sumOfEvenNumbers(5));
Above computation avoids modulo and division operators which consume more CPU cycles than multiplication, addition and bit-shifting. Pay attention to the limited range of the bit-shifting operator >> though.
See e.g. http://oeis.org/A002378 for this and other formulas leading to the same result.
First thing is to eliminate the test in the loop:
function sumOfEvenNumbers(n) {
var sum = 0;
var halfN= Math.floor(n/2);
for(let i = 1; i < n/2;i++) {
sum += i;
}
return sum * 2;
}
Then we can observe that is just calculating the sum of all the integers less than a limit - and there is a formula for that (but actually formula is for less-equal a limit).
function sumOfEvenNumbers(n) {
var halfNm1= Math.floor(n/2)-1;
var sum = halfNm1 * (halfNm1+1) / 2;
return sum * 2;
}
And then eliminate the division and multiplication and the unnecessary addition and subtraction:
function sumOfEvenNumbers(n) {
var halfN= Math.floor(n/2);
return (halfN-1) * halfN;
}
Your solution computes in linear (O(N)) time.
If you use a mathematical solution, you can compute it in O(1) time:
function sum(n) {
let half = Math.ceil(n/2)
return half * (half + 1)
}
Because the question is tagged ecmascript-6 :)
const sumEven = x => [...Array(x + 1).keys()].reduce((a, b) => b % 2 === 0 ? a + b : a, 0);
// set max number
console.log(sumEven(10));
How can I make a function that calculates the factorial (or the gamma function) of decimal numbers in JavaScript? For example, how could I calculate 2.33!?
I might have found an existing solution...
It's an implementation of Lanczos method, I found it at the swedish wikipedia (http://sv.wikipedia.org/wiki/Gammafunktionen). It was written in python and says to be correct up to 15 decimals. I ported it to js, cross checked some random values against (http://www.efunda.com/math/gamma/findgamma.cfm).
http://jsfiddle.net/Fzy9C/
var g = 7;
var C = [0.99999999999980993, 676.5203681218851, -1259.1392167224028,771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];
function gamma(z) {
if (z < 0.5) return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));
else {
z -= 1;
var x = C[0];
for (var i = 1; i < g + 2; i++)
x += C[i] / (z + i);
var t = z + g + 0.5;
return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
}
}
(and ofcourse it does not support imaginary numbers, since js does not)
As an alternative to the other answers here, here's a much simpler approximation for the gamma function, proposed in 2007 by Gergő Nemes. (See the wikipedia page on Stirling's approximation).
This can be implemented directly in JavaScript in a single line:
function gamma(z) {
return Math.sqrt(2 * Math.PI / z) * Math.pow((1 / Math.E) * (z + 1 / (12 * z - 1 / (10 * z))), z);
}
You can see this in action on jsFiddle.
This is accurate to 8 digits for z > 8, but it is still accurate to a handful of digits for smaller z. It is not quite as accurate as Lanczos approximation, but it is simpler and also slightly faster.
Note that the gamma function and the factorial function are slightly different. The factorial function can be defined in terms of the gamma function thus:
function factorial(n) {
return gamma(n + 1);
}
This is not a trivial problem. There is not a simple closed-form formula for the gamma function. That said, there are some numerical approximations that should suit your needs.
The following answer will be using a technique called Lanczos approximation. The formula is as follows:
where g is an arbitrarily chosen constant that controls how accurate the approximation will be. For larger g, the approximation will be more accurate. Ag(z) is defined thus:
The hardest part is finding Ag(z), since pn is also defined with a complicated formula dependent on g.
I can't take too much credit for the following code, since I am just writing a port of the Python program on the wikipedia page.
function gamma(n) { // accurate to about 15 decimal places
//some magic constants
var g = 7, // g represents the precision desired, p is the values of p[i] to plug into Lanczos' formula
p = [0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];
if(n < 0.5) {
return Math.PI / Math.sin(n * Math.PI) / gamma(1 - n);
}
else {
n--;
var x = p[0];
for(var i = 1; i < g + 2; i++) {
x += p[i] / (n + i);
}
var t = n + g + 0.5;
return Math.sqrt(2 * Math.PI) * Math.pow(t, (n + 0.5)) * Math.exp(-t) * x;
}
}
and of course, by definition of the gamma function:
function factorial(n) {
return gamma(n + 1);
}
You can see this in action on jsFiddle.
Just to complete #apelsinapa answer to correct the calculation for an integer (we didn't get an integer solution when inputing an integer number).
#apelsinapa's great solution:
var g = 7;
var C = [0.99999999999980993, 676.5203681218851, -1259.1392167224028,771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];
function gamma(z) {
if (z < 0.5) return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));
else {
z -= 1;
var x = C[0];
for (var i = 1; i < g + 2; i++)
x += C[i] / (z + i);
var t = z + g + 0.5;
return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
}
}
And to get a correct answer for integer:
function factorialOfNumber(number) {
if (number % 1 != 0 || number<0){
return gamma(number + 1);
}
else {
if(number == 0) {
return 1;
}
for(var i = number; --i; ) {
number *= i;
}
return number;
}
}
Here's a version I wrote a few years ago ... a bit messy but tested :)
var
M_GAMMA = [76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5],
M_GAMMAS = 6;
function gammaFn(x) // Modified to JavaScript from "Numerical Recipies in C" by P. Mainwaring
{
var i = 0, n = ++x, tmp = x + 5.5, ser = 1.000000000190015;
for (tmp -= (x + 0.5) * Math.log(tmp); i < M_GAMMAS; ++i) ser += M_GAMMA [i] / ++n;
return Math.log(2.5066282746310005 * ser / x) - tmp;
}
function fact(x) { return x > 1 ? Math.exp(gammaFn(x)) : 1 }
function combin(n, k) { return (Math.exp(gammaFn(n) - gammaFn(n - k) - gammaFn(k)) + 0.5) | 0 } // Ms Excel COMBIN() n! / k!(n - k)!
n = 49; k = 6; alert(fact(n) + ' ' + fact(k) + ' ' + combin(n, k)); // Lottery odds! (13,983,816)
The gamma and gammaLn functions are then:
function gammaLn(x) { return gammaFn(--x) }
function gamma(x) { return Math.exp(gammaLn(x)) }
:-)
If you're just looking for the function to compute factorials of real numbers then you only need this bit of code from Lanczos approximation:
function = factorial(z) {
var g = 7;
var C = [0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];
var x = C[0];
for (var i = 1; i < g + 2; i++)
x += C[i] / (z + i);
var t = z + g + 0.5;
return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
}
Works great for negative numbers in addition to decimals.