Using Random Number Generator for a Loop Timeout - javascript

This script should scroll through each container on a website with a randomly generated delay.
I want to replace the "pause" of 2000ms with a randomly generated number between min and max seconds for each iteration in the loop.
var min = 3,
max = 9;
var y = document.querySelectorAll('.chunk-container').length;
for (let x=0; x<y; x++) {
task(x);
}
function task(x) {
// var z = randomGen() * 1000;
setTimeout(function() {
console.log('X = ' + x + ' Y = ' + y);
document.getElementsByClassName('chunk chunk-container')[x].scrollIntoView({behavior: 'smooth'});
console.log('Scrolled to next Container');
}, /* z */ 2000 * x);
}
})
Random number generator:
function randomGen() {
var rand = Math.floor(Math.random() * (max - min + 1) + min);
console.log('Random Number generated: ' + rand + ' seconds');
return rand;
}
Like this it works fine. After each iteration, there is a 2 second pause. However when I remove the comments to add the lines var z = randomGen() * 1000; to randomize the time between each iteration, the x value (at which container it should scroll to) starts off fine, but then turns out random as well.
The console output:
Random Number generated: 6 seconds
Random Number generated: 5 seconds
Random Number generated: 4 seconds
Random Number generated: 8 seconds
Random Number generated: 4 seconds
Random Number generated: 8 seconds
X = 0 Y = 7
Scrolled to next Container
X = 1 Y = 7
Scrolled to next Container
X = 2 Y = 7
Scrolled to next Container
X = 4 Y = 7
Scrolled to next Container
X = 3 Y = 7
Scrolled to next Container
X = 5 Y = 7
Scrolled to next Container
X = 6 Y = 7
Scrolled to next Container
How can I fix this?

