How to set a timeout between each iteration in Javascript? - javascript

I am trying to set a timeout(wait time) between each call of drawLines() inside a setTimeout() in a while-loop.
while(i < j){
while(i < lastElIndex && array[i] < pivot){
const iCopy = i;
const lastCopy = lastElIndex;
i++;
const arrCopy = array.slice();
setTimeout(() => {
drawLines(arrCopy, iCopy, -1, lastCopy, -1);
});
}
while(j > firstElIndex && array[j] >= pivot){
const jCopy = j;
const firstCopy = firstElIndex;
j--;
const arrCopy = array.slice();
setTimeout(() => {
drawLines(arrCopy, -1, jCopy,-1, firstCopy);
});
}
I already tried setting the wait time like this
setTimeout(() => {drawLines(arrCopy, iCopy, -1, lastCopy, -1);}, 1000);
...but the timeout happens before the calling drawLines() repeatedly without waiting between calls.
I also tried
setTimeout(async() => {
drawLines(arrCopy, iCopy, -1, lastCopy, -1);
await sleep(1000);
});
...with
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
...but nothing happened
Here is the whole function:
function quickSort(arr, firstElIndex, lastElIndex){
let array = arr;
let currentIndex = 0;
function partition(firstElIndex, lastElIndex) {
let i = firstElIndex;
let j = lastElIndex;
let pivot = array[lastElIndex];
while(i < j){
while(i < lastElIndex && array[i] < pivot){
const iCopy = i;
const lastCopy = lastElIndex;
i++;
const arrCopy = array.slice();
setTimeout(() => {
drawLines(arrCopy, iCopy, -1, lastCopy, -1);
});
}
while(j > firstElIndex && array[j] >= pivot){
const jCopy = j;
const firstCopy = firstElIndex;
j--;
const arrCopy = array.slice();
setTimeout(() => {
drawLines(arrCopy, -1, jCopy,-1, firstCopy);
});
}
if(i < j){
array.swap(i, j);
}
}
if(array[i] > pivot){
array.swap(i, lastElIndex);
}
return i;
}
if(firstElIndex < lastElIndex){
currentIndex = partition(firstElIndex, lastElIndex);
quickSort(array, firstElIndex, currentIndex - 1);
quickSort(array, currentIndex + 1, lastElIndex);
}
setTimeout(() => {drawLines(array, -1, -1);}, 1000);
}
And here is drawLines():
function drawLines(arr, x, y, rightPivot, leftPivot){
let array = arr.slice();
let container = document.getElementById("centerContent");
let line = undefined;
container.innerHTML = '';
for(let i = 0; i < array.length; i++){
let my_length = (array[i]/6.7).toString() + "vw";line = document.createElement("div");
line.className = "line";
if(i === x || i === y){
line.style.borderLeft = (1/7).toString() + "vw solid red";
}
else if(i === rightPivot){
line.style.borderLeft = (1/7).toString() + "vw solid aqua";
}
else if(i === leftPivot){
line.style.borderLeft = (1/7).toString() + "vw solid orange";
}
else{
line.style.borderLeft = (1/7).toString() + "vw solid black";
}
line.style.height = my_length;
container.appendChild(line);
}
}
I apoligize if the question is poorly structured. It's the my first one. Many thanks in advance.

It sounds like you just want a delay to happen before the line is drawn and also before the loop continues. Using async/await can do that for you fairly easily. This is a structure you can use:
// to use await you must define the function to be async
async function partition(firstElIndex, lastElIndex) {
let i = firstElIndex;
let j = lastElIndex;
let pivot = array[lastElIndex];
while(i < j){
while(i < lastElIndex && array[i] < pivot){
const iCopy = i;
const lastCopy = lastElIndex;
i++;
const arrCopy = array.slice();
// this line pauses execution for 1 second
await new Promise(resolve => setTimeout(resolve, 1000);
drawLines(arrCopy, iCopy, -1, lastCopy, -1);
}
while(j > firstElIndex && array[j] >= pivot){
const jCopy = j;
const firstCopy = firstElIndex;
j--;
const arrCopy = array.slice();
// this line pauses execution for 1 second
await new Promise(resolve => setTimeout(resolve, 1000);
drawLines(arrCopy, -1, jCopy,-1, firstCopy);
}
if(i < j){
array.swap(i, j);
}
return i;
}
}
and since partition is now an async function you need to do this when calling it:
if(firstElIndex < lastElIndex){
currentIndex = await partition(firstElIndex, lastElIndex);
quickSort(array, firstElIndex, currentIndex - 1);
quickSort(array, currentIndex + 1, lastElIndex);
}
That requires that quickSort is also and async function which means it returns a Promise object that can be either waited for too, or use .then to know when it's complete.

The first thing is that the setTimeout needs a 2nd parameter with the amount of time to wait, so written like this:
setTimeout(() => {
drawLines(arrCopy, iCopy, -1, lastCopy, -1);
});
will not wait for any amount of time, unless you pass it a 2nd argument like:
setTimeout(() => {
drawLines(arrCopy, iCopy, -1, lastCopy, -1);
}, 500);
I think you where on the correct path when creating that sleep function; indeed it wouldn't work when running the timeout inside the loop. But there's no need to return a Promise. Just try:
function sleep(callback, ...callbackParameters) {
setTimeout(() => callback(...callbackParameters), 500) // remember your timer
}
And then pass whatever function you want to run after the timeout to your sleep function like:
sleep(drawLines, arrCopy, iCopy, -1, lastCopy, -1)

Related

Javascript flatten a generator in lambda

I have solved the sum-of-multiples small puzzle like this:
function sum(multiples, limit_in) {
return [...multiples.map(n => [...range(n, limit_in, n)])
.reduce((numbers, array) => {
array.forEach(value => numbers.add(value));
return numbers;
}, new Set())
.values()]
.reduce((sum, n) => sum + n, 0);
}
function* range(start, stop, step = 1) {
if (stop == null) {
stop = start;
start = 0;
}
for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
yield i;
}
}
const result = sum([5, 6, 8], 150);
console.log(result); // The result is 4419
Second version was something like this:
function sum(multiples, limit_in) {
return [...multiples.flatMap(n => [...range(n, limit_in, n)])
.reduce((numbers, value) => {
numbers.add(value);
return numbers;
}, new Set())
.values()]
.reduce((sum, n) => sum + n, 0);
}
function* range(start, stop, step = 1) {
if (stop == null) {
stop = start;
start = 0;
}
for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
yield i;
}
}
const result = sum([5, 6, 8], 150);
console.log(result); // Result 4419
The question is it possible to make it more lazy?
Is there a way to flatten more the generator (function*): range(n, limit_in, n) and not construct the array [...range(n, limit_in, n)]?
Javascript standard library is not good at dealing with iterators. If you want a completely lazy, memory-savvy solution, you'll have to define your own set of generic iteration primitives, for example:
function* range(start, stop, step = 1) {
if (stop == null) {
stop = start;
start = 0;
}
for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
yield i;
}
}
function* map(it, fn) {
for (let x of it)
yield fn(x)
}
function* chain(iters) {
for (let it of iters)
yield* it
}
function* uniq(it) {
let s = new Set
for (let x of it) {
if (!s.has(x)) {
s.add(x)
yield x
}
}
}
function sum(it) {
let s = 0
for (let x of it)
s += x
return s
}
let solution = (multiples, limit_in) =>
sum(
uniq(
chain(
map(
multiples,
n => range(n, limit_in, n)))))
const result = solution([5, 6, 8], 150);
console.log(result); // The result is 4419
You could write another helper function that allows to reduce the values originating from an iterable:
function sum(multiples, limit_in) {
return iterReduce(
multiples.reduce((numbers, n) =>
iterReduce(
range(n, limit_in, n),
(numbers, n) => numbers.add(n),
numbers
),
new Set
),
(sum, n) => sum + n,
0
);
}
function iterReduce(iterable, callback, acc) {
const it = iterable[Symbol.iterator]();
if (arguments.length < 3) acc = it.next();
for (const val of it) {
acc = callback(acc, val);
}
return acc;
}
function* range(start, stop, step = 1) {
if (stop == null) {
stop = start;
start = 0;
}
for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
yield i;
}
}
const result = sum([5, 6, 8], 150);
console.log(result); // The result is 4419

