Creating dynamic split functionality using sliders - javascript

Currently I'm creating an application where the user can split the bill to individual candidates. Initially the slider values are equal for all candidates and the user can adjust them on the page accordingly where to increase someone's percentage, they have to decrease someone else's first. If the total amount of all sliders equal to 100, the sliders cannot move further which I'm achieving using upperLimit in the slider library
So far I've been able to achieve this with 2 users where I basically hardcoded the states and the logic by saying if when slider 1 is moved and state1 + state2 < 101 then pass the value 100 - (slider 1 value) to the slider 2 value. I'm achieving this with the following code:
const SplitBillPage = () => {
const splitUsers = 2;
const equalValueSplit = 100 / splitUsers;
const [range, setRange] = useState(equalValueSplit + '%');
const [secondRange, setSecondRange] = useState(equalValueSplit + '%');
const [limit, setLimit] = useState(100);
const [limitSecond, setLimitSecond] = useState(100);
function rangeSetter(value) {
setRange(parseInt(value) + '%');
let x = range;
let y = secondRange;
let withoutPercentage = x.replace('%', '');
let withoutPercentageSecond = y.replace('%', '');
let valueLimit = 100 - parseInt(withoutPercentage);
if (parseInt(withoutPercentage) + parseInt(withoutPercentageSecond) < 101) {
setLimitSecond(valueLimit);
}
}
function rangeSetterSecond(value) {
setSecondRange(parseInt(value) + '%');
let x = range;
let y = secondRange;
let withoutPercentage = x.replace('%', '');
let withoutPercentageSecond = y.replace('%', '');
let valueLimit = 100 - parseInt(withoutPercentageSecond);
if (parseInt(withoutPercentage) + parseInt(withoutPercentageSecond) < 101) {
setLimit(valueLimit);
}
}
return (
<View>
<Text>{range}</Text>
<Slider
minimumValue={0}
value={equalValueSplit}
maximumValue={100}
upperLimit={limit}
onValueChange={value => rangeSetter(value)}
/>
<Text>{secondRange}</Text>
<Slider
minimumValue={0}
value={equalValueSplit}
maximumValue={100}
upperLimit={limitSecond}
onValueChange={value => rangeSetterSecond(value)}
/>
</View>
);
};
export default SplitBillPage;
Now I'm confused on how to create the logic when the number increases above 2. Here I've used 1 states for each user which are limit and limitSecond, but this doesnt seem efficient when I've 10 users and me creating an individual state for each of them and the using state1 +state2 + ... <101.
So I need a way to solve this logic when the number increases from 2.

Related

I need function that calculates amount of games required to reach certain rating with defined winrate

function ChangePts(pts) {
this.pts = pts;
this.win = function() {
console.log(this.pts + 30)
return this.pts += 30
}
this.lose = function() {
console.log(this.pts - 30)
return this.pts -= 30;
}
};
I made it to calculate how many games you need to lose, to get certain ratingw with while loop. This implies that win% is 0%, how do I calculate amount of games if we declare starting pts for example 5000, how many games it takes to get to 1000 pts if your winrate is 27%
P.S.: For this case I need only negative amount of win%.
You can just calculate it like this (variables should be explanation enough). There is no real coding necessary for it, just math calculations
const winRate = 0.27
const loseRate = 1-winRate
const pointsWin = 30
const pointsLose = 30
const startingPoints = 5000
const targetPoints = 1000
const pointsGainedOnAverage = pointsWin*winRate - pointsLose*loseRate
const pointsDistance = targetPoints - startingPoints
const games = pointsDistance / pointsGainedOnAverage
console.log('games', games)

Create a function that returns the next/prev 4 results in an array of objects