I rewrote you code.
I think the main problem was that setTimeout is non blocking.
I used promise together with await to solve it
function sleep(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
function randomGen(max, min) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
async function randomWaitAndScroll(elements, timeMin, timeMax) {
for (element of elements) {
let random = randomGen(timeMin, timeMax)
await sleep(random);
element.scrollIntoView({behavior: 'smooth'});
}
}
const containerElements = document.querySelectorAll('.chunk-container');
randomWaitAndScroll(containerElements, 3000, 9000);

The problem is that 2000 * x works because the delay between each scroll is constant, so your timeouts trigger at:
x = 0 -> 0ms
x = 1 -> 2000ms
x = 2 -> 4000ms
So you launch all of the timeouts at once at the start and they end up triggering every 2 seconds, but each timeout's value is different. Each implicitly depends on the fact that all timeouts before have been triggering every 2 seconds.
One approach would be to use an asynchronous function that returns a promise and await for it before scrolling to the next element as Josh points out
An alternative would be to keep track of the previous timeouts values and add them up, to compute the next timeout's value. Like this:
var min = 3,
max = 9;
var y = document.querySelectorAll('.chunk-container').length;
for (let x=0, accumulatedDelay = 0; x<y; x++) {
accumulatedDelay += task(x, accumulatedDelay);
}
function task(x, accumulatedDelay) {
const z = randomGen() * 1000;
setTimeout(function() {
console.log('X = ' + x + ' Y = ' + y);
document.getElementsByClassName('chunk chunk-container')[x].scrollIntoView({behavior: 'smooth'});
console.log('Scrolled to next Container');
}, z + accumulatedDelay);
return accumulatedDelay;
}

Related

Adding up results from for loop + auto break when number in let is reached

I am working on an algoritm that outputs random data for an upcoming movie project I am working on.
I have an RNG called ShotRNG that outputs a number between 1 and 72 (which stands for the length of each shot in frames based on 24 frames per second)
Let's say I have a usableRunTime let which defines the total usable time.
In this case, usableRunTime = 500
I want a for loop to run and add all of the results from the shotRNG together. Once the total numbers generated by usableRunTime reaches 500 or above, I want the for loop to automatically break.
Can someone help me with this?
I've tried for and while loops but I haven't been able to get it to work yet. Some of my code is underneath:
`
function partSixDivider(){
console.log ("#006\tPOST PRODUCTION\n[DETERMINES ENTIRE FLOW OF THE FILM BASED UPON THE PREVIOUSLY GENERATED PARAMETERS]\n\n");
}
let usableRunTime = 0;
function convertSecondsToFramesDeclaration() {
usableRunTime = totalRunTime * 24 - 360;
return "TOTAL USABLE FRAMES (BASED ON 24FPS):\t" + usableRunTime;
}
function shotRNG() {
return Math.floor(Math.random() * (totalShotSum - 1 + 1)) + 1;
}
function frameRNG() {
return Math.floor(Math.random() * (72 - 1 + 1)) + 1;
}
function shotVersion() {
let versionRNG = shotParameterCalculation(1,4)
return versionRNG;
}
let introFrames = 240; // Based on 10 seconds * 24 frames per second
let outtroFrames = 120; // Based on 5 seconds * 24 frames per second
function filmDeclaration() {
console.log("FILM OVERVIEW REPORT\n\n");
console.log("INTRO \tVERSION: N/A\tFRAMES: "+ introFrames)
for (let x = 1; x <= usableRunTime; x++) {
console.log("SHOT " + shotRNG() + ":\t" + "VERSION: " +shotVersion()) + "\t" + "FRAMES:"
}
console.log("OUTTRO \tVERSION: N/A\tFRAMES: "+ outtroFrames) +"\n\n"
}
`

How to do a task in a time interval in javascript

inside JavaScript, how can I tell something to be done in a certain period of time? For example, for 5 seconds, the value of a variable will increase and it will stop after 5 seconds.
Of course, there are setinterval and settimeout, but they only do that work after that period of time is over. I want that work to be done during that time.
setInterval
You can use setInteveral, but it's mostly trash for everything I've ever attempted to use it for. In the code below, the counter will only increment to around 35 in 1 second; about 28 milliseconds per interval -
// initialize a counter
let counter = 0
// increment the counter on interval
let myprocess = setInterval(_ => counter++, 0)
// after 5 seconds, stop the process and log the counter value
setTimeout(_ => { clearInterval(myprocess); console.log(counter) }, 1000)
A more appropriate utility would be setImmediate. It is supported in Node but not in browsers yet. Polyfills are available if you plan to use this in the browser.
async generators
Above we see setInterval is quite slow. Another approach is to use an asynchronous generator. In the code below, the counter can reach over 300,000 in just 1 second; About 3.3 microseconds per interval.
async function *count(until) {
let counter = 0
while (Date.now() < until) {
yield counter++
}
}
async function main() {
for await (const n of count(Date.now() + 1000))
document.write(`${n}, `)
return "done"
}
main().then(console.log)
If the goal is to run the process as fast as possible for the fixed duration, we will skip expensive operations like document.write. In the example below the counter can reach over 1M in just 1 second; just under 1 microsecond per interval -
async function *count(until) {
let counter = 0
while (Date.now() < until) {
yield counter++
}
}
async function run(process, duration) {
let result
for await (const n of process(Date.now() + duration))
result = n
return result
}
run(count, 1000).then(console.log)
// 1045592
blocking while loop
If the loop itself is the main part of your program and you want simply want to run the counter as fast as possible for a fixed duration, use a while loop. Note this will block all other code from running for the entire duration of the loop -
function count(until) {
let n = 0
while (Date.now() < until)
n++
return n
}
setTimeout(console.log, 0, "I was blocked")
console.log(count(Date.now() + 1000))
// 36618673
// "I was blocked"
You will need to check inside some sort of loop if the time has passed, make sure the loop is async in nature or the JS main thread will have issues.
I have modified PI calculator from here Javascript: PI (π) Calculator to run for 5 seconds instead of infinite
, it's using requestAnimationFrame to keep things async->
function * generateDigitsOfPi() {
let q = 1n;
let r = 180n;
let t = 60n;
let i = 2n;
while (true) {
let digit = ((i * 27n - 12n) * q + r * 5n) / (t * 5n);
yield Number(digit);
let u = i * 3n;
u = (u + 1n) * 3n * (u + 2n);
r = u * 10n * (q * (i * 5n - 2n) + r - t * digit);
q *= 10n * i * (i++ * 2n - 1n);
t *= u;
}
}
// Demo
let iter = generateDigitsOfPi();
const tmStart = Date.now();//
let output = document.querySelector("div");
(function displayNextDigit() {
output.insertAdjacentHTML("beforeend", iter.next().value);
scrollTo(0, document.body.scrollHeight);
if (Date.now() - tmStart < 5000) requestAnimationFrame(displayNextDigit);
})();
div { word-wrap:break-word; font-family: monospace }
<div></div>
const setRunInSeconds = (callback, ms = 1000, delay = 1) => {
const intervalId = setInterval(callback, delay)
setInterval(() => {
clearInterval(intervalId)
}, ms)
}
let num = 0
setRunInSeconds(() => {
console.log('interval: ' + num)
num += 1
}, 5000, 1)

JavaScript - Generate a random number between 1 and 5, but never the same number twice consecutively

I have some simple code to generate a random number between 1 and 5.
Depending on what number is pulled, a certain sound file will play. However, how could I prevent this simple program from generating the same number twice in a row consequently making the same audio to play twice?
For example: a roll of the dice is never the same twice consecutively, meaning you cannot roll a 2 if you just rolled a 2 etc.
I was experimenting with if else statements and kind of backed myself into a corner. I know this may seem like a simple problem but I'm at my wits end on this one.
<!DOCTYPE html>
<html>
<body>
<p>Click the button to display a random number between 1 and 5.</p>
<button onclick="myFunction()">ROLL DICE</button>
<p id="demo"></p>
<script>
function myFunction() {
var x = Math.floor((Math.random() * 5) + 1);
document.getElementById("demo").innerHTML = x;
}
</script>
</body>
</html>
you need to get a new number and compare with the old one, if it's the same one, get a new one and repeat. a little recursuion could help here!
let lastNumber // start with undefined lastNumber
function getRandNumber() {
var x = Math.floor((Math.random() * 5) + 1); // get new random number
if (x === lastNumber) { // compare with last number
return getRandNumber() // if they are the same, call the function again to repeat the process
}
return x // if they're not the same, return it
}
function myFunction() {
const number = getRandNumber()
lastNumber = number
document.getElementById("demo").innerHTML = number;
}
How about creating a list to pick from with the last number removed?
lastNumber = 0; // Initialize it to a number outside the list so first pick is from full five.
sourceList = [1,2,3,4,5];
function noRepeatRandom(){
nextList = sourceList.filter(function(x){return x!=lastNumber});
randomIndex = Math.floor(Math.random() * nextList.length);
lastNumber = nextList[randomIndex]
return lastNumber
}
This should pick from the full five for the first call.
var lastRoll = null;
function myFunction() {
var thisRoll = lastRoll;
while (thisRoll === lastRoll) {
thisRoll = Math.floor((Math.random() * 5) + 1);
}
document.getElementById("demo").innerHTML = thisRoll;
lastRoll = thisRoll;
}
<p>Click the button to display a random number between 1 and 5.</p>
<button onclick="myFunction()">ROLL DICE</button>
<p id="demo"></p>
var curr = 0;
function myFunction() {
var x = Math.floor((Math.random() * 5) + 1);
if(x == curr) {
myFunction();
}
else {
curr = x;
document.getElementById("demo").innerHTML = x;
}
}
This should do it if you don't mind using a global variable.
Demo: http://jsfiddle.net/jkrb837p/1/
<!DOCTYPE html>
<html>
<body>
<p>Click the button to display a random number between 1 and 5.</p>
<button onclick="myFunction()">ROLL DICE</button>
<span id="previousNumber" style="display:none"></span>
<p id="demo"></p>
<script>
function myFunction() {
var x = getRandomNum();
var temp = document.getElementById("previousNumber").innerHTML;
while(x == temp){
x = getRandomNum();
}
document.getElementById("demo").innerHTML = x;
document.getElementById("previousNumber").innerHTML = x;
}
function getRandomNum(){
var x = Math.floor((Math.random() * 5) + 1);
return x;
}
</script>
Simple math trick to exclude excessive random calls:
In the first time generate random in range 1..5
In the next times call random in range 1..4, add it to the current value and use modulo operation - to preserve result range 1..5 but exclude current value
Code (note it uses x in range 0..4 but output is shifted by one to simplify math)
var s = ""
var x = Math.floor((Math.random() * 5));
for (i = 0; i < 40; i++) {
s = s + (x + 1) + " "
x = (x + Math.floor((Math.random() * 4) + 1 )) % 5;
}
print(s)
1 4 3 2 1 5 4 5 2 1 4 2 4 5 1 4 3 1 4 5 2 1 3 5 2 4 5 3 1 5 3 5 4 5 1 5 3 5 4 2
Here is a function that you can use. It's quite simple:
min: minimum range;
max: maximum range;
length: how many numbers to generate.
Function and usage:
const randomUniqueNumbers = (min, max, length=1) =>
{
const limit = max-min+1;
if (min > max || max < min){
throw new Error(`Parameter "min" has to be smaller than "max" and vice-versa.`)
}
else if (length > limit){
throw new Error(`The length between ${min} and ${max} cannot exceed ${limit}.`)
}
let uniqueNumbers = [];
let number;
for(var i=0; i<length; i++){
do
number = Math.floor(Math.random() * limit) + min;
while(uniqueNumbers.indexOf(number) !== -1);
uniqueNumbers[i] = number;
}
return uniqueNumbers;
}
console.log(randomUniqueNumbers(20, 30, 3)); //[24, 27, 26]
console.log(randomUniqueNumbers(100, 1000, 5)) //[865, 438, 798, 247, 232]
console.log(randomUniqueNumbers(1, 5, 5)) //[5, 2, 1, 3, 4]

Ease time between firing specific number of timeouts in a specific period of time

It's kind of math problem. I want to fire specific number of setTimeout (the number is based on an array length) in a specific period of time (say, 5 seconds).
The first setTimeout should start at 0 sec. and the last at 5 sec.. All timeouts between should start with an ease-in effect, so that each timeout starts faster.
There's an example which ilustrates what I want to achieve exactly.
I'm struggling around this line:
next += timePeriod/3.52/(i+1);
which works almost perfect in demo example (for any timePeriod), but obviously it doesn't work for a different letters.length as I have used static number 3.52.
How do I calculate next?
var letters = [
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T'
];
var div = $('#container');
var timePeriod = 5000; // 5 seconds;
var perLetter = timePeriod/(letters.length-1); // it gives equal time between letters
var next = 0;
for(var i=0; i<letters.length; i++){
setTimeout(function(letter){
//div.append('<span class="letter">' + letter + '</span>');
// Used "|" instead of letter, For better redability:
div.append('<span class="letter">|</span>');
}, next, letters[i]);
// Can't find the logic here:
next += timePeriod/3.52/(i+1);
};
///////////////// FOR DEMO: ///////////////
var sec = timePeriod/1000;
var secondsInterval = setInterval(seconds, 1000);
var demoInterval = setInterval(function(){
sec >= 0 || clearInterval(demoInterval);
div.append('\'');
}, 30);
function seconds(){
sec || clearInterval(secondsInterval);
$('#sec').text(sec-- || 'DONE');
}
seconds();
.letter{
color : red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id=container></span>
<span id=sec class=letter></span>
var steps = letters.length;
var target = timePeriod;
function easeOutQuad(t, b, c, d) {
t /= d;
return -c * t*(t-2) + b;
};
var arrayOfTimeouts = new Array(steps);
var n;
var prev = 0;
for(var i = 1; i <= steps; i++){
n = easeOutQuad(i, 0.0, target, steps);
arrayOfTimeouts[i-1] = n-prev;
prev = n;
}
This one should work with any input value.
fiddle
Note that the graph appears to be slightly too fast but I believe that discrepancy to be a product of timing imperfections, as the sum of my array equals the timePeriod exactly.
more on easing equations
Here's a solution based on a geometric series. It's a bit goofy but it works. It generates an array with your timeout values.
Steps = size of your array.
Target = the total time.
var steps = 50;
var target = 5000;
var fraction = 1.5 + steps / 7;
var ratio = (fraction-1) / fraction;
var n = target / fraction;
var sum = 0;
var arrayOfTimeouts = new Array(steps);
for(var i = 0; i < steps; i++){
sum += n;
arrayOfTimeouts[i] = n;
n *= ratio;
}
console.log(arrayOfTimeouts, sum);

Calculating Pi in JavaScript using Gregory-Leibniz Series

I have to calculate value of Pi using Gregory-Leibniz series:
pi = 4 * ((1/1 - 1/3) + (1/5 - 1/7) + (1/9 - 1/11) + ...)
I want to write a function in JavaScript that would take the number of digits that needs to be displayed as an argument. But I'm not sure if my way of thinking is fine here.
This is what I got so far:
function pi(n) {
var pi = 0;
for (i=1; i <= n; i+2) {
pi = 4 * ((1/i) + (1/(i+2)))
}
return pi;
}
How do I write the pi calculation so it calculates values till n?
You could use an increment of 4 and multiply at the end of the function with 4.
n is not the number of digits, but the counter of the value of the series.
function pi(n) {
var v = 0;
for (i = 1; i <= n; i += 4) { // increment by 4
v += 1 / i - 1 / (i + 2); // add the value of the series
}
return 4 * v; // apply the factor at last
}
console.log(pi(1000000000));
You may also do as follows; The function will iterate 10M times and will return you PI with n significant digits after the decimal point.
function getPI(n){
var i = 1,
p = 0;
while (i < 50000000){
p += 1/i - 1/(i+2);
i += 4;
}
return +(4*p).toFixed(n);
}
var myPI = getPI(10);
console.log("myPI #n:100M:", myPI);
console.log("Math.PI :", Math.PI);
console.log("The Diff :", Math.PI-myPI);

Categories

Resources