Calculate best fit from number input - javascript

I'm currently trying to figure out the algorithm to calculate the best fit.
What is the problem:
I have many types of bowls (5-15 types). Each type holds a minimum amount and a maximum amount of food (per person). So as an example I have five bowls:
A: holds between 3 and 5 persons worth of food.
B: holds between 4 to 6 persons worth of food.
C: holds between 5 and 10 persons worth of food.
D: holds between 10 and 15 persons worth of food.
E: holds between 15 and 20 persons worth of food.
The rules are:
A bowl is always filled up with food until the minimum amount or the maximum amount.
Prevent giving away free food or having waste as much as possible.
What I want to do:
give in an amount of people, and that the functions calculates what is the best fit of the amount of bowls that I need.
So as an example I would say that I have 12 people. In this case Bowl D is the best since only one bowl is needed.
But if I give in 36 people. I would expect that I would get that the best fitis:
1 X E: Holds up to 20 people
1 X C: Holds up to 10 people
1 X B: Holds up to 6 people
That makes a total of 36 people. If you know a better or more efficient way let me know.
How to create such a function in Javascript?
Since I'm a junior, please try to explain as much as possible.

This question is an optimization problem. The following code walk every possible solutions and use some heuristics (or deterministic function) to calculate the solution with least cost. There may be room for more optimizations, but your problem space is relatively small.
// 1. List of bowl
const bowlTypes = [
{
name: "A",
description: "holds between 3 and 5 persons worth of food",
min: 3,
max: 5
},
{
name: "B",
description: "holds between 4 to 6 persons worth of food",
min: 4,
max: 6
},
{
name: "C",
description: "holds between 5 and 10 persons worth of food",
min: 5,
max: 10
},
{
name: "D",
description: "holds between 10 and 15 persons worth of food",
min: 10,
max: 15
},
{
name: "E",
description: "holds between 15 and 20 persons worth of food",
min: 15,
max: 20
}
];
// 2. Create a cost function for the best combination of bowls
// e.g. may use sum of the bowls' costs
function getCost(bowls, surplus) {
const total = bowls.reduce((total, { min, max }) => total + ((max - min) / 2), 0);
// penalty for more bowls, heavy penalty for surplus
// adjust function to calibrate, perhaps add actual
// bowl cost to data set
return bowls.length + total + (surplus * surplus);
}
// 3. Evaluate how many bowls we need given a number of persons
function evaluatePersons(persons) {
const bowlCount = bowlTypes.length;
let bestSolution;
// recursive function walking all possible options.
const findSolution = (bowls, servings, startIndex) => {
// while we can add more bowls...
if (servings > 0) {
// try next combination
for (let bowlIndex = startIndex; bowlIndex < bowlCount; ++bowlIndex) {
const bowl = bowlTypes[bowlIndex];
findSolution([ ...bowls, bowl ], servings - bowl.max, bowlIndex);
}
// if the current solution has enough, or too many servings
} else {
// get amount of surplus
const surprlus = Math.abs(servings);
// get the cost of this solution
const cost = getCost(bowls, surprlus);
// if the current solution is better than any previous one
if (!bestSolution || (cost < bestSolution.cost)) {
bestSolution = { bowls, cost, surprlus };
}
}
};
// init first step
for (let bowlIndex = 0; bowlIndex < bowlCount; ++bowlIndex) {
findSolution([], persons, bowlIndex);
}
// optimize solution
bestSolution.bowls = Array.from(bestSolution.bowls.reduce((map, bowl) => {
if (map.has(bowl.name)) {
map.get(bowl.name).qty = map.get(bowl.name).qty + 1;
} else {
map.set(bowl.name, { ...bowl, qty:1 });
}
return map;
}, new Map()).values());
// return our best solution
return bestSolution;
}
// UI for testing purposes
const inputPersons = document.getElementById('inputPersons');
const output = document.getElementById('output');
inputPersons.addEventListener('change', () => {
const solution = evaluatePersons(inputPersons.value);
const verbatim = solution.bowls.map(bowl => `${bowl.qty} x ${bowl.name}: ${bowl.description}`).join('\n');
const debugString = JSON.stringify(solution, null, 3);
output.innerHTML = verbatim + '\n--------\n' + debugString;
});
main {
width: 100%;
display: flex;
flex-direction: column;
}
form {
flex: 0;
}
pre {
min-height: 200px;
flex: 1;
border: 1px solid black;
background-color: #e7e7e7;
padding: 10px;
}
<main>
<form>
<input type="number" id="inputPersons" />
</form>
<pre><code id="output"></code></pre>
</main>

