setTimeout and recursive IFFEs, nested!? How to calculate the delay. - javascript

This problem has really reminded me where I am regarding JS skills... :(
I feel I'm right on the cusp but I'm struggling to understand what to do conceptually, it's not so much a syntax issue as I feel I almost cracked it.
What I need
I'm trying to console.log a series of strings, one letter at a time.
There needs to be a delay between each LETTER outputted, say 300ms.
There must then be a delay between each STRING outputted, say 2000ms.
There are 2 strings in example array, but solution must support a dynamic number of strings.
My Current Code (can be pasted into a console)
var stringList = ['first test','second test'],
stringListLen = stringList.length;
for(var i = 0; i < stringListLen; i++){
// begin with first string and read it's length for iterations
var stringNumber = 0;
var currentString = stringList[stringNumber];
var currentStringLen = currentString.length;
// just see the word for clarification at this point in code
console.log(currentString);
(function (i) {
setTimeout(function () {
for (var j = 0; j < currentStringLen; j++) {
(function (j) {
setTimeout(function () {
// THE MAGIC HAPPENS HERE
console.log(j, currentString.charAt(j));
// End of string, so read next string, reset letter count
if(j === currentStringLen - 1){
stringNumber++;
currentString = stringList[stringNumber];
j = 0;
}
}, 300 * j); // each letter * specified delay
})(j);
};
}, currentStringLen * 300 * i); // letter * delay * letters in word
})(i);
}
The Issue
THE GOOD: I am successfully getting the short delay between letters outputted, and my check to switch to a new word and reset the letter counter when we get to the end of the first word is working...
THE BAD: I can't get the wait between the two words to work. I have tried a few ideas and have just got myself so confused I don't know if my approach is correct now.
THE UGLY: The final letter of the last term is also not outputting, and that is just totally unexpected.
What I've tried.
Okay, I've tried simply changing the "currentStringLen * 300 * i" elements to various combinations that seemed logical but had no effect better or worse. Ultimately I think I am trying to calculate "wait the number of letters in current string times 300 (the letter delay) * " <---- STRIKETHROUGH...
I actually don't know what I'm calculating and that's the issue.
I now think I want to split this into TWO functions, not two nested ones. One to READ AND PASS IN a string to another function that JUST outputs the letters with a short delay, then once it gets to the last letter it calls the first function asking for the next word. BUT then I'm still going to need to recurse for the number of strings in the array which creates the same issue...
Am I missing anything fundamental here people?

Is this roughly what you had in mind?
function printLetters(stringList) {
var wordIndex = 0,
letterIndex = 0;
printNext();
function printNext() {
var word = stringList[wordIndex];
var letter = word.charAt(letterIndex);
console.log(letter);
++letterIndex;
if (letterIndex === word.length) {
letterIndex = 0;
++wordIndex;
if (wordIndex < stringList.length) {
setTimeout(printNext, 2000);
}
return;
}
setTimeout(printNext, 300);
}
}
printLetters(['first test', 'second test']);
Here there's only ever one setTimeout running at once and a new one is being set as required with the appropriate time.
While I don't recommend having multiple timers running at once, it can be done. Something like this:
function printLetters(stringList) {
var letterCount = 0,
startTime = Date.now();
stringList.forEach(function(word, wordCount) {
word.split('').forEach(function(letter) {
setTimeout(function() {
console.log(letter, Date.now() - startTime);
}, wordCount * 1700 + (letterCount * 300));
++letterCount;
});
});
}
printLetters(['first test', 'second test']);
Here I've included the time delta in the logging to give a better sense of what is going on when. The gap between strings is 2000 but the constant in the code is 1700 because there's already 300 being added.

I would do a quite different approach. Rather than doing a bunch of precalculated timeouts and their associated closures, i would do just one timeout at a time, using recursion to then move on to the next timeout:
function delayShow(words) {
if (!words || words.length === 0) {
return;
} else if (words[0].length === 0) {
words.shift()
setTimeout(() => delayShow(words), 2000);
} else {
console.log(words[0].charAt(0));
words[0] = words[0].substr(1);
setTimeout(() => delayShow(words), 300);
}
}
delayShow(['first test','second test']);

You can use a condition inside the loop, and when you're on the last iteration of the strings characters, you call the recursive function again, inside a timeout, to iterate over the next string in the array etc.
var stringList = ['first test', 'second test'];
(function rec(j) {
var chars = stringList[j].split('');
chars.forEach(function(char, i) {
setTimeout(function() {
console.log(char);
// if it's the last iteration && there are more strings in the array
if ((i == (chars.length - 1)) && (j < stringList.length - 1)) {
setTimeout( function() {
rec(++j); // play it again
}, 2000);
}
}, i * 300);
});
})(0);

Even something as trivial as setTimeout's, is a good example of using async / await. So I've included an example below.
As you will see, the code is much easier to follow. With the added advantage of not creating multiple setTimeout's running concurrently,.
It also really helps make things much easier to change things, eg. If I asked you to alter the code below so that SPACE takes less time than other letters, to make it flow more naturally, it wouldn't take a lot of thought to work out what to change.
var
stringList = ['first test','second test'];
async function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
async function run() {
let wordpos = 0;
let wordElem = document.querySelector('.words');
while (true) {
let word = stringList[wordpos];
let text = '';
for (var letterpos = 0; letterpos < word.length; letterpos ++ ) {
let letter = word[letterpos];
text = text + letter;
wordElem.innerText = text;
await delay(300);
}
await delay(2000);
wordpos = (wordpos + 1) % stringList.length;
}
}
run();
<h1 class="words">?</h1>

Related

JS create an array with unique random numbers

Full code looks like this, ideally we have 4 div boxes that need to be randomly filled with random numbers ansValue, one of them (rightAnsValue with its rightAnsId) is already done and works fine, I've managed to make it unique in comparison to others (code without commented section). But met a problem with making others unique, I keep having some identical values in my boxes. In comments is one way I tried to solve this, but pretty sure there is a much simpler and smarter solution that actually works. I would appreciate if you could help to find an understandable solution to this problem.
(P.S. I've seen similar questions but they are either too dificult or done without JS.)
function createAnswers(){
for(ansId=1; ansId<5; ansId++){
if(ansId!=rightAnsId){
for(i=1; i<10; i++){
digitArray[i-1] = i;
}
genNewRandNum();
// ansArray.length = 3;
// ansArray.push(ansValue);
// for(k=0; k<3; k++){
// if(ansArray[k] == ansArray[k+1] || ansArray[k] == ansArray[k+2]){
// genNewRandNum();
// ansArray[k] = ansValue;
// }else if(ansArray[k+1] == ansArray[k+2]){
// genNewRandNum();
// ansArray[k+1] = ansValue;
// }else{
// break;
// }
// }
if(ansValue!=rightAnsValue){
document.getElementById("box" + ansId).innerHTML = ansValue;
}else{
genNewRandNum();
document.getElementById("box" + ansId).innerHTML = ansValue;
}
}
}
}
The way I generate new numbers:
function genNewRandNum(){
rand1 = digitArray[Math.floor(Math.random() * digitArray.length)];
rand2 = digitArray[Math.floor(Math.random() * digitArray.length)];
ansValue = rand1 * rand2;
}
Replace your genNewRandNum() with below code. I have used IIFE to create a closure variable alreadyGeneratedNumbers thats available inside the function generateRandomNumber() thats returned.
So everytime genNewRandNum() is executed, it checks against alreadyGeneratedNumbers to make sure it always returns a unique between 1 and 9.
var genNewRandNum = (function(){
var alreadyGeneratedNumbers = {};
return function generateRandomNumber() {
var min = Math.ceil(1),
max = Math.floor(9);
randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;
if(alreadyGeneratedNumbers[randomNumber]) {
return generateRandomNumber();
} else {
alreadyGeneratedNumbers[randomNumber] = randomNumber;
return randomNumber;
}
}
})();
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
Note: If you call genNewRandNum() for the 10th time it will throw error. So if you have a use case where you would need to reset it after all numbers from 1 to 9 are returned, then you need to add code to handle that
The easiest way to brute-force this is to use accept/reject sampling. You can do something like so:
uniqueRandomNumbers = function(n, nextRandom)
{
var nums = {}; var m = 0;
while(m < n)
{
var r = nextRandom();
if(! nums.hasOwnProperty(r))
{
nums[r] = true; m++;
}
}
return Object.keys(nums);
}
Here I'm using the fact that js objects are implemented as hashmaps to get a hashset. (This has the downside of converting the numbers to strings, but if you're not planning on imediately doing arithmetic with them this is not a problem.)
In order to get four unique integers between 0 and 9 you can then do something like:
uniqueRandomNumbers(4, function() { return Math.floor(Math.random() * 10); })
If you want something a little better than brute force (which probably isn't relevant to your use case but could help someone googling this), one option is to go through each element and either take or leave it with an appropriate probability. This approach is outlined in the answers to this question.

How do I re run the function in my Simon game?

I'm pretty confident that I have the basic logic behind my Simon game. However, I'm having issues in going to the next round. I have attempted to use an if statement and while loop but neither have worked. How should I proceed?
A brief explanation: gameColors is an array that has all the random colors that will be played out in an increasing number over several rounds. The variable sequencelength is used to increase the counter used in the user input function, whereas the variable sequenceLengthInv is the opposite of that, subtracted from gameColors.length to get sequenceLength.
random sequence displays the random sequence of colors up to the sequenceLength value.
JS:
if (sequenceLengthInv >= 0) {
// have the game play the random sequence of colors
for (var i = 0; i < gameColors.length - sequenceLengthInv; i++) {
randomSequence();
}
//listens for the user's input
for (var i = 0; i < color.length; i++) {
userInput();
}
sequenceLength++;
sequenceLengthInv--;
}
Try using for loops and using throw/reject & catch (or break). Also, Promises may be useful here.
async function playGame(availableColors, minColors, maxColors) {
await wait(333);
var colors = Array.from({ length: maxColors }, () => randomChoice(availableColors));
try {
for (var i = minColors; i <= maxColors; i++) {
console.log(colors.slice(0, i));
await playRound(colors.slice(0, i));
}
alert('You win!');
} catch (e) {
if(e !== undefined) {
throw e;
}
alert('You lose!');
}
}
My modified (and now commented) code is available here: https://repl.it/#solly_ucko/InconsequentialRoastedMonitor.

Javascript: setTimeout Closure inside nested (double) for loops

Hi I'm trying to recreate what http://austintexas.wayblazer.com/locations/austin-tx is doing for its search textbox - displaying typed character and rotating between several phrases.
My initial logic is to read the phrases from an array like so: [abc,123,XYZ] and then to use a split and forEach to separate the string into characters and create a typing effect using setTimeout.
However, when I attempt this logic, I seem to have trouble getting the desired effect. I've been reading through a lot of these questions on SO, but few of them address issues with nested for loops.
Desired results:
a b c (in a typing effect character by character)
clear textbox
1 2 3 (in a typing effect character by character)
clear textbox
X Y Z
Actual results:
abc123XYZ (abc shows up together at once, followed by 123, then XYZ)
$(document).ready(function() {
$('input#123').attr("placeholder", "");
var phrases = ['abc', '123', 'XYZ'];
for (var i = 0; i < phrases.length; i++) {
$('input#123').attr("placeholder", "");
(function(ind) {
var sentence = phrases[ind];
sentence.split("").forEach(function(char, index) {
setTimeout(function() {
$('input#123').attr("placeholder", $('input#123').attr("placeholder") + char);
}, ind * 500);
});
})(i);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<input id="123" />
Is there an elegant solution for what I want to achieve, and hopefully some explanation to enlighten me would be very much appreciated!
The great thing with javascript is closures, so you can create function closures, and push onto an array to act like a stack.. You can then pop these function off inside a setInterval..
$(document).ready(function(){
var input = $('input#123'),
phrases = ['Stack Overflow','Is the place you come','To get help with coding stuff'],
typedelay = 140,
waitops = 5,
cmds = [],
ph = input.attr.bind(input, 'placeholder');
function clear() {
ph('');
}
function addLetter(a) {
return function () {
ph(ph() + a);
}
}
function doLoop() {
cmds = [];
for (var i=0; i<phrases.length;i++) {
cmds.push(clear);
var sentence = phrases[i];
sentence.split("").forEach(function(char,index) {
cmds.push(addLetter(char));
});
//at the end of each sentence, there is a pause. lets do some no ops,.. 5*300 ms
for (var nn = 0; nn < waitops; nn ++) cmds.push(0);
}
//lets make this thing go on forever.. :)
cmds.push(doLoop);
}
doLoop();
var icheck = setInterval(function () {
//is there a cmd waiting, pop it and run it.
var cmd = cmds.shift();
if (cmd) { cmd(); }
}, typedelay);
input.focus(function () {
clearInterval(icheck);
cmds = [];
ph('What your want');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<input id="123" />
Here's a recursive function that shifts the array and stops when array has no length
typePhrases(['Message #1','Another message','And the last one'], 300)
function typePhrases(phrases, speed){
// shift array to get word/phrase
var word = phrases.length && phrases.shift(), $input= $('#test').attr('placeholder','');
// don't do anything if no phrase/word
if(word){
word.split('').forEach(function(char, i){
setTimeout(function(){
// use attr(attributeName, function) to update
$input.attr('placeholder', function(_, curr){
return curr + char;
});
// on last letter call function again to get next word
if(i=== word.length-1){
setTimeout(function(){
typePhrases(phrases, speed);
},speed);
}
},i*speed)
});
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="test" placeholder=''>

How to go through an unknown number of quotes randomly without repeating

I have a small snippet of Javascript where I rotate through a list of quotes in order from beginning to end.
However, I want to randomly go through the list (instead of in order), without repeating until all of the quotes are iterated through, and then start around with a random quote again. How would I go about doing this?
$(function(){
var quotes = $('#quotes').children('.rotate-quote');
firstQuo = quotes.filter(':first');
lastQuo = quotes.filter(':last');
quotes.first().show();
setInterval(function(){
if($(lastQuo).is(':visible')) {
var nextElem = $(firstQuo);
} else {
var nextElem = $(quotes).filter(':visible').next();
}
$(quotes).filter(':visible').fadeOut(300);
if($(lastQuo).is(':visible')) {
setTimeout(function() {
$(firstQuo).fadeIn(300);
}, 600);
} else {
setTimeout(function() {
$(nextElem).fadeIn(600);
}, 600);
}
}, 10000);
});
Here's a possible solution with demo:
var $container = $('div'),
quotes = $('quote').hide().toArray(),
delay = 500;
function shuffle(arr) {
return arr.map(function(v){ return [v,Math.random()]; })
.sort().map(function(v){ return v[0]; });
}
function loop() {
$(shuffle(quotes)).each(function(i,el) {
setTimeout(function(){ $(el).appendTo($container).show(); }, i*delay);
});
}
function start() {
function begin(){ $(quotes).hide(); loop(); }
setInterval(begin, quotes.length * delay);
begin();
}
start();
Demo: http://jsbin.com/agihix/1/edit
Edit: I turned this into a little plugin, grab it here https://gist.github.com/elclanrs/5610886
Just to show the Fisher Yates (not my code):
function fisherYates ( myArray ) {
var i = myArray.length, j, temp;
if ( i === 0 ) return false;
while ( --i ) {
j = Math.floor( Math.random() * ( i + 1 ) );
temp = myArray[i];
myArray[i] = myArray[j];
myArray[j] = temp;
}
return myArray;
}
So get your quotes into an array, run it through that function, then do loop through the array, when you get to the end, run it through that function again, etc.
The following copies the quote array, then randomly slices one from it each time. wWen there are no entries left, it starts again.
var randomQuote = (function() {
var quotes = ['quote 0','quote 1','quote 2'];
var quoteCopy = [];
return function () {
if (!quoteCopy.length) {
quoteCopy = quotes.slice();
}
return quoteCopy.splice(Math.random()*quoteCopy.length | 0, 1)[0];
}
}());
I don't think your algorithm as stated is good. Suppose you have 100 quotes and then you make a first random presentation of the full set.
At the end, as stated in your description, will start again from scratch and so it's possible that quote 101 will be the same quote as 100.
I think that something better (more uniform) would be
Randomly shuffle the n quotes. This is done only once at the beginning.
Pick the first quote and display it.
Remove the quote from the list and insert it in a random position between n-w and n where w is a parameter (e.g. n/2).
Repeat from 2 for each quote you need.
The number w will modulate how much random you want the sequence to be... the smaller and the more uniform the delay will be after a quote is presented.
With this approach there will be no "distribution glitch" and the average delay after which a quote is presented next time will not depend on the current position.

JavaScript: Improving the performance of a FOR Loop to stop the browser locking up?

I have an array of objects, I loop through this array one object at a time and I make a couple of checks to see if each object in that array meets certain criteria, if that object meets this criteria I then copy a property of this object in to an array (that property also contains another object).
for(var v = 0; features.length > v; v++){
var feature = features[v];
//If the feature is on the map then we want to add it
if(feature.onScreen()){
dataArray.push(feature.attributes);
}
}
Now for some reason if this array of objects is big (5000+) in size this operation becomes very expensive and the browser just locks up for a second or two (Some times more).
I cant go in to much more info of what the code does but I was wondering given this loop, what would be the best way to give the browser a break lets say every 500 iterations and so it doesn't lock up and then continue on etc.
Thanks
Not sure... but what about putting that code into a function, then breaking out and recalling the function (say every xMs)?
eg,
var LastPos = 0;
function DoLoop()
{
for(var v = LastPos; features.length > v; v++){
var feature = features[v];
//If the feature is on the map then we want to add it
if(feature.onScreen()){
dataArray.push(feature.attributes);
}
if(v > 500)
{
LastPos = v;
break;
}
}
setTimeout('DoLoop()', 10);
}
setTimeOut() can solve this.
var maxStatement = 1000, currentIndex=0, timeoutVar = ''
var sLimit = features.length
function multiStepLoop(){
for (var i=currentIndex; i<currentIndex+maxStatement; i++){
//---DO YOUR STUFFS HERE
}
var a = sLimit-i;
currentIndex += maxStatement;
if (maxStatement >= a){ maxStatement=a }
if (a<=0){
callBackFunction() //-- function to call when your peocess finish.
}else{
timeoutVar = setTimeout('multiStepLoop()',1)
}
}
multiStepLoop();
The drawback is that you need to cover all you want to do after this process complete into a function, then run it like a callBack_Function.
Note : Jus need to set the time for setTimeout to 1 millisecond & the browser won't display the waiting-cursor.
Change the structure of the loop to :
var index = 0;
function myfunc() {
while(features.length > index){
var feature = features[v];
//If the feature is on the map then we want to add it
if(feature.onScreen()){
dataArray.push(feature.attributes);
}
index++;
//You can do this
var waitingTime = 0;
If (index% 500=0) waitingTime=100; //or 10, 20...
//Or just with a short interval
var waitingTime = 10;
setTimeOut('myfunc', waitingTime);
}
}
Or with a parameter : [I prefer this one]
function myfunc(index) {
if(!index) {
index=0;
}
while(features.length > index){
var feature = features[v];
//If the feature is on the map then we want to add it
if(feature.onScreen()){
dataArray.push(feature.attributes);
}
//You can do this
var waitingTime = 0;
If (index% 500=0) waitingTime=100; //or 10, 20...
//Or just with a short interval
var waitingTime = 10;
setTimeOut('myfunc', waitingTime);
}
}
[EDIT]
Change setTimeOut calls...
And in your code, when you call the function, don't give the param, to initialize the index!
What if you did something with a little burst and a little recursion:
Fiddle Code
Main Function
What happens is a given index is passed with a burst amount of intervals (i.e. 500), upon ending the for loop at the given number of burst iterations, it recalls itself with the same burst number. The function ends when the end of the features array is completed.
var transferFeatures = function (index, burst) {
for (var z = 0; z <= burst; z++) {
if (index === features.length) {
return;
}
var feature = features[index];
if (feature.onScreen) {
dataArray.push(feature.attributes);
}
index++;
}
if (index !== features.length) {
transferFeatures(index, burst);
}
};
Testing
To simulate load, I created an array of objects using various key-value pairs (most importantly, your attributes inner object):
//To simulate a random boolean
var randomBoolean = function () {
return Math.random() <= 0.5;
};
//A random integer
var getRand = function () {
return (Math.floor(Math.random() * 10).toString());
};
// Create a bunch of dummy objects
var randomFeatures = function (arr, i) {
for (var p = 0; p < i; p++) {
arr.push({
onScreen: randomBoolean(),
attributes: {
width: getRand(),
height: getRand(),
someAtt: "I'm just an attribute",
coolKidsRideBikes: true,
foo: "bar",
bar: "baz"
}
});
}
};
Granted, it's not the same onScreen() test you will be using, but either way, it evaluates to a boolean value. I think if you apply this concept with your code, you could have amazing results.
Everything in the Fiddle I linked too at the top is called like this:
randomFeatures(features, 5000);
console.log(features.length);
transferFeatures(0,500);
console.log(dataArray.length);
Load Testing
I simulated 5000000 (5 million) random objects being pushed onto features with a burst of 1000 and the script completed in around 3.29 seconds.

Categories

Resources