Javascript Greedy Algorithm Making Change - javascript

I'm working on the classic "making change" problem, which is highly documented in plenty of other languages, but there's not much out there for it in Javascript. So far, I have this:
var total = $('#total').val();
var coins = [];
function makeChange(total, coins) {
var remainder = 0;
if (total % 0.25 < total) {
coins[3] = parseInt(total / 0.25);
remainder = total % 0.25;
total = remainder;
}
if (total % 0.10 < total) {
coins[2] = parseInt(total / 0.10);
remainder = total % 0.10;
total = remainder;
}
if (total % 0.05 < total) {
coins[1] = parseInt(total / 0.05);
remainder = total % 0.05;
total = remainder;
}
coins[0] = parseInt(total / 0.01);
}
function showChange(coins) {
if (coins[3] > 0) {
$('.quarter').html(coins[3] + " quarter(s).");
}
if (coins[2] > 0) {
$('.dime').html(coins[2] + " dime(s).");
}
if (coins[1] > 0) {
$('.nickel').html(coins[1] + " nickel(s).");
}
if (coins[0] > 0) {
$('.penny').html(coins[0] + " pennies.");
}
}
makeChange(total, coins);
showChange(coins);
However, this seems awfully repetitive and I'm finding that with certain values, it's a penny off. How can I make it more accurate and concise?

I'm finding that with certain values, it's a penny off.
Probably due to floating-point issues. And you shouldn't use parseInt to convert a number - it's meant for strings.
this seems awfully repetitive
A loop, with a data structure that represent the different coins will help. You already did something like that for your result: coins is an array, not 4 different variables.
function makeChange(total, values) {
var coins = [],
epsilon = 1e-5; // this is wrong in general!
// assume values are ascending, so we loop backwards
for (var i=values.length; i--; ) {
coins[i] = Math.floor(total / values[i].val + epsilon);
total %= values[i].val;
}
return coins;
}
function showChange(coins, values) {
for (var i=values.length; i--; ) {
var el = $(values[i].sel);
if (coins[i] > 0) {
el.html(coins[i] + " "+values[i].name+".");
} else {
el.empty();
}
}
}
var values = [
{val:0.01, sel:'.penny', name:"pennies"},
{val:0.05, sel:'.nickel', name:"nickel(s)"},
{val:0.10, sel:'.dime', name:"dime(s)"},
{val:0.25, sel:'.quarter', name:"quarter(s)"}
];
showChange(makeChange(parseFloat($('#total').val()), values), values);

Your best bet to avoid rounding problems is to just multiple your total by 100 to convert your dollar amount into all pennies, then do you conversion. For example:
function makeChange(total, coins) {
var remainder = 0;
total = Math.round(total * 100);
coins[3] = Math.floor(total / 25);
remainder = total - coins[3] * 25;
total = remainder;
coins[2] = Math.floor(total / 10);
remainder = total - coins[2] * 10;
total = remainder;
coins[1] = Math.floor(total / 5);
remainder = total - coins[1] * 5;
total = remainder;
coins[0] = total;
}
http://jsfiddle.net/t14cwdph/4/
For making your code easier to manage - see #Bergi's answer.

Related

Get from one number to another number

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);
}

Bet calculator not working when using negative money line