Related

How to distribute one array of elements over another but have the element count gradually go down?

I'm creating mock data for my app with fakerJS.
I have created one array of 10 000 user IDs. And 1 array of 100 000 content IDs.
I want to distribute these content IDs over the user ID array where the first one will get the most content IDs and the last user will get the least (0).
Eg.
const userIds = ['a', 'b', 'c', ...] // 10_000 long
const contentIds = ['c1', 'c2', 'c3', ...] // 100_000 long
const result = distribute(userIds, contentIds)
result // { a: ['c1', 'c2', 'c3', ...], b: [...], z: [] }
The distribution should look something like this theoretically:
However, the 40 here for highest number is way too low, so imagine this being way higher for the first user ID and with 10_000 users, many of the last users could have 0 content IDs with this distribution basically.
I've been coding for 6+ years, but I think I need to start learning Mathematics to figure this one out, would appreciate any help on how to even start 😅
Maybe a logarithm decay and truncate (you can play with value_data.push formula until you find what you want)
Start and End
0: 90
1: 83
2: 79
3: 76
4: 74
5: 72
...
9995: 0
9996: 0
9997: 0
9998: 0
9999: 0
<script>
var time_data = [];
var value_data = [];
max = 10000;
//logarithmic decay array and truncate
for (let i=1;i<=max;++i) {
time_data.push(i);
value_data.push( Math.floor(100 - ( (9.71672 * (1 + Math.log(i)) ) )) );
}
//initializer
guarda = 0;
valueGuarda = 0;
for (let index = 0; index < time_data.length; index++) {
guarda = time_data[index] + guarda;
valueGuarda = value_data[index] + valueGuarda;
}
//arrays
console.log(time_data)
console.log(value_data)
//calculating the summation
let total = value_data.reduce((a, b) => a + b, 0); //100000
console.log("total: " + total);
</script>

Distribute items evenly but add remainder to first

The snippet below pretty much says it all, but in short I need to distribute a certain amount of months equally over activities. Since there is always a chance to deal with a remainder these should be added to the first month.
const selectedMonth = 5
const project = {
duration: 2, // in months
activities: [{
number: 1,
title: 'game 1'
},
{
number: 2,
title: 'game 2'
},
{
number: 3,
title: 'game 3'
},
]
}
// 1 Add a "plannedInMonth" property to each activity
// 2 Start planning from the selected month and onwards (the month number can be > 12)
// 3 Spread the activities evenly based on the duration
function planActivitiesInMonths() {}
planActivitiesInMonths()
// So this function should, since the remainder of 3 / 2 = 1, return as follows:
activities: [{
number: 1,
title: 'game 1',
plannedInMonth: 5
},
{
number: 2,
title: 'game 2',
plannedInMonth: 5
},
{
number: 3,
title: 'game 3',
plannedInMonth: 6
},
]
// However, it should also work when e.g. 24 activities need to be distributed across 5 months
If you're just looking to copy paste an implementation of the algorithm, this should do it:
function planActivitiesInMonths(project, selectedMonth) {
const remainder = project.activities.length % project.duration
const activitesPerMonth = Math.floor(project.activities.length / project.duration)
return project.activities.map((activity, i) => {
let index = Math.floor((i - remainder) / activitesPerMonth)
if (index < 0) {
index = 0
}
activity.plannedInMonth = index + selectedMonth
return activity
})
}
Just keep in mind that my function returns a value and doesn't mutate directly the object.
I am shifting the index by the remainder to have to be able to nicely handle the fact that the remainder activities should be added to the first month, but there are a tons of ways of implementing this algorithm.
However, this algorithm has a strange behaviour if the project duration is slightly below a multiple of the activities per month. In this case, the remainder would be very big and a lot of activities would be added to the first month.
For example, if you want to distribute 9 activities across 5 months, the remainder would be 5 % 9 = 4, so the first month would have a total of 5 activities!
Maybe it's better to evenly distribute the remainder too. And this algorithm has a cleaner and simpler implementation:
function planActivitiesInMonths(project, selectedMonth) {
const activitesPerMonth = project.activities.length / project.duration
return project.activities.map((activity, i) => {
const index = Math.floor(i / project.activities.length * activitesPerMonth)
activity.plannedInMonth = index + selectedMonth
return activity
})
}