Merge Sort Visualizer

My merge sort visualizer draws the array but does not show them being sorted and doesnt seem to be correctly merging them and displaying each step. instead of returning a sorted array, I see a quick flash of the array then a blank screen.
values is the array and states is supposed to change in value from -1 to 1 based on whether an index is currently in progress, waiting, or completed but I'm trying to get the sort and merge working first.
//array of values to be sorted
let values = [];
//width of values
let w = 2;
//hold the state of the array
let states = [];
function setup() {
createCanvas(windowWidth, windowHeight);
values = new Array(floor(random(250, width / w)));
for (i = 0; i < values.length; i++) {
values[i] = random(height);
states[i] = -1;
}
}
async function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
//get midpoint
let mid = Math.round(arr.length / 2);
states[mid] = -1;
//split the array
temp1 = arr.slice(0, mid);
temp2 = arr.slice(mid, arr.length);
//merge the array
await Promise.all([
merge(mergeSort(temp1), mergeSort(temp2))
]);
}
async function merge(arr1, arr2) {
await sleep(25);
let sorted = [];
while (arr1.length > 0 && arr2.length > 0) {
(arr1[0] < arr2[0]) ? sorted.push(arr1.shift()):
sorted.push(arr2.shift());
}
while (arr1.length > 0) {
sorted.push(arr1.shift());
}
while (arr2.length > 0) {
sorted.push(arr2.shift());
}
values = sorted.slice();
}
function draw() {
background(0);
mergeSort(values);
for (let i = 0; i < values.length; i++) {
noStroke();
if (states[i] == 0) {
fill('#38e332');
} else if (states[i] == 1) {
fill('#c9c8c7');
} else {
fill(255);
}
//draw the array values at location x=i*w , y=height-array[i] with given width (w) and height(array[i])
rect(i * w, height - values[i], w, values[i]);
}
}
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
'