Good day all,
I am trying to create a simple small bet calculator with the following line of codes:
function betCalculator(moneyLine) {
var odds;
var betAmount = 500;
if (moneyLine > 0) {
odds = (moneyLine / 100) + 1;
} else {
odds = (100 / moneyLine) + 1;
}
return parseFloat((odds * betAmount).toFixed(2));
}
console.log(betCalculator(130)); // Result / Expected = 1150.00
But if I change the Money Line to negative, say -130, as shown below:
function betCalculator(moneyLine) {
var odds;
var betAmount = 500;
if (moneyLine > 0) {
odds = (moneyLine / 100) + 1;
} else {
odds = (100 / moneyLine) + 1;
}
return parseFloat((odds * betAmount).toFixed(2));
}
console.log(betCalculator(-130)); // Result I am getting = 115.38 // Expected = 884.62
What I am trying to do is:
if (moneyLine > 0) {
odds = (moneyLine / 100) + 1;
But
if (moneyLine < 0) {
odds = (100 / moneyLine) + 1;
Any help would be appreciated.
Regards,
ZeManel
For the calculation for the negative moneyLine, you want to change the sign, or use the absolute value, when you move it to the denominator.
If you change odds = (100 / moneyLine) + 1; in your else condition to odds = (100 / Math.abs(moneyLine)) + 1;, then you will get your expected answer.

Get reverse of an equation - JavaScript

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)

How to get percent from an binary int, with every bit representing x percent?

I have an binary bitmap with 10bits. Every bit represents 10%. Is there any simple math function to get the sum of percent from the bitmap?
Sample
0000000000 = 0%
0000000001 = 10%
1000000000 = 10%
0000100000 = 10%
1000000001 = 20%
0000000011 = 20%
0000110000 = 20%
0010000010 = 20%
1010000010 = 30%
Be aware this is just an example of how the bits are activated. The number i have actually is an int such as 0,1 to 1023.
You don't have to use a loop. You don't have to do math. Just do it like this:
var number = 1000100010;
alert(number.toString().split("1").length - 1);
//A little more deep:
var number2 = 1100100000;
alert((number2.toString().split("1").length - 1) * 10 + "%");
Use a loop:
function count_1bits(n, bitsize) {
var count = 0;
for (var i = 0; i < bitsize; i++) {
count += (n & 1); // test low-order bit
n >>= 1; // shift number down 1 bit
}
return count;
}
var pct = count_1bits(bitmap, 10)*100;
You can count the bits with a function like this, then just multiply by 10 to get it as percent:
function countBits(n, cnt) {
if (cnt == 1) return n & 1;
var half = Math.floor(cnt / 2);
return countBits(n >> half, cnt - half) + countBits(n, half);
}
n = 1023;
document.write(countBits(n, 10) * 10 + '%');
Try doing something like
x = '0000100011';
function binTopre(x) {
c = (x.match(/1/g)||[]).length;
return (c*10) + '%';
}
console.log(binTopre(x));//30%

Generate incremented random numbers between 0 to 100 every second where new number should be greater than the previous nymber

I need a javascript where with every second call i get a random number between 0-100 where current number should be greater than previous number. I wrote a code to get random numbers every second but stuck at generating it with increment.
<script>
var span = document.getElementsByTagName("span")[3];
var i = 100;
(function randNumber() {
var a = Math.floor((Math.random() * i) + 1);
span.innerHTML = "RandNumber: " + a;
setTimeout( randNumber, 1000);
})();
Note : The numbers should generate randomly.
example result may be : 2,5,7,8,22,23,34,56,78,88.....
You should not create a new random number between zero and your maximum (i), but just between the last created number (lastNumber) and max (i).
Also you might want to stop, when the random numbers reached the maximum.
var span = document.getElementsByTagName("span")[3],
i = 100,
lastNumber = 0;
function randNumber() {
lastNumber = lastNumber + Math.floor( Math.random() * (i - lastNumber) + 1 );
span.innerHTML = lastNumber;
if( lastNumber < i ) {
setTimeout( randNumber, 1000 );
}
}
randNumber();
AS for the comments and the requirement of a minimum amount of steps until reaching the maximum:
In each iteration, just increase you number by a random value between 1 and ((max - min) / steps. It is pretty much the same code as above.
var span = document.getElementsByTagName("span")[3],
max = 100,
min = 0,
lastNumber = 0,
minSteps = 30;
// how wide can the average step be at most?
var stepWidth = (max - min) / minSteps;
function randNumber() {
lastNumber = lastNumber + Math.floor( Math.random() * stepWidth + 1 );
span.innerHTML = lastNumber;
if( lastNumber < max ) {
setTimeout( randNumber, 1000 );
}
}
randNumber();
If you extract the "between two numbers" logic into its own function like this, this becomes a lot easier. This way, you just generate a number between your last generated number and your maximum.
var max = 100
var last_number = 1
var interval_id = setInterval(function(){
last_number = get_random_number_greater_between(last_number, max)
span.innerHTML = "RandNumber: " + last_number;
if(last_number >= max){
window.clearInterval(interval_id)
}
}, 1000)
function get_random_number_greater_between(low, high){
return Math.floor(Math.random() * (high - low + 1) + low);
}
Try this:
// generate a random number from 0 to max - 1.
function rand(max) {
return Math.floor(max * Math.random());
}
// generate a random number from min to max.
function range(min, max) {
return min + rand(1 + max - min);
}
// the loop function
function next(n, callback) {
var m = range(n, 100);
if (m < 100) setTimeout(next, 1000, m + 1, callback);
callback(m);
}
var span = document.getElementById("rand");
next(0, function (m) {
span.innerHTML = "Random number: " + m;
});
<span id="rand"></span>

Categories