Waterpay calculator else if statements

I've been going over this question now for a couple of days and I'm still no closer to getting it right or understanding as to how to get it to run properly.
This is the current code I have:
let waterPay = prompt("Please enter the amount of water you use to get a price you need to pay, thank you!");
if (waterPay < 6000) {
console.log("The number is below 6000");
console.log (waterPay / 1000); //The outcome of this must be saved as a different let
console.log (waterPay * 15.73);// outcome of the above times by this amount
}
else if (waterPay > 6000 && waterPay <= 10500) {
console.log("The number is between 6000 and 10500");
}
else if (waterPay > 10500 && waterPay <= 35000) {
console.log("The number is between 10500 and 35000");
}
else if (waterPay > 35000) {
console.log("The number is above 35000");
}
What my code needs to do is take an input from the user stating how many litres of water they use, you can see in the code that depending on the amount of litres they use it should print out how much they owe.
The table above states that the first 6 000 litres will cost R15.73 per kilolitre.
Next, water consumption above 6 000 litres but below 10 500 litres will be
charged at R22.38 per kilolitre. Therefore, a household that has used 8000
litres will pay R139.14 (15.73 x 6 + 22.38 x 2). The table carries on in this
manner.
Im battling to figure out how I should go about working this out. Any help would be appreciated.
The data structure needed is something that pairs rates with usage thresholds. The last threshold is effectively infinite, to catch any usage above the highest. The logic is to find() the right rate object and multiply that rate tier's rate by the usage.
let rateData = [{
upTo: 6000,
rate: 15.73
},
{
upTo: 10500,
rate: 22.38
},
{
upTo: 35000,
rate: 34.0. // made this one up, not in the OP
},
{
upTo: Number.MAX_SAFE_INTEGER,
rate: 50.0. // made this one up, not in the OP
}
];
function rateDatumForUsage(usage) {
return rateData.find(r => usage <= r.upTo);
}
function costForUsage(usage) {
const rateDatum = rateDatumForUsage(usage);
return usage * rateDatum.rate;
}
console.log(`The cost of using 5000 units is (15.73*5000) ${costForUsage(5000)}`)
console.log(`The cost of using 10000 units is (22.38*10000) ${costForUsage(10000)}`)
console.log(`The cost of using 100000 units is (50*100000) ${costForUsage(100000)}`)
Total cost should be calculated by steps.
This means that, for example, if the first 10 liters cost USD 2, the following 10 liters (from 10 to 20) cost USD 1 and from 20 cost will be USD 0.5, then the total cost for 30 liters will be: 10*2 + 10*1 + 10*0.5 = 35.
This can only be achieved generically by looping. Here is the code:
const steps = [
6000,
10500,
35000
];
const rates = [
10,
20,
30
];
function calculate(used) {
let output = 0;
for (let i = 0; i < steps.length; i++) {
if (used >= steps[i]) {
output += steps[i] * rates[i];
} else {
output += (used - (steps[i - 1] || 0)) * rates[i];
break;
}
}
return output;
}
console.log(calculate(3000));
console.log(calculate(6000));
console.log(calculate(9000));
console.log(calculate(50000));