change the number of the iterator in a for loop

I want to conditionally break out of a loop like this..
for(let i = 0; i < 3; i++) {
exampleFunction().then(result => {
res = 0 ? i = 3 : null
})
}
I want exampleFunction to run at least 3 times unless it gets the desired result, in which case I want it to stop running.
exampleFunction runs asynchronously. The only way to get it working is using async/await.
const iterateWithExampleFunction = async () => {
for (let i = 0; i < 3; i++) {
console.log('before', i)
await exampleFunction().then(result => {
i = result === 0 ? 3: i;
});
console.log('after', i)
}
};
const exampleFunction = async () => {
return 0;
}
iterateWithExampleFunction();
You can have a count on the outer scope and then do the async call.
let count = 0;
function executeCall() {
exampleFunction().then(result => {
// do something with the result
if (result !== 0 && count !== 3) {
count += 1;
executeCall();
}
});
}
Just await the result, than break:
(async function() {
for(let i = 0; i < 3; i++) {
const result = await exampleFunction();
if(result === 0) break;
}
})();
Hopefully this gives you some ideas
async function poll(f, threshold = 3) {
if (!threshold) {
throw new Error("Value not found within configured amount of retries");
}
const result = await f();
if (result >= 0.5) {
return result;
} else {
return await poll(f, threshold - 1);
}
}
async function task() {
return Math.random();
}
poll(task).then(console.log).catch(console.error);

How to setInterval Generator correctly?

