I have two values on my html page, that I got from a form.
The person completes the amount of money they have on one input, and on the other input, the number of people.
So, my question is:
How do i divide the amount.value by the people.value, and distribute it, in a way it appears as shown in the example below?
Amount: 150 / Number of People: 3
-Person 1 - 50
-Person 2 - 50
-Person 3 - 50
What i'm actually struggling with, is to create a function that will add to the HMTL another person + the result, depending on the number of people added previously.
The code down below just finds the total share of all people and distributes the amount among them dependin on their shares.
/*----- Define your variables in here ------*/
var amount = 150;
var people = {
1: 0.75,
2: 0.15,
3: 0.10
}
var totalSharePerc = 0;
/*----- Loop thruogh people to calculate total sum ------*/
for (const [key, value] of Object.entries(people)) {
totalSharePerc = totalSharePerc + value;
}
/*----- Loop thruogh people and split the amount ------*/
for (const [key, value] of Object.entries(people)) {
/*------ Calculate share of person -----*/
var share = (amount*value/totalSharePerc);
/*----- Do whatever you want ------*/
console.log("Person"+key+" => "+share);
}
You can use Array#reduce() to calculate the total share for every person involved.
This assumes you have a mapping defined of which person has to cover which share (which you will need to have if you want to distribute in non-equal shares).
const amount = 150;
// define mapping of percentages to persons
const sharesPerPersonPct = {
1: 0.75,
2: 0.15,
3: 0.1,
};
// create a mapping of total values to persons
const sharesPerPersonTotal = Object.entries(sharesPerPersonPct).reduce(
(allShares, [personId, share]) => {
allShares[personId] = {
pct: share, // add percentage (optional)
total: amount * share // add total share
}
return allShares;
},
{}
);
console.log("Resulting JS object:")
console.log(sharesPerPersonTotal);
Object.entries(sharesPerPersonTotal).forEach(([personId, share]) => console.log(`Person ${personId} has to cover ${(share.pct * 100).toFixed(2)}% which amounts to ${share.total}$.`))
Updated answer to reflect your edit
The following is for an equal distribution of an amount to a number of people. The challenge is that e.g 10$ cannot be distributed 3.33$ for each of 3 persons as then penny would be missing. This is the sort of stuff you get when using floating point arithmetic. To prevent that use integer arithmetic instead. So multiply 10$ by 100 so you get 1000p and you can then assign each person their floored share (Math.floor(1000 / 3) = 333) use modulo to get the remainder (10 % 3 = 1) and distribute that remainder among the persons involved. The current implementation isn't quite fair either though because it always assigns that one penny more starting from the front, but you could use something like this to account for that.
The rest is just input validation using RegEx and displaying the results doing some DOM manipulation.
function handleUpdateDistribution() {
const amountMoney = document.getElementById("amount-money");
const noPersons = document.getElementById("no-persons");
if (!isMoneyValid(amountMoney.value)) {
console.log("Money value can only have two decimal places!");
return;
}
if (!isNoPersonValid(amountMoney.value)) {
console.log("Number of persons must be an integer greater than one!");
return;
}
const distribution = updateDistribution(
Number.parseInt(noPersons.value),
Number.parseFloat(amountMoney.value)
);
showDistribution(distribution);
}
function isMoneyValid(money) {
const matches = money.match(/^[0-9]+(\.[0-9]{1,2})?$/g);
if (matches === null) return null;
else return matches[0];
}
function isNoPersonValid(noPersons) {
const matches = noPersons.match(/[1-9]*/g);
if (matches === null) return null;
else return matches[0];
}
function showDistribution(distribution) {
const list = document.createElement("ul");
const listItems = Object.entries(distribution).map(([personId, share]) => {
const item = document.createElement("li");
item.textContent = `Person ${personId} has to cover ${share}$.`;
return item;
});
list.append(...listItems);
document.getElementById("result").replaceChildren(list);
}
/**
*
* #param {number} noPersons number of persons to split between
* #param {number} amountMoney amount of money to split
*/
function updateDistribution(noPersons, amountMoney) {
// use integer arithmetic as floating point arithmetic is not very suitable for task at hand
amountMoney *= 100;
const share = Math.floor(amountMoney / noPersons);
let remainder = amountMoney % noPersons;
const persons = {};
for (let i = 1; i <= noPersons; i++) {
const shareInInts = share + (remainder > 0 ? 1 : 0);
remainder--;
persons[i] = (shareInInts / 100).toFixed(2);
}
return persons;
}
window.addEventListener("DOMContentLoaded", (e) => {
const amountMoney = document.getElementById("amount-money");
const noPersons = document.getElementById("no-persons");
amountMoney.addEventListener("input", handleUpdateDistribution);
noPersons.addEventListener("input", handleUpdateDistribution);
});
input:invalid {
border: red solid 3px;
}
<form>
<input id="no-persons" placeholder="Number of persons" type="number" required />
<input id="amount-money" placeholder="Amount of money" type="text" pattern="^[0-9]+(\.[0-9]{1,2})?$" required />
</form>
<div id="result"></div>
Please note you can and should do more to give the user a nice user experience (Show popovers instead of console.log(), nicer styling etc). See MDN docs on form validation. You should also probably restrict the number of persons as rendering thousands of list items will seriously impact the performance.
Related
I have created a class that takes computer parts and their costs and calculates them based on what parts are chosen. At the moment I have two functions, one to add more parts to the quote and one to remove parts. It works correctly in the sense of removing or adding items, but does not change the total cost. When I remove parts, the price remains the same, likewise if I add more parts. What could I do to get this working as expected. Here is the code:
class PriceCalc {
Motherboard = 520.99;
RAM = 250.4;
SSD = 500.8;
HDD = 400.66;
Case = 375.5;
Monitor = 600.75;
Keyboard = 100.99;
Mouse = 25.5;
constructor(Obj) {
this.parts = Obj;
this.cost = "$" + Obj.reduce((a, b) => a + this[b], 0).toFixed(2);
this.retail ="$" +(Obj.reduce((a, b) => a + this[b], 0) +Obj.reduce((a, b) => a + this[b], 0) * 1.75).toFixed(2);
this.quote = "Your quote is " + this.retail;
}
add(item) {
this.parts = [...this.parts, item];
}
remove(item) {
this.parts = this.parts.filter((x) => x !== item);
}
}
quote4 = new PriceCalc(["RAM", "SSD", "Case", "Mouse"]);
console.log(quote4.parts);//Returns ["RAM", "SSD", "Case", "Mouse"]
console.log(quote4.cost);//Returns $1152.20
console.log(quote4.retail);//Returns $3168.55
console.log(quote4.quote);//Returns "Your quote is $3168.55"
quote4.remove("Case")
console.log(quote4.parts);//Returns ["RAM", "SSD", "Mouse"]
console.log(quote4.cost);//Returns $1152.20
console.log(quote4.retail);//Returns $3168.55
console.log(quote4.quote);//Returns "Your quote is $3168.55"
At the moment this.cost/retail/quote doesnt change when things are added or removed, whereas they should be modified if items are added or removed. The only way I can change the values at the moment is by manually changing the parts within the called array. How could I fix this?
You need to recalculate them whenever a new item gets added or an item is removed. Since they need to be recalculated from different places (the constructor, add and remove), a new method (called calculate or update for example) is perfect for this as reusable code should be grouped in a function/method so you don't repeat yourself.
Also, cost and retail should be numbers instead of strings. You are using reduce to calculate cost, then using the same reduce twice to calculate retail which should be avoided. Just calculate cost and use cost to calculate retail.
Finally, add and remove should not create new arrays each time an item is removed or added.
class PriceCalc {
Motherboard = 520.99;
RAM = 250.4;
SSD = 500.8;
HDD = 400.66;
Case = 375.5;
Monitor = 600.75;
Keyboard = 100.99;
Mouse = 25.5;
constructor(initialParts) { // always choose good names for your variables. initialParts makes more sense than Obj
this.parts = initialParts; // store the initial parts
calculate(); // calculate cost, retail and quote
}
add(item) {
this.parts.push(item); // use push instead of creating a new array (which is an overkill)
calculate(); // recalculate cost, retail and quote
}
remove(item) {
this.parts = this.parts.filter((x) => x !== item); // I'd rather use this https://stackoverflow.com/a/5767357 over filter but filter is shorter so OK
calculate(); // recalculate cost, retail and quote
}
calculate() { // the method that calculates cost, retail and quote
this.cost = this.parts.reduce((a, b) => a + this[b], 0); // calculate cost and store as a number instead of a string
this.retail = this.cost + this.cost * 1.75; // calculate retail using the value of this.cost and also store as a number (you can use this.retail = 2.75 * this.cost; which is the same)
this.quote = "Your quote is $" + this.retail.toFixed(2); // generate the quote (notice the added $ sign and the call to toFixed(2))
}
}
I need a function to calculate an amount of frames in the user input. I have some code from a Electron GUI that does the job, but i'm unsure how to do it in my HTML. I want it to grab the input from #id_desiredFrames in the html and then calculate the amount of total frames from that.
Code that needs to be rewritten:
* #example
* "55;58-60,2" -> 55,58,60 -> 3 Frames
* "55;58-60" -> 55,58,59,60 -> 4 Frames
*/
function calculateFrameAmount(_frame){
const notationArray = _frame.match(/(\d+)(-)?(\d+)?(\,\d)?/g)
const calculateNotation = item => {
if (!isNaN(item))
return 1
if (item.includes(",")) {
[item, diff] = item.split(",");
}
const splitItem = item.split("-")
return Math.floor((Math.max(...splitItem) - Math.min(...splitItem)) / diff) + 1
}
let diff = 1;
return notationArray
.map(calculateNotation)
.reduce((total, amount) => total += amount)
}
export default calculateFrameAmount
How would I go on about this? I know I can get the value by using document.getElementById("id_desiredFrames").value; but how do I add the value into the function??
If you're looking for the value to be computed on change, then you can do something like the following:
var desiredFrames = document.getElementById("id_desiredFrames");
desiredFrames.onchange = function(e) {
calculateFrameAmount(e.target.value);
}
Additionally, you can run the calculation onkeyup instead if you need it to be real-time:
desiredFrames.onkeyup = function(e) {
calculateFrameAmount(e.target.value);
}
How do I ensure that I don't get a repeat of a random number? Right now, this isn't working. I'm using a local array to store previous results.
getUniqueRandomNumber(x){
var index;
var viewedIndices = [];
index = Math.floor(Math.random() * (x));
if(viewedIndices.includes(index))
{
viewedIndices.push(index);
this.getUniqueRandomNumber(x);
}
else {
console.log(index);
return index;
}
}
You need to make viewedIndicies persistent, so that further calls of getUniqueRandomNumber can see elements previously added. Rather than keeping track of the indicies, it would probably be easier to keep track of just the plain numbers chosen. You can use a Set instead of an array for less computational complexity (.has is O(1), .includes is O(N)).
const makeGetUniqueRandomNumber = (x) => {
const chosenNumbers = new Set();
return () => {
if (chosenNumbers.size === x) {
throw new Error('No more uniques!');
}
let num;
do {
num = Math.floor(Math.random() * x);
} while (chosenNumbers.has(num));
chosenNumbers.add(num);
return num;
};
};
const getRand5 = makeGetUniqueRandomNumber(5);
console.log(
getRand5(),
getRand5(),
getRand5(),
getRand5(),
getRand5()
);
try {
getRand5();
} catch(e) {
console.log(e.message);
}
const anotherGetRand5 = makeGetUniqueRandomNumber(5);
console.log(
anotherGetRand5(),
anotherGetRand5(),
anotherGetRand5(),
anotherGetRand5(),
anotherGetRand5()
);
You may also generate the whole array of random numbers ahead of time, and then splice each time another is chosen, but that'll be inefficient when the number of possibilities is large but you only need a few random numbers. The right choice depends on the proportion of unique numbers needed in one session to the size of the random range.
If developing in an ancient environment which doesn't understand ES6 (ES2015) syntax, then you can use an array instead of a Set, and pass the code through Babel:
"use strict";
var makeGetUniqueRandomNumber = function makeGetUniqueRandomNumber(x) {
var chosenNumbers = [];
return function () {
if (chosenNumbers.length === x) {
throw new Error('No more uniques!');
}
var num;
do {
num = Math.floor(Math.random() * x);
} while (chosenNumbers.includes(num));
chosenNumbers.push(num);
return num;
};
};
var getRand5 = makeGetUniqueRandomNumber(5);
console.log(getRand5(), getRand5(), getRand5(), getRand5(), getRand5());
try {
getRand5();
} catch (e) {
console.log(e.message);
}
var anotherGetRand5 = makeGetUniqueRandomNumber(5);
console.log(anotherGetRand5(), anotherGetRand5(), anotherGetRand5(), anotherGetRand5(), anotherGetRand5());
You have 2 mistakes, oné is the array inside the function this cleared for each try, and then there is wrong logic ending up in an infinite loop.
const usedIndexes = [];
function getUniqueRandomNumber(x) {
const index = Math.floor(Math.random() * (x));
if (usedIndexes.includes(index)) {
return this.getUniqueRandomNumber(x);
} else {
console.log(index);
usedIndexes.push(index);
return index;
}
}
Also, I would think about using Set, in this situation instead of the array.
const usedIndexes = new Set();
function getUniqueRandomNumber(max, min = 0) {
const newNumber = Math.floor(Math.random() * (max - min) + min);
if (usedIndexes.has(newNumber)) {
return this.getUniqueRandomNumber(max, min);
} else {
usedIndexes.add(newNumber);
return newNumber;
}
}
I have also edited variables names to better reflect their actual use and added a minimum for a random number.
This is not working because every time you call getUniqueRandomNumber it re-initializes your viewedIndices array to empty array. So to make your code work declare this array above the function call.
Do you just want the code you wrote to work or do you want a better solution? Picking random numbers until you don't get a repeat is a recipe for disaster down the line as your program stalls for several seconds trying to find a number that hasn't been used. Sure if you're only asking for a few numbers maybe it won't take forever but then the code sits in your code base and 5 years from now someone else is using it not knowing there is a time bomb in the code. Imagine there are 10000 elements in the array and 9999 have been picked. It could easily take 1 million re-tries before it ends up picking the one unused index.
The code appears to be choosing indices with variable names like index and viewedIndices
One way to pick random elements is just just remove then from the array at random. If you need to make copy of the array
const array = ["a", "b", "c", "d", "e", "f", "g"];
while (array.length) {
const ndx = Math.random() * array.length | 0;
const elem = array.splice(ndx, 1)[0];
console.log(elem);
}
Note: using Math.random() * value | 0 to get a random 0 -> positive integer is faster than Math.floor(Math.random() * value) as | is an operator, not a function attached to the Math object that has to be checked on every call to see if it has been replaced.
this is my first post. I am facing an error with fitting a curve in tensorflow.js which I can't seem to fix. I have spent two days on it so far. Since tensorflow.js is pretty new, there's not a whole lot of answers to this sort of question out there, so I'm sure many people are interested in this. I have tried to replicate the example from the tensorflow.js project's website:
https://js.tensorflow.org/tutorials/fit-curve.html .
The difference is that I am using multiple predictors to predict an outcome variable. I have 20 prices, and I am using the previous 4 prices to predict the fifth one. So I start out with price number 5 and go up to price number 20 where price 5 is predicted by price 1 to 4 and so forth in a weighted time-series prediction model. I am using a multiple linear regression framework where I set up 4 random parameters (one weight for each of the four previous prices). My goal is to train the variable to minimize my loss function (using minimum least square criterion). I have tried following the example from the link as closely as possible. Whenever I run my code I get:
Error: The f passed in variableGrads(f) must be a function
which is generated by the call of .minimize in line 59 (right before return in the train function at the end). Basicially what I'm doing is fit a linear regression which could be more easily done in R but we aim at very large data sets and more complex machine learning procedures. I'm sure this is interesting to a lot of other people who are getting started woth tensorflow.js.
here's my code with some comments:
const tf = require('#tensorflow/tfjs');
require('#tensorflow/tfjs-node');
module.exports = function tensorFlow() {
//the trainable variable with initial random numbers
let lag = tf.variable(tf.tensor([Math.random(), Math.random(), Math.random(), Math.random()], [4]));
//20 observed prices
let priceData = [21.00397, 21.29068, 22.80492, 23.40646, 24.06598, 23.89722, 25.40211, 24.63436, 25.83449, 26.44832, 26.25194, 27.34009, 27.90455, 27.14175, 28.12549, 29.99411, 30.43631, 30.39753, 30.16104, 31.14931];
//the prices from price 5 on that are to be predicted
let toBePredictedList = [24.06598, 23.89722, 25.40211, 24.63436, 25.83449, 26.44832, 26.25194, 27.34009, 27.90455, 27.14175, 28.12549, 29.99411, 30.43631, 30.39753, 30.16104, 31.14931];
//set up tensor of labels to compare predictions with
let toBePredicted = tf.tensor(toBePredictedList, [16]);
//a list of predictors with 16 rows and four columns for 16 predictions to be made using 4 previous prices each
let predictorsList = [];
for (let predictorIndex = 0; predictorIndex < 16; predictorIndex++) {
for (let predictionsIndex = 0; predictionsIndex < 4; predictionsIndex++) {
predictorsList.push(priceData[predictorIndex + predictionsIndex]);
}
}
//make it a tensor
let predictors = tf.tensor(predictorsList, [16, 4]);
//predict multiplies all predictors in all lines with the parameters from lag to be trained and adds up the four elements to generate an estimate of the fifth price
function predict(predictors) {
function modelMaker() {
let modelList = [];
for (let rowIndex = 0; rowIndex < 16; rowIndex++) {
let prediction = 0;
for (let colIndex = 0; colIndex < 4; colIndex++) {
prediction += lag.get(colIndex) * predictors.get(rowIndex, colIndex);
console.log({prediction});
}
modelList.push(prediction);
}
return tf.tensor(modelList, [16]);
}
return tf.tidy(modelMaker);
}
//means square error of my prediction when compared to actual outcome price
function loss(predictions, toBePredicted) {
return tf.losses.meanSquaredError(toBePredicted, predictions);
}
function train(predictors, toBePredicted, numIterations) {
function computeLoss (predictors, toBePredicted) {
let predictions = predict(predictors);
return loss(predictions, toBePredicted);
}
let learningRate = 0.5; //suggested by Google Developers
const OPTIMIZER = tf.train.sgd(learningRate); //suggested by Google Developers
for (let iter = 0; iter < numIterations; iter++) {
OPTIMIZER.minimize(computeLoss(predictors, toBePredicted));
}
return {
a: lag.get(0),
b: lag.get(1),
c: lag.get(2),
d: lag.get(3)
};
};
//75 suggested by google developers
return train(predictors, toBePredicted, 75);
};
The problem is with minimze in the end as I said. The above code works fine and computes everything it is supposed to.
Thanks for any suggestions!
Chris
optimizer.minimize() updates the weights during each cycle of training. For the weights to be updated, they need to be created using tf.variable. A variable created using tf.variable is mutable whereas tf.tensor creates immutable variable.
It is also noteworthy to point out that the predict() should return a function whose coefficient are created using tf.variable that will be updated to minimize the loss function.
I am looking for a neat way to do the following but have a brain freeze:
User can add (and subsequently remove) a type to a shopping basket.
For each add or remove, I need to see if there is a type higher in the hierarchy that can be suggested instead. The highest type is preferable (note it has the same price as the penultimate type but b6 should be preferred over b5)
Examples:
2 x type1 (100+100) -> 1 x type2
type1 + type3 (100+300) -> 1 x type4
type3 + type4 (300+400) -> 1 x type6 + 1 x type2
1 type6 + 1 type2 + 1 type1 -> 1 x type6 + 1 x type3
and so on.
So if the total is 0, just add whatever is clicked.
If not, see if the total plus the new click can be resolved in a better combination starting from the top. If user declines the suggestion, ignore the add.
Code so far (tagged jQuery since the code is using it - map and filter are welcome)
I have not posted my attempts of the recalc, since I want to avoid the X/Y problem.
jsFiddle
var types= {
b1: {price:100,quantity:0},
b2: {price:200,quantity:0},
b3: {price:300,quantity:0},
b4: {price:400,quantity:0},
b5: {price:500,quantity:0},
b6: {price:500,quantity:0}
}
function getTotal() {
var total = 0;
$.each(types,function(typeId,type) {
total+=type.quantity*type.price;
});
return total
}
$(function() {
var cont = $("#container");
$.each(types,function(typeId,type) {
$('<button class="add" id="'+typeId+'add">Add to '+typeId+'<button><span id="'+typeId+'val">0</button><button class="remove" id="'+typeId+'remove">Remove from '+typeId+'</button><span id="'+typeId+'total">0</span><br/>').appendTo(cont);
});
$(".add").on("click",function() {
var id = this.id.replace("add",""),type=types[id];
type.quantity++;
var subTotal = type.quantity*type.price;
$("#"+id+"val").text(type.quantity);
$("#"+id+"total").text(subTotal);
$("#total").text(getTotal());
});
$(".remove").on("click",function() {
var id = this.id.replace("remove",""),type=types[id];
if (type.quantity>0) type.quantity--;
var subTotal = type.quantity*type.price;
$("#"+id+"val").text(type.quantity);
$("#"+id+"total").text(subTotal);
$("#total").text(getTotal());
});
});
It's a little confusing what you are after. Try this:
//Sort the keys by price descending, and get the current total
var remaining = getTotal(),
keys = Object.keys(types).sort(function(a,b){
return b.price > a.price ? -1 : 1;
});
// Loop through the keys (sorted by price).
// Set quantity based on how many times the remainder goes into the price
// and update the remainder
keys.forEach(
function(k){
var x = Math.floor(remaining/types[k].price);
types[k].quantity = x;
remaining -= x * types[k].price;
});
http://jsfiddle.net/GC3mW/
The problem as you have described it sounds remarkably similar to the coins-change algorithm.
It can be made to work with a rather simple greedy loop, going somewhere along these lines:
while(total > 0) {
total = total - [get largest price less than total]
[increment amount for above largest price]
}
Here's your fiddle with that update: http://jsfiddle.net/DnRG9/1/
This doesn't handle any corner cases, so it's possible I've missed something. Let me know if it helps