Function to approximate long numbers

I have a question I have a value that I need to divide and make the values ​​without infinite decimals when divided by an odd number
example:
5 values ​​that add up to 200 divided by three people
this result is: 66.66666666666667
I want to avoid this, so that an approximation is made like:
2 people would stay with 65
and one person with 70
my code:
const test = [
{ price: 5, quantity: 10 },
{ price: 10, quantity: 10 },
{ price: 5, quantity: 10 },
];
const persons = ['person 1', 'person 2', 'person 3']
const total = testList
.map((test ) => test .unitPrice * test .quantity)
.reduce((sum, current) => sum + current);
const division = total/persons.length
I need a way that in the end the total is 200 divided for 3 people, without having decimal numbers
You need to figure out how many will remain. Remove them, divide it, than loop to add the remaining to the other buckets until you run out.
const total = 200
const numParts = 3
let extras = total % numParts;
const base = (total - extras) / numParts
const portions = new Array(numParts).fill(base)
for (let i = 0; i < numParts && extras > 0; i++) {
portions[i]++;
extras--;
}
console.log(portions);

JavaScript array find fitting range

I have the following array with two objects:
var myArr = [{
id: 3,
licences: 100
new_value_pr_licence: 40
}, {
id: 4,
licences: 200
new_value_pr_licence: 25
}]
A user wish to buy 150 licences. This means that they fall into the category 100 because they are above 100 licences but below 200 which means they pay $40 per licence.
Note that the array object values varies.
Order your plans by the price per licence:
myArr.sort(function (a, b) {
return a.new_value_pr_licence - b.new_value_pr_licence;
})
then starting from the start of the array, take as many of that plan as you can without going over the number the user wants to buy:
var numUserWants = 150;
var purchases = {};
var cheapestAvailableProduct = myArr.shift();
while (numUserWants > 0 && cheapestAvailableProduct) {
if (numUserWants <= cheapestAvailableProduct.licences) {
purchases[cheapestAvailableProduct.id] = Math.floor(cheapestAvailableProduct.licences / numUserWants);
numUserWants = cheapestAvailableProduct.licences % numUserWants;
}
cheapestAvailableProduct = myArr.shift();
}
At this point, purchases will now be a map of plan id to number:
purchases => {
3: 3
4: 1
}
This doesn't handle the case where over-purchasing is the cheapest option (eg: it's cheaper to buy 160 at 4x40, instead of 150 at 3x40 + 1x25 + 1x5), but it's probably a good start for you to tweaking.
Just a simple forEach here. Take the number requested, begin calculating/mutating total based on option limits, and once the number requested is less than the option limit you have your final total, which wont be mutated any longer and returned from the function.
function calculateDiscountedTotal(numberRequested, myArr){
var total;
// loop, compare, calculate
myArr.forEach(function(option) {
if(numberRequested >= option.licenses){
total = numberRequested * option.new_value_pr_licence
}
}
if(total != undefined){
return total;
} else {
// user never had enough for initial discount
return "no discount price";
}
}
Sort the array first in terms of number of licenses and then get the object in which number of licenses is less than number of licenses to be bought (just less than the next item in the array which is greater than number of licenses to be bought)
var myArr = [
{
id: 3,
licences: 100
new_value_pr_licence: 40,
},
{
id: 4,
licences: 200,
new_value_pr_licence: 25
},
];
var numOfLic = 150;
myArr.sort( function(a,b){ return a.licences - b.licences } );
var selectedObj = myArr.reduce( function(prev,current){
if ( current.licences > numOfLic )
{
return prev;
}
});
console.log ( "pricing should be " + ( selectedObj.new_value_pr_licence * numOfLic ) );

Categories

Resources