Goal:
I want to create a generator function that is being invoked inside setInterval(), and console.log 1 to 10.
the problem:
In order to clearInterval() at the end I need a condition to check if gen.next().done === true.
but every time the condition runs it actualy calls another .next()
so so final print i get is:
1 3 5 7 9 undefined
How do I set a done == true condition without calling .next() ?
function* myGen(){
let counter = 0;
for(let i = 0 ; i <= 10; i++){
yield counter++;
}
}
const gen = myGen();
const start = setInterval(() => {
if(gen.next().done){
clearInterval(start);
} else {
console.log(gen.next().value);
}
}, 1500)
You remember the object in a variable rather than calling next a second time:
function* myGen(){
let counter = 0;
for(let i = 0 ; i <= 10; i++){
yield counter++;
}
}
const gen = myGen();
const start = setInterval(() => {
var next = gen.next(); // *** Save it here
if(next.done){ // *** Use it here...
clearInterval(start);
} else {
console.log(next.value); // *** ...and here
}
}, 150)
You can alternatively use for..of loop, setTimeout(), async/await to avoid the need to check for .done property value
function* myGen() {
let counter = 0;
for (let i = 0; i <= 10; i++) {
yield counter++;
}
}
const gen = myGen();
(async() => {
for (let n of gen) {
await new Promise(resolve => {
setTimeout(() => {
console.log(n);
resolve()
}, 1500)
})
}
})();
Another way is to use the relatively new AsyncGenerator feature
https://github.com/tc39/proposal-async-iteration
I think it abstracts the problem nicely (creating an iterator that sleeps in between each iteration).
async function* sleepGenerator(numSleeps, sleepMillis) {
for (let i = 0; i < numSleeps; i++) {
await sleep(sleepMillis);
yield {i, numSleeps, sleepMillis};
}
}
function sleep(sleepMillis) {
return new Promise(resolve => setTimeout(resolve, sleepMillis));
}
(async function run() {
for await (const iterMeta of sleepGenerator(5, 500)) {
console.log(iterMeta);
}
})();
just, store the nextValue
function* myGen(){
let counter = 0;
for(let i = 0 ; i <= 10; i++){
yield counter++;
}
}
const gen = myGen();
const start = setInterval(() => {
let nextValue = gen.next();
if(nextValue.done){
clearInterval(start);
} else {
console.log(nextValue.value);
}
}, 1500)
function* myGen(){
let counter = 0;
for(let i = 0 ; i <= 10; i++){
yield counter++;
}
}
const gen = myGen();
const start = setInterval(() => {
var genObj=gen.next();//keep next result as an object to avoid use next method twice
if(genObj.done){
clearInterval(start);
} else {
console.log(genObj.value);
}
}, 1500)//I spent an hour learning this,late but get some konwledge,so,thanks.

How to make for loop log line by line with 1sec delay in JS

My code will log all 10 lines in the order that I want (descending triangle), but I need it to delay 1 second before logging each successive line. I tried putting a setTimeout before the for loop, but that just caused a 1 second delay before printing all 10 lines concurrently.
function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
for (var j = num; j > 0; j--) {
newArr.pop();
console.log(newArr.join(' '));
}
}
minusTen(10);
I can use jQuery but I'd like to avoid having to implement Bootstrap if possible.
Thank you!
you can use setTimeout for it but then you will have to keep setTimeout inside the for loop. you can also use setInterval here and clear the interval if num becomes 0. something like this:
function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
var interval = setInterval(function(){
newArr.pop();
console.log(newArr.join(' '));
num--;
if(!num)
clearInterval(interval);
}, 1000)
}
minusTen(10);
You can use a function. Check the .length of newArr, if greater than 0, call function again
function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
function fn() {
if (newArr.length)
setTimeout(function() {
console.log(newArr.join(" "));
newArr.pop();
fn()
}, 1000)
else
console.log("done, newArr.length:", newArr.length);
}
fn()
}
minusTen(10);
function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
function printLine(counter){
var k = counter;
window.setTimeout(function(){
console.log(newArr.join(' '));
newArr.pop();
}, k*1000);
console.log(k);
}
for (var j = num; j > 0; j--) {
printLine(j);
}
}
minusTen(10);
It's straightforward with async/await. delay holds the execution of the code for a specified amount of time.
async function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
for (var j = num; j > 0; j--) {
newArr.pop();
await delay(1000)
console.log(newArr.join(' '));
}
}
function delay(time) {
return new Promise((resolve) => setTimeout(resolve, time))
}
minusTen(10);
You can use setTimeout with an offset. Just add whatever parameters you need to the log function.
function log(offSet) {
setTimeout(() => {
const str = new Array(offSet + 1).join('*');
console.log(str);
}, 1000 * offSet);
}
for(let i = 1; i < 11; i ++) {
log(i);
}
https://jsfiddle.net/ycreaL9w/
If you wanted to reduce your code footprint a little you could do something like this:
const printStars = (n) => {
// Make sure we don't try to print zero stars
if (n > 0) {
// Fill an array with n stars
const arr = Array(n).fill('*').join(' ');
console.log(arr);
// After a second, reduce n by 1 and call
// the function again
setTimeout(() => printStars(--n), 1000);
}
}
printStars(10);
DEMO
It's worth pointing out here, however, that IE/Opera don't support Array.fill.
If you need to support them use an for/loop like in your example. Here I've separated out that code into its own function.
const getStars = (n) => {
let arr = [];
for (let i = 0; i < n; i++) {
arr.push('*');
}
return arr.join(' ');
}
const printStars = (n) => {
if (n > 0) {
console.log(getStars(n));
setTimeout(() => printStars(--n), 1000);
}
}
DEMO 2

Categories