I'm trying to create a function that renders the next & prev 4 results in an array of objects onClick. Currently I am only returning the first 4 items and their images and adjusting the values in the return statement onClick and am not happy with this solution. Any pointers?
const ImageSlider = ({ loadData, data, setData }) => {
const [current, setCurrent] = useState(0);
const length = data.length;
const [test, setTest] = useState(0);
const [another, setAnother] = useState(4);
useEffect(() => {
loadData().then((data) => setData(data));
}, []);
const getNextSlide = () => {
setCurrent(current === length - 1 ? 0 : current + 1);
if (current / 3 === 1) {
setTest(test + 4);
setAnother(another + 4);
}
};
const getPrevSlide = () => {
setCurrent(current === 0 ? length - 1 : current - 1);
// setTest(test - 1);
// setAnother(another - 1);
};
console.log(current);
if (!Array.isArray(data) || length <= 0) {
return null;
// Need that error message here
}
return (
<div className="slider">
<FaArrowCircleLeft className="slider-left-arrow" onClick={getPrevSlide} />
<FaArrowCircleRight
className="slider-right-arrow"
onClick={getNextSlide}
/>
{data.slice(test, another).map((program, index) => {
return (
<div
className={
index === current
? "slider-active-program"
: "slider-inactive-program"
}
key={index}
>
<img
src={program.image}
alt="program"
className="slider-program-image"
/>
</div>
);
})}
</div>
);
};
export default ImageSlider;
This may be one possible approach to achieve the desired objective:
const ImageSlider = ({ loadData, data, setData }) => {
const imageLength = 4; // number of images to be shown per-page
const totalImages = data.length;
const [startAt, setStartAt] = useSate(0); // start rendering from image-0
useEffect(() => {
loadData().then((data) => setData(data));
}, []);
const switchImages = (direction = 'forward') => (
setStartAt(prev => {
if (direction === 'forward') {
// if there are more images, reposition 'startAt' to next set
if (prev + imageLength < totalImages) return prev + imageLength;
return 0; // reset back to image-0
} else {
// if there is previous set, reposition to it's start
if (prev - imageLength >= 0) return prev - imageLength;
// otherwise, reposition to the start of the last-set
return (Math.floor((totalImages - 1) / imageLength) * imageLength)
};
})
);
return (
<div className="slider">
<FaArrowCircleLeft
className="slider-left-arrow"
onClick={() => switchImages('reverse')}
/>
<FaArrowCircleRight
className="slider-right-arrow"
onClick={() => switchImages()}
/>
{Array.isArray(data) && data.filter((el, idx) => idx >= startAt && idx < (startAt + imageLength)).map((program, index) => {
return (
<div
className={
index === current
? "slider-active-program"
: "slider-inactive-program"
}
key={index}
>
<img
src={program.image}
alt="program"
className="slider-program-image"
/>
</div>
);
})}
</div>
);
};
export default ImageSlider;
Explanation
imageLength is fixed (set to 4)
startAt is a state-variable (initially set to 0)
data is rendered on return only when it is an Array
switchImages is a method invoked on both left-arrow and right-arrow click-events (changing the parameter direction to reverse on left-arrow)
startAt is updated based on the direction, the prev value, the totalImages and imageLength.
Suggestion
If one needs to render a message to the user that the data array is either not-yet-loaded or it is empty, it may be acccomplished by changing the {Array.isArray(data) && .... into a conditional such as {Array.isArray(data) ? ....<existing> : <some message here>.
Warning
The above code is only an approach. It is not tested & may contain syntax errors or other issues. Please use comments to share feedback and this answer may be updated accordingly.
I don't think you have to change much about your approach to get what you're looking for here.
To render the images to the left and right of your current selection and allow looping from the front to the back (as it looks like you want to do), we can paste a copy of the array to the front and back and select the middle slice. It's a little bit like this:
The implementation would just require you to replace the current .map(...) method in your return statement with this one:
[...data, ...data, ...data].slice(
length + current - peekLeft,
length + current + peekRight + 1
).map((program, index) => {
...
}
And then you can also remove the test and another state objects as well since they aren't necessary. You just need to add the peekLeft and peekRight or replace them inline inside the .map(...) statement. The first option may look something like this:
const peekLeft = 4;
const peekRight = 4;
const ImageSlider = ({ loadData, data, setData }) => {
...
}

Total percentages of three input fields

I'm trying to set 100% in total on three input fields. So the idea is when the user sets 30 in one field, in the other two automatically to split numbers up to 100. So in the second field needs to be 35 and in the third one 35. Also, if any number is changed after that, the other two will be adjusted accordingly.
The number in any of those three fields can't exceed 100. I think you are getting the idea of what I'm trying to achieve.
Here is what I have tried so far (I'm not so experienced in JavaScript, but I know that this is a bad approach):
HTML:
<input class="pp-w-100 pp-range-numbers pp-first-field" type="number" name="pp-age-old-2030">
<input class="pp-w-100 pp-range-numbers pp-second-field" type="number" name="pp-age-old-3040">
<input class="pp-w-100 pp-range-numbers pp-third-field" type="number" name="pp-age-old-4050">
JS:
const firstField = document.querySelector('.pp-first-field');
const secondField = document.querySelector('.pp-second-field');
const thirdField = document.querySelector('.pp-third-field');
const totalPercentage = 100;
firstField.addEventListener('change', () => {
let intValue = parseInt(firstField.value);
if (intValue < totalPercentage) {
let getFirstValue = totalPercentage - intValue;
secondField.value = getFirstValue / 2;
thirdField.value = getFirstValue / 2;
}
});
secondField.addEventListener('change', () => {
let intValue = parseInt(firstField.value) + parseInt(secondField.value);
if (intValue < totalPercentage) {
thirdField.value = totalPercentage - intValue;
}
});
The code above is not working as expected.
Thank you.

setAttribute not working consistently on change event

I'm having trouble getting a set of change events to work consistently: I have a table set up that checks to see whether either an input has been updated and then, depending on what input gets updated, the code submitted below either changes the margin or the price.
If I only change the price, I consistently calculate margin correctly, and if I only change the margin, I consistently calculate price correctly. If I change one and then the other, the second setAttribute doesn't work. (e.g. changing the price updates the margin, but, after that, changing margin, does not update price).
I'm new to JS, but I've tried debugging and can't seem to nail this down. Any help would be much appreciated.
see link for codepen: https://codepen.io/skeanerw/pen/mdBpppE
function calcBuySellMargin(bracket, rowID) {
const vendPriceID = document.getElementById(`VendPrice_${bracket}_${rowID}`);
const sellPriceID = document.getElementById(`ItemPrice_${bracket}_${rowID}`);
const marginID = document.getElementById(`Margin_${bracket}_${rowID}`);
const priceMeth = document.getElementById(`priceMeth_${rowID}`).innerText;
const vpUOM = document.getElementById(`vpUOM_${rowID}`).innerText;
//when we change the margin adjust the sell price.
marginID.addEventListener('change', () => {
let marginValue = (marginID.value) / 100
let vendPriceValue = (vpUOM === 'M') ? vendPriceID.value / 1000 : vendPriceID.value;
let sellPriceVal = (priceMeth === 'M') ? ((vendPriceValue / (1 - marginValue)) * 1000).toFixed(0) : (vendPriceValue / (1 - marginValue)).toFixed(3);
sellPriceID.setAttribute('value', parseFloat(sellPriceVal));
})
//when we change the buy price or the sell price, adjust the margin.
function setMargin() {
let vendPriceValue = (vpUOM === 'M') ? vendPriceID.value / 1000 : vendPriceID.value;
let sellPriceVal = (priceMeth === 'M') ? sellPriceID.value / 1000 : sellPriceID.value;
const marginValue = parseFloat( (sellPriceVal - vendPriceValue) / sellPriceVal * 100 )
marginID.setAttribute('value', parseFloat(marginValue).toFixed(0));
}
vendPriceID.addEventListener('change', () => {
setMargin()
})
sellPriceID.addEventListener('change', () => {
setMargin()
})
}
window.onload = (event) => {
document.querySelectorAll('.marginDisplay').forEach(element => {
const rowID = (element.id.replace(/Margin_\d_/, ''));
let bracket = (element.id.replace(/Margin_/,''))
bracket = (bracket.match(/^[0-9]/, ''))
calcBuySellMargin(bracket, rowID);
})
}
I was able to resolve by replacing the setAttribute methods:
marginID.addEventListener('change', () => {
let marginValue = (marginID.value) / 100
let vendPriceValue = (vpUOM === 'M') ? vendPriceID.value / 1000 : vendPriceID.value;
let sellPriceVal = (priceMeth === 'M') ? ((vendPriceValue / (1 - marginValue)) * 1000).toFixed(0) : (vendPriceValue / (1 - marginValue)).toFixed(3);
sellPriceID.value = sellPriceVal;
})
function setMargin() {
let vendPriceValue = (vpUOM === 'M') ? vendPriceID.value / 1000 : vendPriceID.value;
let sellPriceVal = (priceMeth === 'M') ? sellPriceID.value / 1000 : sellPriceID.value;
const marginValue = parseFloat( (sellPriceVal - vendPriceValue) / sellPriceVal * 100 )
marginID.value = marginValue
}

Coin Change Algorithm JS

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)
*/

Categories