Incrementing and padding in Javascript - javascript

I'm trying to write an app that allows a user to increment a series of numbers. When I let i = 1, all is well and my sequence output is perfect. Unfortunately, the series will rarely start with 1, it will start with a user defined number. And that's where I'm getting hung up:
const totalEl = document.querySelector('#total');
const perEl = document.querySelector('#per');
const numEl = document.querySelector('#num');
const calculateEl = document.querySelector('#calculate');
const outputEl = document.querySelector('#output');
calculateEl.addEventListener('click', onCalculateButtonClick);
function onCalculateButtonClick() {
const total = Number(totalEl.value);
const numPerRoll = Number(perEl.value);
const num = Number(numEl.value);
const numPerBatch = numPerRoll * 2;
const batches = Math.ceil(total / numPerBatch);
let output = `Total batches: ${batches}\nTotal rolls: ${batches * 2}\n\n`;
for (let i = 1; i <= batches; i++) {
output += `Batch ${i}A -- ${(numPerBatch * (i - 1)) + 1}-${(numPerBatch * (i - 1)) + numPerRoll}\n`;
output += `Batch ${i}B -- ${(numPerBatch * (i - 1)) + numPerRoll + 1}-${numPerBatch * i}\n`;
}
outputEl.innerText = output;
}
body {
font-family: system-ui;
background: linear-gradient(to bottom, azure, #a4ffff);
color: black;
height: 100vh;
margin: 0;
display: grid;
}
<h1 style="text-align: center;">👋 Generator 0.1 👋</h1>
<p style="text-align: center;">Please enter your values in the dashboard<br> Note: in order to print a complete 2-up batch, the order may be rounded up</p>
<div><label>Order total <input id="total" value="50000"/></label></div>
<div><label>Total per sequence <input id="per" value="1500"/></label></div>
<div><label>Starting number <input id="num" value="1"/></label></div>
<div><label>Prefix <input id="prefix" value="ABC"/></label></div>
<div><button id="calculate">Calculate</button></div>
<br/>
<div id="output"></div>
How do I let i = a user defined number? I'm missing something fundamentally obvious here.
I also need to turn this number into a string (I think) and pad it out to 8 digits... I've tried adding something like this in with lots of hilarity, hair pulling and no results:
let num = i;
let text = num.toString();
let padded = text.padStart(8,"0");
I think what I tried was something like this:
for (let i = num; i <= batches; text = num.toString(); padded = text.padStart(8,"0"); {
I've got a kind of working version of this at codepen:
https://codepen.io/swartzfeger/pen/yLjgEpK
More in-depth dump of what I'm trying to accomplish
TL;DR What I need: generate a series of UPC codes that are incremented and split into batches.
Let's say our customer wants 10,000 UPCs, with 1000 UPCs per roll (these are being printed on film and wound onto rolls).
That would be a total of 10 rolls. But each 2x 1000 sequence can be printed at once, printing 2 up. So there are a total of 5 print batches.
That's not the hard part since the example is cut and dried. Most client jobs vary greatly, and we often have to print extra so we're not wasting film -- ie, we always want a full batch and not printing just 1 up for the batch. A partial batch would leave us with 1 roll of empty non-printed film.
ie, an order of 50,000 total, 1500 per sequence. 50k / 3000 per batch leaves us with 16.66 batches. So we would want to round that up to 17. So we would want to bump the print total to 51k (17 x 3000 = 51,000.
What I need to do is create a dashboard where the operator enters the job total (50,000) and the total per sequence (1500). The dashboard would then spit out you're running a job total of 51,000, 17 batches/34 rolls total. It would then give the operator the print breakdown --
Batch 1A -- 0000-1500
Batch 1B -- 1501-3000
Batch 2A -- 3001-4500
Batch 2B -- 4501-6000 etc
HOWEVER -- we would rarely begin a sequence with i =1 -- we would be picking up from where our last job ended, so the user would enter the new beginning sequence number. So I wouldn't be incrementing from 0 or 1, but an arbitrary number like 6293.
AND -- these numbers are of going to be a fixed length (usually 8 digits). So a starting sequence number of 6293 would have to be padded out to 00006293.

Make a small utility function like so:
const z = (number, size) => number.toString().padStart(size, "0");
Then assign some variables to the function's returns and then interpolate those variables into the template literals like so:
let A1 = z(AB, 8);
let A2 = z(AB + (now / 2) - 1, 8);
let B1 = z(AB + (now / 2), 8);
let B2 = z(AB + now - 1, 8);
output += `Batch ${z(i + 1, 2)}A -- ${A1}-${A2}\n`;
output += `Batch ${z(i + 1, 2)}B -- ${B1}-${B2}\n`;
I removed the for loop and added .reduce().
Details are commented in example
const genForm = document.forms.generator;
const fc = genForm.elements;
const totalFC = fc.total;
const perFC = fc.per;
const numFC = fc.num;
const outputFC = fc.output;
// Utility function for padding zeros
const z = (number, size) => number.toString().padStart(size, "0");
genForm.addEventListener('submit', onCalculate);
function onCalculate(e) {
e.preventDefault();
const total = Number(totalFC.value);
const numPerRoll = Number(perFC.value);
const init = Number(numFC.value);
const numPerBatch = numPerRoll * 2;
const batches = Math.ceil(total / numPerBatch);
let output = `Total batches: ${batches}\nTotal rolls: ${batches*2}\n\n`;
// Generate an array of batches
const totalBatches = [...new Array(batches)].fill(numPerBatch);
/**
* On each iteration of totalBatches by .reduce()...
* >AB< is the accumulator, it will keep track of it's own value
* >now< is the current value (>numPerBatch<)
* >AB< starts as >init<, defaulting to 1
* >A1< - >A2< is >numPerRoll<
* >B1< - >B2< is >numPerRoll<
* >output< is rendered to display ranges A and B
* >now< is added to >AB<
* The returned value isn't used but needs to be defined so
* that the accumulator returns on the next iteration increased
*/
const final = totalBatches.reduce((AB, now, idx) => {
let A1 = z(AB, 8);
let A2 = z(AB + (now / 2) - 1, 8);
let B1 = z(AB + (now / 2), 8);
let B2 = z(AB + now - 1, 8);
output += `Batch ${z(idx + 1, 2)}A -- ${A1}-${A2}\n`;
output += `Batch ${z(idx + 1, 2)}B -- ${B1}-${B2}\n`;
return AB + now;
}, init);
outputFC.innerText = output;
}
html {
font: 300 2ch/1.15 'Segoe UI'
}
body {
min-height: 100vh;
margin: 0 auto;
background: linear-gradient(to bottom, azure, #a4ffff);
}
form {
display: flex;
justify-content: center;
align-items: center;
padding-top: 20px;
}
fieldset {
position: relative;
width: min(70vw, 500px);
}
input,
button {
font: inherit;
}
input {
width: 12rem;
}
button {
position: absolute;
right: 30px;
cursor: pointer;
}
label {
display: flex;
justify-content: space-between;
margin: 0 20px 15px 0;
}
label sup {
align-self: flex-start;
}
legend {
font-size: 1.5rem;
}
legend b,
[type="number"],
output {
font-family: Consolas;
}
[type="number"] {
text-align: right;
}
.note {
margin-left: 0.5ch;
font-size: 0.75rem;
text-indent: -1ch;
}
sup {
color: tomato
}
<form id="generator">
<fieldset>
<legend><b>❚❘❘❙⦀</b>Generator <b>0.1❚⦀⦀∥❙❙❘❘❘⦀∥</b></legend>
<p>Please enter your values in the dashboard</p>
<label><span>Order total<sup>⚝</sup></span> <input id="total" type="number" value="50000"/>
</label>
<label>Total per sequence <input id="per" type="number" value="1500"/></label>
<label>Starting number <input id="num" type="number" value="1"/></label>
<label>Prefix <input id="prefix" placeholder="ABC"/></label>
<button>Calculate</button>
<p class='note'><sup>⚝</sup>Note: in order to print a complete 2-up batch, <br>the order may be rounded up.</p>
<output id="output"></output>
</fieldset>
</form>

The const z utility #zer00ne added cleaned things up for padding the digits
const z = (number, size) => number.toString().padStart(size, "0");
The user defined number sets the starting number of the sequence, and size determines the length in digits of the sequence. The "0" determines what the sequence will be padded with to reach the required final length.
ex, the user defines '123' as the starting the number of the sequence; const z turns this into '00000123' (an 8-digit sequence)
Defining the value >now< for the accumulator/iteration made things work flawlessly.
By using AB + now, the batches were able to increment properly:
// Generate an array of batches
const totalBatches = [...new Array(batches)].fill(numPerBatch);
// * On each iteration of totalBatches by .reduce()...
// * >AB< is the accumulator, it will keep track of it's own value
// * >now< is the current value (>numPerBatch<)
// * >AB< starts as >init<, defaulting to 1
// * >A1< - >A2< is >numPerRoll<
// * >B1< - >B2< is >numPerRoll<
// * >output< is rendered to display ranges A and B
// * >now< is added to >AB<
// * The returned value isn't used but needs to be defined so
// * that the accumulator returns on the next iteration increased
const final = totalBatches.reduce((AB, now, idx) => {
let A1 = z(AB, 8);
let A2 = z(AB + now / 2 - 1, 8);
let B1 = z(AB + now / 2, 8);
let B2 = z(AB + now - 1, 8);
and for the final output:
output += `Batch ${z(idx + 1, 2)}A | ${prefix} ${A1} - ${prefix} ${A2}\n`;
output += `Batch ${z(idx + 1, 2)}B | ${prefix} ${B1} - ${prefix} ${B2}\n`;
return AB + now;
}, init);
outputEl.innerText = output;
}

Related

Jquery price increase by slab

i am working on some project and I'm facing some issue
actually i want price increase by slap like i am allowing customer to select guest for booking for e.g the main price of product is 100 and additional price of guest is 50 and limit of guest is 4 when customer select 5 guest then the main price should be add in total price after when customer select 6 guest then only add 50 price i am sharing some code but its not working properly.
main price = 100
addintion price = 50
Guest
Price
1
100
2
150
3
200
4
250
5
350 add main price again (100)
6
400
7
450
8
500
9
600 add main price again (100)
10
650
group_size = 4;
increased_group_size = 4;
total_guests = me.guests;
if(total_guests<=group_size){
increased_group_size -= group_size;
if(increased_group_size==0){
increased_group_size += group_size;
}
}
if(total_guests>increased_group_size){
main_price = main_price;
increased_group_size += group_size;
final_price += main_price;
}
else{
final_price1 = total_guests * me.addtional_price;
}
total += final_price1 + final_price + me.price;
I tried the above code but not working properly.
Divide the number of additional guests by the group size. For each group, add the difference between the main price and additional price.
function calculate_price(total_guests) {
let additional_guests = total_guests - 1;
let group_size = 4;
let additional_groups = Math.floor(additional_guests / group_size);
let main_price = 100;
let additional_price = 50;
let total_price = main_price + additional_guests * additional_price + additional_groups * (main_price - additional_price);
return total_price;
}
for (let guests = 1; guests <= 10; guests++) {
console.log(`Guests = ${guests} Price = ${calculate_price(guests)}`);
}
Essentially what you need to do is check whether the current number is divisible by your max number. Then add one number if it is, and another number if it isn't.
Here's a working example.
const guestNum = $(".guests-total");
const total = $(".total");
$(".button").on("click", (e) => {
let numMult = 1;
const num = parseInt(guestNum.text());
let curTotal = parseInt(total.text());
let testNum = num + 1;
if (e.target.classList[1] === "min") {
numMult = -1;
testNum = num;
if (num === 0) {
return;
}
}
while (testNum > 0) {
testNum = testNum - 5;
}
if (testNum !== 0) {
curTotal += numMult * 50;
guestNum.text(num + numMult * 1);
total.text(curTotal);
return;
}
curTotal += numMult * 100;
guestNum.text(num + numMult * 1);
total.text(curTotal);
return;
});
.wrapper {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 5vw;
gap: 2vw;
}
.row-one {
display: flex;
justify-content: center;
align-items: center;
gap: 3vw;
}
.icon-test {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div class="wrapper">
<div class="row-one">
<div class="button min">-</div>
<div class="guests-total">0</div>
<div class="button add">+</div>
</div>
<div class="row-two">
<div class="money-total">Total: $<span class="total">0</span></div>
</div>
</div>
</body>
</html>
Let's break this down.
Here we get the DOM elements for the number of guests and the sub-total of money.
const guestNum = $(".guests-total");
const total = $(".total");
Then we register an event listener for a click on the button to either add or subtract a guest.
$(".button").on("click", (e) => {
I set a variable called numMult that I will multiply all my sub-total numbers by to either subtract or add money (I'll explain that more later).
let numMult = 1;
Then I get the actual text contained in the DOM elements and convert those to integers so we can add or subtract from them.
const num = parseInt(guestNum.text());
let curTotal = parseInt(total.text());
Finally I make a new variable that will be used to test if the current number is a multiple of 5 or not.
let testNum = num + 1;
Here we check if the button that was clicked is the subtract button or the add button, if it's the subtract button then we will be multiplying all our numbers by -1 so that it subtracts from the subtotal instead of adds to it.
If the current number is 0 we return because there obviously can't be negative guests.
if (e.target.classList[1] === "min") {
numMult = -1;
testNum = num;
if (num === 0) {
return;
}
}
Here we subtract from the total number of guests until num is less than or equal to zero.
while (testNum > 0) {
testNum = testNum - 5;
}
If our test number doesn't come out as zero, we know that the number isn't 5 or divisible by 5, so we add or subtract 50.
if (testNum !== 0) {
curTotal += numMult * 50;
guestNum.text(num + numMult * 1);
total.text(curTotal);
return;
}
Otherwise, if our test number is 0, we know that the number of guests is either 5 or a multiple of 5, so we add 100.
curTotal += numMult * 100;
guestNum.text(num + numMult * 1);
total.text(curTotal);
return;
I hope this helps out.

How do I get innerHTML to display my output? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 11 months ago.
Improve this question
I'm fairly new to JS and I cannot figure out why my innerHTML is not displaying any output to my 4 input text fields. The ID values for all of the text fields match to the document.getElementByID values, but they aren't getting displayed.
document.getElementById('calculate').addEventListener('click', calculateCoins)
function calculateCoins (){
//converts cents value from string to Int
var cent = parseInt(document.getElementById('cents').value, 10);
/*
calculates # of quarters, displays # of quarters,
and calculates the remainder money
*/
let quarterAmount = Math.floor(cent / 25);
document.getElementById('quarters').innerHTML = quarterAmount;
let quarterRemainder = cent % 25;
/*
calculates # of dimes, displays # of dimes,
and calculates the remainder money
*/
let dimeAmount = Math.floor(quarterRemainder / 10);
document.getElementById('dimes').innerHTML = dimeAmount;
let dimeRemainder = quarterRemainder % 10;
/*
calculates # of nickels, displays # of nickels,
and calculates the remainder money
*/
let nickelAmount = Math.floor(dimeRemainder / 5);
document.getElementById('nickels').innerHTML = nickelAmount;
let nickelRemainder = dimeRemainder % 5;
/*
calculates # of pennies and displays # of pennies
*/
let pennyAmount = Math.floor(nickelRemainder / 1);
document.getElementById('pennies').innerHTML = pennyAmount;
console.log(quarterAmount);
console.log(quarterRemainder);
console.log(dimeAmount);
console.log(dimeRemainder);
console.log(nickelAmount);
console.log(nickelRemainder);
console.log(pennyAmount);
}
To update the input field use .value not .innerHTML
document.getElementById('pennies').value = pennyAmount;
For forms, you need to use the value property instead of the innerHTML property. This is because innerHTML changes the inside code of the tags, while value changes the value attribute of the input.
An example of this is below.
document.querySelector("#text").value = "I'm text!";
<input type="text" name="text" id="text" placeholder="Enter any text here..." />
Also, the value property can also be read to see the current text inputted by the user.
Extra: I also just noticed the below line from your code.
let pennyAmount = Math.floor(nickelRemainder / 1);
This code is actually not nessecary, as division by one is basically just the same number, and flooring it will not change the result.
This may be one possible solution to achieve the desired objective:
This uses document.getElementById(k).value = <<calculated value>>; rather than innerHTML.
Code Snippet
const coins = Object.fromEntries('quarters:25, dimes:10, nickels:5, pennies:1'
.split(',')
.map(ob => ob.split(':'))
.map(([k, v]) => ([k.trim(), {divisor: v, val: 0}])));
const getQR = (num, divisor) => ([
Math.floor(num / divisor), num % divisor
]);
const calculateCoins = () => {
const userInput = +document.getElementById('cents').value.toString();
coins.quarters.val = userInput;
Object.entries(coins).
forEach(([k, {divisor, val}], idx, selfArr) => {
const [d, r] = getQR(val, divisor);
document.getElementById(k).value = d;
if (idx + 1 < selfArr.length) selfArr[idx+1][1].val = r;
});
};
document.getElementById('calculate').addEventListener('click', calculateCoins)
.outer { display: flex; flex-direction: column }
input { width: fit-content; margin: 25px 5px 5px; border: 2px solid black; }
button { width: fit-content; margin: 10px; }
<div class="outer">
<input id="cents">Enter num of cents</input>
<input id="quarters" >Num of quarters</input>
<input id="dimes" >Num of dimes</input>
<input id="nickels" >Num of nickels</input>
<input id="pennies" >Num of pennies</input>
<button id="calculate">Get coins</button>
</div>
Explanation
This solution uses a coins object generated by using known information
Each coin has multiple attributes
The entries of this object are iterated in order to obtain the HTML element information required to render.
Results of the calculation are stored in val for next iteration.
Take a look at this, it worked for me
// Elements
const calculate = document.getElementById("calculate");
const centsEl = document.getElementById("cents");
const quartersEl = document.getElementById("quarters");
const dimesEl = document.getElementById("dimes");
const nickelsEl = document.getElementById("nickels");
const penniesEl = document.getElementById("pennies");
calculate.addEventListener("click", calculateCoins);
function calculateCoins() {
const cents = Number(centsEl.value);
// Quarters calc
let quarterAmount = Math.floor(cents / 25);
quartersEl.innerHTML = quarterAmount;
let quarterRemainder = cents % 25;
// Dimes calc
let dimeAmount = Math.floor(quarterRemainder / 10);
dimesEl.innerHTML = dimeAmount;
let dimeRemainder = quarterRemainder % 10;
// Nickels calc
let nickelAmount = Math.floor(dimeRemainder / 5);
nickelsEl.innerHTML = nickelAmount;
let nickelRemainder = dimeRemainder % 5;
// Pennies calc
let pennyAmount = Math.floor(nickelRemainder / 1);
penniesEl.innerHTML = pennyAmount;
}
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app">
<input type="text" id="cents" />
<button id="calculate">Calculate</button>
<h5>Quarters</h5>
<div id="quarters"></div>
<h5>Dimes</h5>
<div id="dimes"></div>
<h5>Nickels</h5>
<div id="nickels"></div>
<h5>Pennies</h5>
<div id="pennies"></div>
</div>
<script src="src/index.js"></script>
</body>
</html>

Javascript | How to split one semi-random number into three random numbers, whose additive value add to initial number

My goal: Given a specific rgb value such as (215, 183, 71), I want to generate a random number between 20-30, and divide that number into three additive parts that add to the initial number. From there, each number should be subtracted from their respective color value, from a specified color.
Unless I'm lacking a significant amount of brainpower currently, this seems like it requires a somewhat creative solution, which is why I leave it in the hands of whomever may read this.
E.G:
var colorDifference=27;
var RGB=rgb(100-differenceR,100-differenceG,100-differenceB,1);
function colorDiffValues(){
differenceR=5;
differenceG=13;
differenceB=9;
}
However, clearly not with those exact value. so it would have to be something such as
var colorDiff= random number between 20 to 30
var RGB=rgb(r-diffR, g-diffG,b-diffB);
function colorDiffValues(){
diffR= random value
diffG= random value
diffB= random value
// where diffR+diffG+diffB=colorDiff
// AND diffR,diffG, and diffB are all positive numbers.
}
goodnight!!
This is the relevant logic that would split a random value intro three random values. You only need to specify what is the minimum value for each of the three numbers.
<script>
// get random number between 20 and 30
let colorDifference = 20 + Math.floor(Math.random() * 11);
// defines the smallest value for each of the generated numbers
// if set to 3, then 25 split into 10, 2, 13 wouldn't be valid as one of them is lower than 3
let smallestValue = 3;
// generate numbers
let totalRange = colorDifference;
let numberOne = smallestValue + Math.floor(Math.random() * (totalRange - 3*smallestValue));
totalRange -= numberOne;
let numberTwo = smallestValue + Math.floor(Math.random() * (totalRange - 2*smallestValue));
let numberThree = colorDifference - (numberOne + numberTwo);
console.log('Value ' + colorDifference + ' was split into ' + numberOne + ',' + numberTwo + ', ' + numberThree);
</script>
Here you have an example where I use it to paint a div with a random color:
<div class="box" id="original"></div>
<div class="box" id="generated"></div>
<span id="result"></span>
<style>
.box{
width:100px;
height:100px;
border:1px solid #000;
}
</style>
<script>
// values for original
let red = 200;
let green = 200;
let blue = 200;
// get random number between 20 and 30
let colorDifference = 20 + Math.floor(Math.random() * 11);
// defines the smallest value for each of the generated numbers
// if set to 3, then 25 split into 10, 2, 13 wouldn't be valid as one of them is lower than 3
let smallestValue = 3;
// generate numbers
let totalRange = colorDifference;
let numberOne = smallestValue + Math.floor(Math.random() * (totalRange - 3*smallestValue));
totalRange -= numberOne;
let numberTwo = smallestValue + Math.floor(Math.random() * (totalRange - 2*smallestValue));
let numberThree = colorDifference - (numberOne + numberTwo);
// generate colors
let newRed = red - numberOne;
let newGreen = green - numberTwo;
let newBlue = blue - numberThree;
// set colors
document.getElementById('result').innerHTML = colorDifference + ' split into ' + numberOne + ', ' + numberTwo + ', ' + numberThree;
document.getElementById('original').style.backgroundColor = 'rgba('+red+','+green+','+blue+',1)';
document.getElementById('generated').style.backgroundColor = 'rgba('+newRed+','+newGreen+','+newBlue+',1)';
</script>

Javascript / JQuery - Favor Number Range In Math.random() [duplicate]

I'm trying to devise a (good) way to choose a random number from a range of possible numbers where each number in the range is given a weight. To put it simply: given the range of numbers (0,1,2) choose a number where 0 has an 80% probability of being selected, 1 has a 10% chance and 2 has a 10% chance.
It's been about 8 years since my college stats class, so you can imagine the proper formula for this escapes me at the moment.
Here's the 'cheap and dirty' method that I came up with. This solution uses ColdFusion. Yours may use whatever language you'd like. I'm a programmer, I think I can handle porting it. Ultimately my solution needs to be in Groovy - I wrote this one in ColdFusion because it's easy to quickly write/test in CF.
public function weightedRandom( Struct options ) {
var tempArr = [];
for( var o in arguments.options )
{
var weight = arguments.options[ o ] * 10;
for ( var i = 1; i<= weight; i++ )
{
arrayAppend( tempArr, o );
}
}
return tempArr[ randRange( 1, arrayLen( tempArr ) ) ];
}
// test it
opts = { 0=.8, 1=.1, 2=.1 };
for( x = 1; x<=10; x++ )
{
writeDump( weightedRandom( opts ) );
}
I'm looking for better solutions, please suggest improvements or alternatives.
Rejection sampling (such as in your solution) is the first thing that comes to mind, whereby you build a lookup table with elements populated by their weight distribution, then pick a random location in the table and return it. As an implementation choice, I would make a higher order function which takes a spec and returns a function which returns values based on the distribution in the spec, this way you avoid having to build the table for each call. The downsides are that the algorithmic performance of building the table is linear by the number of items and there could potentially be a lot of memory usage for large specs (or those with members with very small or precise weights, e.g. {0:0.99999, 1:0.00001}). The upside is that picking a value has constant time, which might be desirable if performance is critical. In JavaScript:
function weightedRand(spec) {
var i, j, table=[];
for (i in spec) {
// The constant 10 below should be computed based on the
// weights in the spec for a correct and optimal table size.
// E.g. the spec {0:0.999, 1:0.001} will break this impl.
for (j=0; j<spec[i]*10; j++) {
table.push(i);
}
}
return function() {
return table[Math.floor(Math.random() * table.length)];
}
}
var rand012 = weightedRand({0:0.8, 1:0.1, 2:0.1});
rand012(); // random in distribution...
Another strategy is to pick a random number in [0,1) and iterate over the weight specification summing the weights, if the random number is less than the sum then return the associated value. Of course, this assumes that the weights sum to one. This solution has no up-front costs but has average algorithmic performance linear by the number of entries in the spec. For example, in JavaScript:
function weightedRand2(spec) {
var i, sum=0, r=Math.random();
for (i in spec) {
sum += spec[i];
if (r <= sum) return i;
}
}
weightedRand2({0:0.8, 1:0.1, 2:0.1}); // random in distribution...
Generate a random number R between 0 and 1.
If R in [0, 0.1) -> 1
If R in [0.1, 0.2) -> 2
If R in [0.2, 1] -> 3
If you can't directly get a number between 0 and 1, generate a number in a range that will produce as much precision as you want. For example, if you have the weights for
(1, 83.7%) and (2, 16.3%), roll a number from 1 to 1000. 1-837 is a 1. 838-1000 is 2.
I use the following
function weightedRandom(min, max) {
return Math.round(max / (Math.random() * max + min));
}
This is my go-to "weighted" random, where I use an inverse function of "x" (where x is a random between min and max) to generate a weighted result, where the minimum is the most heavy element, and the maximum the lightest (least chances of getting the result)
So basically, using weightedRandom(1, 5) means the chances of getting a 1 are higher than a 2 which are higher than a 3, which are higher than a 4, which are higher than a 5.
Might not be useful for your use case but probably useful for people googling this same question.
After a 100 iterations try, it gave me:
==================
| Result | Times |
==================
| 1 | 55 |
| 2 | 28 |
| 3 | 8 |
| 4 | 7 |
| 5 | 2 |
==================
Here are 3 solutions in javascript since I'm not sure which language you want it in. Depending on your needs one of the first two might work, but the the third one is probably the easiest to implement with large sets of numbers.
function randomSimple(){
return [0,0,0,0,0,0,0,0,1,2][Math.floor(Math.random()*10)];
}
function randomCase(){
var n=Math.floor(Math.random()*100)
switch(n){
case n<80:
return 0;
case n<90:
return 1;
case n<100:
return 2;
}
}
function randomLoop(weight,num){
var n=Math.floor(Math.random()*100),amt=0;
for(var i=0;i<weight.length;i++){
//amt+=weight[i]; *alternative method
//if(n<amt){
if(n<weight[i]){
return num[i];
}
}
}
weight=[80,90,100];
//weight=[80,10,10]; *alternative method
num=[0,1,2]
8 years late but here's my solution in 4 lines.
Prepare an array of probability mass function such that
pmf[array_index] = P(X=array_index):
var pmf = [0.8, 0.1, 0.1]
Prepare an array for the corresponding cumulative distribution function such that
cdf[array_index] = F(X=array_index):
var cdf = pmf.map((sum => value => sum += value)(0))
// [0.8, 0.9, 1]
3a) Generate a random number.
3b) Get an array of elements that are more than or equal to this number.
3c) Return its length.
var r = Math.random()
cdf.filter(el => r >= el).length
This is more or less a generic-ized version of what #trinithis wrote, in Java: I did it with ints rather than floats to avoid messy rounding errors.
static class Weighting {
int value;
int weighting;
public Weighting(int v, int w) {
this.value = v;
this.weighting = w;
}
}
public static int weightedRandom(List<Weighting> weightingOptions) {
//determine sum of all weightings
int total = 0;
for (Weighting w : weightingOptions) {
total += w.weighting;
}
//select a random value between 0 and our total
int random = new Random().nextInt(total);
//loop thru our weightings until we arrive at the correct one
int current = 0;
for (Weighting w : weightingOptions) {
current += w.weighting;
if (random < current)
return w.value;
}
//shouldn't happen.
return -1;
}
public static void main(String[] args) {
List<Weighting> weightings = new ArrayList<Weighting>();
weightings.add(new Weighting(0, 8));
weightings.add(new Weighting(1, 1));
weightings.add(new Weighting(2, 1));
for (int i = 0; i < 100; i++) {
System.out.println(weightedRandom(weightings));
}
}
How about
int [ ] numbers = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 2 } ;
then you can randomly select from numbers and 0 will have an 80% chance, 1 10%, and 2 10%
This one is in Mathematica, but it's easy to copy to another language, I use it in my games and it can handle decimal weights:
weights = {0.5,1,2}; // The weights
weights = N#weights/Total#weights // Normalize weights so that the list's sum is always 1.
min = 0; // First min value should be 0
max = weights[[1]]; // First max value should be the first element of the newly created weights list. Note that in Mathematica the first element has index of 1, not 0.
random = RandomReal[]; // Generate a random float from 0 to 1;
For[i = 1, i <= Length#weights, i++,
If[random >= min && random < max,
Print["Chosen index number: " <> ToString#i]
];
min += weights[[i]];
If[i == Length#weights,
max = 1,
max += weights[[i + 1]]
]
]
(Now I'm talking with a lists first element's index equals 0) The idea behind this is that having a normalized list weights there is a chance of weights[n] to return the index n, so the distances between the min and max at step n should be weights[n]. The total distance from the minimum min (which we put it to be 0) and the maximum max is the sum of the list weights.
The good thing behind this is that you don't append to any array or nest for loops, and that increases heavily the execution time.
Here is the code in C# without needing to normalize the weights list and deleting some code:
int WeightedRandom(List<float> weights) {
float total = 0f;
foreach (float weight in weights) {
total += weight;
}
float max = weights [0],
random = Random.Range(0f, total);
for (int index = 0; index < weights.Count; index++) {
if (random < max) {
return index;
} else if (index == weights.Count - 1) {
return weights.Count-1;
}
max += weights[index+1];
}
return -1;
}
I suggest to use a continuous check of the probability and the rest of the random number.
This function sets first the return value to the last possible index and iterates until the rest of the random value is smaller than the actual probability.
The probabilities have to sum to one.
function getRandomIndexByProbability(probabilities) {
var r = Math.random(),
index = probabilities.length - 1;
probabilities.some(function (probability, i) {
if (r < probability) {
index = i;
return true;
}
r -= probability;
});
return index;
}
var i,
probabilities = [0.8, 0.1, 0.1],
count = probabilities.map(function () { return 0; });
for (i = 0; i < 1e6; i++) {
count[getRandomIndexByProbability(probabilities)]++;
}
console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Thanks all, this was a helpful thread. I encapsulated it into a convenience function (Typescript). Tests below (sinon, jest). Could definitely be a bit tighter, but hopefully it's readable.
export type WeightedOptions = {
[option: string]: number;
};
// Pass in an object like { a: 10, b: 4, c: 400 } and it'll return either "a", "b", or "c", factoring in their respective
// weight. So in this example, "c" is likely to be returned 400 times out of 414
export const getRandomWeightedValue = (options: WeightedOptions) => {
const keys = Object.keys(options);
const totalSum = keys.reduce((acc, item) => acc + options[item], 0);
let runningTotal = 0;
const cumulativeValues = keys.map((key) => {
const relativeValue = options[key]/totalSum;
const cv = {
key,
value: relativeValue + runningTotal
};
runningTotal += relativeValue;
return cv;
});
const r = Math.random();
return cumulativeValues.find(({ key, value }) => r <= value)!.key;
};
Tests:
describe('getRandomWeightedValue', () => {
// Out of 1, the relative and cumulative values for these are:
// a: 0.1666 -> 0.16666
// b: 0.3333 -> 0.5
// c: 0.5 -> 1
const values = { a: 10, b: 20, c: 30 };
it('returns appropriate values for particular random value', () => {
// any random number under 0.166666 should return "a"
const stub1 = sinon.stub(Math, 'random').returns(0);
const result1 = randomUtils.getRandomWeightedValue(values);
expect(result1).toEqual('a');
stub1.restore();
const stub2 = sinon.stub(Math, 'random').returns(0.1666);
const result2 = randomUtils.getRandomWeightedValue(values);
expect(result2).toEqual('a');
stub2.restore();
// any random number between 0.166666 and 0.5 should return "b"
const stub3 = sinon.stub(Math, 'random').returns(0.17);
const result3 = randomUtils.getRandomWeightedValue(values);
expect(result3).toEqual('b');
stub3.restore();
const stub4 = sinon.stub(Math, 'random').returns(0.3333);
const result4 = randomUtils.getRandomWeightedValue(values);
expect(result4).toEqual('b');
stub4.restore();
const stub5 = sinon.stub(Math, 'random').returns(0.5);
const result5 = randomUtils.getRandomWeightedValue(values);
expect(result5).toEqual('b');
stub5.restore();
// any random number above 0.5 should return "c"
const stub6 = sinon.stub(Math, 'random').returns(0.500001);
const result6 = randomUtils.getRandomWeightedValue(values);
expect(result6).toEqual('c');
stub6.restore();
const stub7 = sinon.stub(Math, 'random').returns(1);
const result7 = randomUtils.getRandomWeightedValue(values);
expect(result7).toEqual('c');
stub7.restore();
});
});
Shortest solution in modern JavaScript
Note: all weights need to be integers
function weightedRandom(items){
let table = Object.entries(items)
.flatMap(([item, weight]) => Array(item).fill(weight))
return table[Math.floor(Math.random() * table.length)]
}
const key = weightedRandom({
"key1": 1,
"key2": 4,
"key3": 8
}) // returns e.g. "key1"
here is the input and ratios : 0 (80%), 1(10%) , 2 (10%)
lets draw them out so its easy to visualize.
0 1 2
-------------------------------------________+++++++++
lets add up the total weight and call it TR for total ratio. so in this case 100.
lets randomly get a number from (0-TR) or (0 to 100 in this case) . 100 being your weights total. Call it RN for random number.
so now we have TR as the total weight and RN as the random number between 0 and TR.
so lets imagine we picked a random # from 0 to 100. Say 21. so thats actually 21%.
WE MUST CONVERT/MATCH THIS TO OUR INPUT NUMBERS BUT HOW ?
lets loop over each weight (80, 10, 10) and keep the sum of the weights we already visit.
the moment the sum of the weights we are looping over is greater then the random number RN (21 in this case), we stop the loop & return that element position.
double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 21) //(80 > 21) so break on first pass
break;
}
//position will be 0 so we return array[0]--> 0
lets say the random number (between 0 and 100) is 83. Lets do it again:
double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 83) //(90 > 83) so break
break;
}
//we did two passes in the loop so position is 1 so we return array[1]---> 1
I have a slotmachine and I used the code below to generate random numbers. In probabilitiesSlotMachine the keys are the output in the slotmachine, and the values represent the weight.
const probabilitiesSlotMachine = [{0 : 1000}, {1 : 100}, {2 : 50}, {3 : 30}, {4 : 20}, {5 : 10}, {6 : 5}, {7 : 4}, {8 : 2}, {9 : 1}]
var allSlotMachineResults = []
probabilitiesSlotMachine.forEach(function(obj, index){
for (var key in obj){
for (var loop = 0; loop < obj[key]; loop ++){
allSlotMachineResults.push(key)
}
}
});
Now to generate a random output, I use this code:
const random = allSlotMachineResults[Math.floor(Math.random() * allSlotMachineResults.length)]
Enjoy the O(1) (constant time) solution for your problem.
If the input array is small, it can be easily implemented.
const number = Math.floor(Math.random() * 99); // Generate a random number from 0 to 99
let element;
if (number >= 0 && number <= 79) {
/*
In the range of 0 to 99, every number has equal probability
of occurring. Therefore, if you gather 80 numbers (0 to 79) and
make a "sub-group" of them, then their probabilities will get added.
Hence, what you get is an 80% chance that the number will fall in this
range.
So, quite naturally, there is 80% probability that this code will run.
Now, manually choose / assign element of your array to this variable.
*/
element = 0;
}
else if (number >= 80 && number <= 89) {
// 10% chance that this code runs.
element = 1;
}
else if (number >= 90 && number <= 99) {
// 10% chance that this code runs.
element = 2;
}

image based count down clock

I have a count down clock which works absolutely fine. Now the question is can I display images as digits instead of html. I cant seem to figure out the logic how would I approach it. I really dont want to use a plugin for this so that is really not an option.
and the JS for the clock is this
setInterval(function(){
var future = new Date("Jan 20 2014 21:15:00 GMT+0200");
var now = new Date();
var difference = Math.floor((future.getTime() - now.getTime()) / 1000);
var seconds = fixIntegers(difference % 60);
difference = Math.floor(difference / 60);
var minutes = fixIntegers(difference % 60);
difference = Math.floor(difference / 60);
var hours = fixIntegers(difference % 24);
difference = Math.floor(difference / 24);
var days = difference;
$(".seconds").text(seconds + "s");
$(".minutes").text(minutes + "m");
$(".hours").text(hours + "h");
$(".days").text(days + "d");
}, 1000);
function fixIntegers(integer)
{
if (integer < 0)
integer = 0;
if (integer < 10)
return "0" + integer;
return "" + integer;
}
I have stored the images in an array which is this
var linkCons = 'http://soumghosh.com/otherProjects/Numbers/'
var num = [];
var linkCons = "http://soumghosh.com/otherProjects/Numbers/";
for(var i = 0; i < 10; i++) {
num.push(linkCons + "nw" + i + ".png");
}
Thanks to stack overflow folks helping me cleaning the array. Really appriciate it
And here is the working fiddle
http://jsfiddle.net/sghoush1/wvbPq/3/
You can do it using only one sprite image and this bit of code I created:
jQuery(function($) { // DOM ready shorthand
// CLOCK
// Just a date in the future... Say 5 days from now
var fut = new Date().setDate(new Date().getDate() + 5);
// Number splitter
function intSpl(i) {
i = Math.floor(i);
return [Math.floor(i / 10), i % 10]; // 37=[3,7] // 5=[0,5] // 0=[0,0]
}
var obj = {}; // {d:[7,7], h:[1,9], .....}
function drawTime() {
var now = new Date().getTime();
var dif = now < fut ? Math.floor((fut - now) / 1000) : 0;
obj.s = intSpl(dif % 60);
obj.m = intSpl(dif / 60 % 60);
obj.h = intSpl(dif / 60 / 60 % 24);
obj.d = intSpl(dif / 60 / 60 / 24);
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
for (var i = 0; i < 2; i++) { // get el ID number (0,1)
$('#' + key + i).css({
backgroundPosition: -obj[key][i] * 50
});
}
}
}
}
drawTime();
setInterval(drawTime, 1000);
});
#clock span {
display: inline-block;
width: 50px;
height: 85px;
background: url('http://i.imgur.com/uBTxTTD.jpg');
background-position: 0 0;
}
#clock span:nth-child(even) {
margin-right: 15px;
}
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<div id="clock">
<span id="d0"></span>
<span id="d1"></span>
<span id="h0"></span>
<span id="h1"></span>
<span id="m0"></span>
<span id="m1"></span>
<span id="s0"></span>
<span id="s1"></span>
</div>
To explain the idea:
Create elements, each will hold a one digit of the current 2 digits value;
Set a common bg image to all spans in CSS
Every second move each element's background-image left by -(witdh * number) px
While the listed above seems logic, the first problem you can see here is how to retrieve separately a JS time number (1 or 2 digits) keep leading zero if needed, and reference each digit to target the right element in HTML?
Let's start by splitting numbers:
35 == 3, 5 /// 0 == 0, 0 // this is an example of what we need.
var n = 35; // Set any 1 or 2 digit number.
var n1 = ~~(n/10); // 3 //// ~~ "Double Bitwise NOT"
// just instead of parseInt(time/10, 10).
var n2 = n%10; // 5 //// % "Mudulus operator" (reminder).
Example playground
JS Grouping
Now, how to group this two separated digits and say: "Hey you two are for my clock seconds!" ?
By simply putting them into an array! [3, 5], and for we'll have also minutes, hours and day - let's simply put all those arrays into an Object and assign a Key Name which will result in having an object like:
obj = {d:[7,4], h:[1,9], m:[2,9], s:[0,7]}
Reference to HTML
Having that Object and knowing that inside an for...in loop we can retrieve the Key name and the array value like eg: obj['d'][0] === 7 obj['d'][5] === 4
means that we'll need a for loop to retrieve the 0 and 1 to get the values in our array positions [pos0, pos1]
all inside a for...in loop that will get the KEY names : d, h, m, s
2pos x 4keyNames = 8 elements iterations/second
means that now we'll be able to target an ID element eg: #s0 and #s1
and all we need now is to retrieve the value and animate that element background by
-width * digit
Well, there's another way that you may use to solve the same problem. Here are the steps. Firstly I wrote one CSS class selector for each image position.
.list-group-item .digit-display{
display:inline-block;
width:50px;
height:85px;
background:url('http://i.imgur.com/uBTxTTD.jpg');
}
.position-0 {
background-position: 0 0;
}
.position-1 {
background-position: -50px 0px !important;
}
Then I wrote a JavaScript function which takes a digit as an input and return the CSS class selector for that digit as below.
displayDigit(digit) {
const baseSelector = "digit-display position-";
return `${baseSelector}${digit}`;
}
Finally this function is called inside the JSX element as below.
<span className = {this.displayDigit(remainingTime["h"].charAt(0))}></span>
That solved the issue.
However, if someone really needs to go with the jquery based approach specified above, we can still condense down that same code as below.
secondsToTime(secs) {
let hours = `${constants.ZERO}${Math.floor(secs / (60 * 60))}`.slice(-2);
let divisorForMinutes = secs % (60 * 60);
let minutes = `${constants.ZERO}${Math.floor(divisorForMinutes / 60)}`.slice(-2);
let divisorForSeconds = divisorForMinutes % 60;
let seconds = `${constants.ZERO}${Math.ceil(divisorForSeconds)}`.slice(-2);
let obj = {
"h": hours,
"m": minutes,
"s": seconds
};
return obj;
}
handleFlipClockImage = () => {
var myObj = this.secondsToTime(seconds);
Object.keys(myObj).forEach(key => {
let obj = myObj[key];
var digits = obj.split(constants.EMPTY_SPACE_CHAR);
digits.forEach((digit, index) => {
jquery(`#${this.state.label}${key}${index}`).css({backgroundPosition: -digit*50 });
});
});
}

Categories