I am attempting to calculate an investment management fee using a fee schedule. For example, let's say the investment amount is $5,000,000 and this is the fee schedule:
1,000,000 & Less: 1.00%
2,000,000: 0.75%
3,000,000: 0.50%
3,000,001+: 0.25%
Where dollars under 1,000,000 are charged 1.00% and dollars over $3,000,000 are charged 0.25%. I need to come up with the blended price using javascript (The answer in this case would be 0.55% or $27,500 in dollars).
How do you get the answer if the amount we're using as an example is $5,000,000 and the fee schedule above?
$1,000,000 * 0.0100 = $10,000 (Fee for first $1m)
$1,000,000 * 0.0075 = $7,500 (Fee for second $1m)
$1,000,000 * 0.0050 = $5,000 (Fee for third $1m)
$2,000,000 * 0.0025 = $5,000 (Everything over $3m)
So to manage the $5,000,000, the cost would be the total of the fees above ($27,500). And $27,500 / $5,000,000 = 0.55% which is our "blended fee".
The caveat, is not every fee schedule looks exactly like this. Some have different fees and limits.
What are the results of my research into this problem so far? I have not found a quality example of a function that could accomplish this on SO. It seems all tiered pricing calculations on SO only identify a single price for a given quantity of items, not a blended price though like I am trying to achieve here.
My fee schedule would potentially look something like this array:
fee_schedule: [
{ fee: 0.0100, limit: 1000000},
{ fee: 0.0075, limit: 2000000},
{ fee: 0.0050, limit: 3000000},
{ fee: 0.0025, limit: Infinity}
]
This is the direction I started down, but hit a wall in my head because I saw myself writing a ridiculously long function.
function determineFee(schedule, amount) {
schedule.forEach(tier => {
if (amount <= tier.limit) {
// is this a path I should go down?
}
}
//
}
You could build the delta of the last or zero and actual limit or if it is the last element take Infinity, for using the fee for the rest of the left over value.
Then make a check if the value is smaller then the delta. If true, calculate the fee for the rest value and end the loop.
Otherwise calculate the fee from the delta, decrement the value with the delta and assign the limit as last value, for having this value for the next loop.
function getFee(value) {
var fee = 0;
fee_schedule.some((o, i, a) => {
var delta = i + 1 === a.length
? Infinity
: o.limit - (a[i - 1] && a[i - 1].limit || 0);
if (value < delta) {
fee += value * o.fee;
return true;
}
fee += delta * o.fee;
value -= delta;
});
return fee;
}
var fee_schedule = [{ fee: 0.0100, limit: 1000000 }, { fee: 0.0075, limit: 2000000 }, { fee: 0.0050, limit: 3000000 }, { fee: 0.0025, limit: 4000000 }],
value = 5000000,
fee = getFee(value);
console.log(fee);
For a shorter approach, you could change the data set and use only the slot size of the limits with one million for the first three itmes and Infinity for the last.
function getFee(value) {
var sum = 0;
fee_schedule.some(({ fee, limit }) => {
var v = value < limit ? value : limit;
sum += v * fee;
return !(value -= v);
});
return sum;
}
var fee_schedule = [{ fee: 0.0100, limit: 1000000 }, { fee: 0.0075, limit: 1000000 }, { fee: 0.0050, limit: 1000000 }, { fee: 0.0025, limit: Infinity }],
value = 5000000,
fee = getFee(value);
console.log(fee);
Here is another possibility:
const blended = (schedule) => (amount) =>
schedule.reduce ((sum, {fee, limit}, idx, sched) => {
const top = Math.min (limit, amount)
const bottom = idx == 0 ? 0 : sched[idx - 1].limit
return sum + (top > bottom ? fee * (top - bottom) : 0)
}, 0)
const blendedFee = (schedule) => {
const calc = blended(schedule)
return (amount) => calc(amount) / amount
}
const fee_schedule = [
{ fee: 0.0100, limit: 1000000},
{ fee: 0.0075, limit: 2000000},
{ fee: 0.0050, limit: 3000000},
{ fee: 0.0025, limit: Infinity}
]
console.log (blended (fee_schedule) (5000000))
console.log (blendedFee (fee_schedule) (5000000))
Note that these are curried, so you can store a reference to blended (fee_schedule) or blendedFee (fee_schedule) if you will need to reuse these for various loan amounts.
This does not include the clever early escape of Nina's answer, but I think it's easier to follow.
Related
I have a sales data for a couple of years in an array:
var= ['Jan-2019',325678], ['feb-2019', 456789], ['Mar-2019',-12890],.....['Dec-2021', 987460]
1 -want to calculate the net total amount of profit/losses over the entire period.
2- Average of the (changes) in profit/losses over the entire period - track the total change in profits from month to month and find the average(total/number of months)
3 - greatest increase in profit (month and amount)over the whole period
4 - greatest decrease3in profit (month and amount)over the whole period
Tried to solve number 1 using :
`
const profitMonths = data.filter(el => el[1] > 0);
console.log("Total:", profitMonths.map(el => el[1]).reduce((a, b) => a + b));
console.log;
`
but the sum I am getting is different from what excel and calculator is giving me.
I will appreciate some help here
Not sure what is the format of your original data records for each of the months. I assumed that your data format is like below. But you could get the sum of each months growth or loss (earnings) like this and also get what you were trying as well (profit months total sales):
const data = [
['Jan-2019', 325678],
['Feb-2019', 456789],
['Mar-2019', -12890],
];
const earningsArray = data.map((el) => el[1]);
const profitMonths = data.filter((el) => el[1] > 0);
const salesOnProfitMonths = profitMonths
.map((el) => el[1])
.reduce((accVal, curVal) => accVal + curVal, 0);
const avgOfProfitAndLoss =
earningsArray.reduce((accVal, curVal) => accVal + curVal, 0) / data.length; // get the average of all total and losses
const maxMonth = {
monthName: '',
profit: 0,
};
const minMonth = {
monthName: '',
profit: 0,
};
data.forEach((month) => {
if (month[1] > maxMonth.profit) {
maxMonth.monthName = month[0];
maxMonth.profit = month[1];
}
if (month[1] < minMonth.profit) {
minMonth.monthName = month[0];
minMonth.profit = month[1];
}
return { maxMonth, minMonth };
});
console.log('Total sale of profit months: ', salesOnProfitMonths);
console.log('Total average : ', avgOfProfitAndLoss);
console.log('The month with max profit is : ', maxMonth);
console.log('The month with min profit is : ', minMonth);
Using .reduce() you can actually build an object to the returned based on all of the data from your original array.
const data = [['Jan-2019', 325678], ['feb-2019', 456789], ['Mar-2019',-12890], ['Dec-2021', 987460]]
let result = data.reduce((a, b, i) => {
let d = (i > 1) ? a : {total: a[1], average: a[1], sumChange: 0, lastMonth: a[1], increase: a, decrease: a},
change = b[1] - d.lastMonth
d.total += b[1]
d.sumChange += change
d.lastMonth = b[1]
d.average = d.sumChange / i
d.increase = (d.increase[1] > change) ? d.increase : [b[0], change]
d.decrease = (d.decrease[1] < change) ? d.decrease : [b[0], change]
return d
})
console.log(result) // Return the full object
console.log(result.total) // Only return one value, the total
Based on the array/input you provided, this should provide a net total, average profit/loss, highest increase from the previous month, and highest decrease from the previous month.
EDIT
I had to make a few adjustments after getting some clarification. But this again should return a single object that holds values for everything requested by OP. (the sumChange and lastMonth values are only there to help with the .reduce() function month over month)
NOTES
Just for clarity as OP claimed they were not getting the right values, here is a breakdown based on the provided data:
Date
Sales
Change
Jan-2019
$325,678
N/A
Feb-2019
$456,789
$131,111
Mar-2019
-$12,890
-$469,679
Dec-2021
$987,460
$1,000,350
Based on this input, calculated manually:
The "Average of the (changes) in profit/losses over the entire period" is $220,594 (($131,111 + $469,679 + $1,000,350) / 3).
The "greatest increase in profit (month and amount)over the whole period" would be Dec-2021 with a $1,000,350 increase.
And the "greatest decrease in profit (month and amount)over the whole period" would be Mar-2019 with -$469,679.
This is exactly what my .reduce() does produce, so I'm not sure what actual input or output OP is getting (or how they are applying this to their code/data).
This function which will execute when you enter on input field then it will calculate how much money do you have to return with x type to customer.
But getting stack size error sometime for 6 digit and everytime for 7 digit.
Reproduce: put 1234567 in input box and check the console.
Function:
let returnList = [];
let predictorList = [
100, 50, 20, 10, 5, 1, 0.5, 0.25, 0.1, 0.05, 0.01,
];
let total = 11.23
function isRemainingMoney(value) {
let remainingValue = value;
// when remaning value becomes zero then return the returnlist
if (remainingValue == 0) {
return returnList;
}
for (let pRed of predictorList) {
/* remainingValue is greater than predictor value then push it
eg: 41.33 > 20
21.33 > 20
1.33 > 1
0.33 > 0.25
0.08 > 0.05
0.03 > 0.01 * 3 times
*/
if (remainingValue >= pRed) {
const isPredExist = returnList.find(
(pItem) => +pItem.money == +pRed
);
if (!!isPredExist) {
isPredExist.count += 1;
isPredExist.total = isPredExist.total + +pRed;
} else {
returnList.push({
type: pRed,
money: pRed,
count: 1,
total: pRed,
});
}
remainingValue = +remainingValue.toFixed(2) - pRed;
break;
}
}
// recursive call the same method untill remainivalue becomes zero.
return isRemainingMoney(+remainingValue.toFixed(2));
}
document.querySelector('input').addEventListener('change', (event) => {
if(!!event.target.value) {
returnList.length = 0;
returnList = isRemainingMoney(+event.target.value - total);
console.log(returnList, 'returnList');
}
})
Playground: https://jsbin.com/kuwomalare/edit?html,js,console,output
Current Output From real application:
You end up having way too many recursive calls when the input value is large. It should be fairly straight forward to convert from recursive to iterative using a while loop. The only issue I ran into was floating point not getting down to 0 (like mentioned in the comment by #Keith). When doing money calculations, it is usually best to use integers. Use the smallest denomination in the currency. For example, in US currency, that would be cents. Only convert to decimal (or dollars in this case) when displaying the values.
I have also simplified your calculations a bit. Because of these changes, you could actually use recursion now, if you want since the maximum level of recursion now is predictorList.length.
let predictorList = [
10000, 5000, 2000, 1000, 500, 100, 50, 25, 10, 5, 1
];
let total = 709;
function isRemainingMoney(value) {
let returnList = [];
// when remaning value becomes zero then return the returnlist
while (value != 0) {
for (let pRed of predictorList) {
if (value >= pRed) {
returnList.push({
money: pRed / 100,
count: Math.floor(value / pRed),
});
value %= pRed;
}
}
}
return returnList;
}
document.querySelector('input').addEventListener('change', (event) => {
if (!!event.target.value) {
let returnList = isRemainingMoney(+event.target.value * 100 - total);
console.log(returnList, 'returnList');
}
})
<input type="number">
I have been trying to come up with a solution for this algorithm for 3-4 days but nothing seems to work and the available solutions are a bit more advanced for me. It has to be solved with conditionals only so no recursion or dynamic programming.
I need to determine the least amount of coins necessary to give change given the following denominations: 1, 0.5, 0.2, 0.1, 0.05, 0.02 and 0.01.
Input is the following:
Price of an item
Sum paid by customer
Current ideas:
let price = +gets();
let paidSum = +gets();
//gets is used to accept number input
let change = paidSum - price;
I figured I could use Math.floor to isolate the integer part and subtract it but then I have no idea what to do with the remaining sum.
Would modulo work to test whether the remaining sum contains any of the remaining values for change and then subtract again until I reach zero?
I do realize this isn't the best formulated question but I am at a loss here and I've done every other task apart from this. Thanks.
Simpler, reverse and map the denominations in cents and return a new array with the number of coins you need for each denomination.
const coinsCents = [1, 2, 5, 10, 20, 50, 100]
const getChange = (amountInCents) => {
return coinsCents.reverse().map(coin => {
let amountCoin = Math.floor(amountInCents/coin)
amountInCents -= amountCoin * coin
return amountCoin
}).reverse()
}
With the denominations you have specified, the problem is simpler than the general change making problem. In this actual case we can be sure that using the largest denomination, that is not greater than the amount to pay, always leads to an optimal solution.
So then there is no need for recursion or dynamic programming. Just a simple loop will do.
I will here ignore the additional "layer" of getting the price of the bill and the amount that the customer pays. In the end the only thing that counts is the change amount to pay back to the customer. So this snippet asks for that change amount and returns the coins that need to be given as change.
function getChange(amount) {
amount *= 100; // Convert to number of cents
var denominations = [1, 2, 5, 10, 20, 50, 100]; // cents
var result = [];
while (amount > 0) {
var coin = denominations.pop(); // Get next greatest coin
var count = Math.floor(amount/coin); // See how many times I need that coin
amount -= count * coin; // Reduce the amount with that number of coins
if (count) result.push([coin/100, count]); // Store count & coin
}
return result;
}
// I/O management
change.oninput = function () {
var coins = getChange(this.value);
result.textContent = coins.map(([coin, count]) => `${count} x $${coin}`).join(" + ");
};
To be paid to customer: <input id="change">
<div>Coins to pay: <span id="result"></span></div>
var coins;
var coinArray = {};
var output = {};
/* Method to get coin value without decimal point - it is required because
* javascript will consider 5.6 as 6 if we do Math.round()
*/
function getRoundFigureCoinValue(x) {
return (x * 10 - ((x * 10) % 10)) / 10;
}
// Method to calculate possible combination of coins
function calculateCoins(input) {
let largestPossibleCoin = 1;
if (input) {
coins.forEach((x) => {
if (input >= x) {
largestPossibleCoin = x;
}
});
let remainingCents = input % largestPossibleCoin;
output[largestPossibleCoin] = getRoundFigureCoinValue(
(input / largestPossibleCoin).toFixed(1)
);
if (remainingCents && input > 1) {
calculateCoins(remainingCents);
}
return largestPossibleCoin;
}
}
// Method to be called to get output.
function calculatePossibleCoinCombinations(value) {
if (isNaN(value) || +value <= 0) {
console.log('Invalid input');
return;
} else {
console.log('Possible combinations are:')
value = +value;
}
coins = [1, 5, 10, 25];
while (coins.length) {
let largestPossibleCoin = calculateCoins(value) || 0;
let outputString = '';
coins = coins.filter((x) => x < largestPossibleCoin);
Object.keys(output).forEach((key) => {
outputString += `${output[key]} - ${key} cents; `;
})
console.log(outputString);
output = {};
}
}
/*
Sample inputs:
calculatePossibleCoinCombinations('89');
calculatePossibleCoinCombinations(10);
calculatePossibleCoinCombinations(0);
calculatePossibleCoinCombinations('someString');
calculatePossibleCoinCombinations(-10)
*/
I have a function that converts two values to a percentage within a range, in this case between 15000 and 100000. It feels very clunky. Is there a more simple way to express this which is easy/easier to understand?
price2percent = (sale) => {
let price = sale.soldPrice / sale.livingArea;
// Specifically these values
price = price > 100000 ? 100000 : price;
price = price < 15000 ? 15000 : price;
return (price - 1500) / 85000;
}
You might use Math.min and Math.max to constrain the ranges:
const adjustedPrice = Math.min(
100000, // can be no higher than 100000
Math.max(price, 15000) // can be no lower than 15000
);
return (adjustedPrice - 1500) / 85000;
Another option is nested conditionals, which will reduce the number of unnecessary reassignments, though it doesn't exactly make the code clearer:
const adjustedPrice =
price > 100000 ? 100000 :
price < 15000 ? 15000 : price
I usually use this utility for things like that:
const clamp = (value, min, max) => value > min? value < max? value: max: min;
price2percent = (sale) => {
let price = clamp(sale.soldPrice / sale.livingArea, 15000, 100000);
return (price - 1500) / 85000;
}
I find that more readable than the Math.min(max, Math.max(min, value)) construct.
Downside, in its current version it doesn't play well with NaN.
Is there a more simple way to express this which is easy/easier to understand?
Easy to understand would be using if/else condition:
price2percent = (sale) => {
let price = sale.soldPrice / sale.livingArea;
if(price > 100000) price = 100000
else if(price < 15000) price = 15000
return (price - 1500) / 85000;
}
Which can be expressed shorter as: (the harder way)
price2percent = (sale) => {
let price = sale.soldPrice / sale.livingArea;
price = price > 100000 ? 100000 : (price < 15000 ? 15000 : price)
return (price - 1500) / 85000;
}
I need to use plain JavaScript to convert an amount of experience points to an amount of hours played by a number of fixed rates.
For example:
A player has 1,129,518 experience points.
The amount of experience points that are gained per hour depends on the amount of xp one already has. They would be arranged something like this:
above 0 xp: 8,000 xp/h
above 2,107 xp: 20,000 xp/h
above 101,333 xp: 45,000 xp/h
above 1,210,421 xp: 68,500 xp/h
above 13,034,431 xp: 75,000 xp/h
I'm struggling to find a way to use these xp rates to convert a given amount of experience points to hours played, using at least somewhat elegant Javascript.
I just end up with a cunfusing mess of if/else statements that ends up failing because of math errors.
Any Math wizards out there that can help me? Thanks.
Code Sample: I would go from here
if(xp === 0){
return 0;
}else if( 2107 >= xp > 0){
const result = (xp/8000).toFixed(1);
return result;
}else if(101333 >= xp > 2107){
const result = ((2107/8000) + ((xp-2107)/20000)).toFixed(1);
return result;
}else if(1210421 >= xp > 101333){
...
}
As you can see it would quickly get out of hand if theres alot of different tiers.
First of all, you should write your if statements like this:
if( 2107 >= xp && xp > 0){
...
}
Next, try thinking about XP as buckets of XP and each bucket having different value/price. Go from most valuable bucket to least valuable, and for each bucket calculate hours and subtract amount of XP that was used to calculate those hours.
You can do this in while loop:
let hours = 0;
while(XP > 0)
{
// figure out bucket you are in, your if statements are fine for that.
let value = 0;
let lowerBoundary = 0;
if( 101333 >= xp && xp > 2107){
value = 20000;
lowerBoundary = 2107;
// you need lower boundary to figure out how many XP you used in this bucket.
}
// else if...
const usedInBucket = XP - lowerBoundary;
hours += usedInBucket / value; // simply calculate hours needed
XP -= usedInBucket;
}
This is what I came up with:
const steps = [{
min: 0,
val: 8000
},
{
min: 2107,
val: 20000
},
{
min: 101333,
val: 45000
},
{
min: 1210421,
val: 68500
},
{
min: 13034431,
val: 75000
},
].sort((a, b) => b.min - a.min);
//using for loop
function xpToHours(xp = 0) {
let h = 0;
steps.forEach(s => {
let amt = Math.max(xp - s.min, 0);
h += amt * s.val;
xp -= amt;
});
return h;
}
//using reduce
function xpToHours2(xp = 0) {
return steps.reduce((h, s) => {
let amt = Math.max(xp - s.min, 0);
xp -= amt;
return h + amt * s.val;
}, 0)
}
[0, 1000, 2000, 3000, 1000000].forEach(xp => console.log(xp, xpToHours(xp)));
[0, 1000, 2000, 3000, 1000000].forEach(xp => console.log(xp, xpToHours2(xp)));
To explain:
steps is just an array containing your different stages. It is sorted by the minimum xp from highest to lowest.
Then we just iterate over this array calculating amt which is the xp used up by the currently highest stage. The needed time is therefore amt * currentstep.val and the xp is reduced by the calculated amount for the next stage.
The easiest way to do this is with a sorted array of ranges and Array.prototype.find
// Make sure this is sorted desc
const expRanges = [{
above: 101333,
xph: 45000
},
{
above: 2107,
xph: 20000
},
{
above: 0,
xph: 8000
}
];
function findExpPerHour(xp) {
return expRanges.find(range => range.above < xp).xph;
}
// TESTS
const playerExpTests = [{
name: "P1",
xp: 12
}, {
name: "P2",
xp: 12000
}, {
name: "P3",
xp: 200000
}, {
name: "P4",
xp: 99999999
}];
playerExpTests.forEach(p => {
console.log(p.name, "Exp per hour:", findExpPerHour(p.xp));
});