JavaScript: Do I need a recursive function to solve this problem? - javascript

In this fiddle http://jsfiddle.net/5L8Q8/28/, if you click the black button, it randomly selects one of two values (red or blue) from an array. The randomly selected value is assigned to ran. In my real life application, there will be 16 elements in that array.
If you the pink "playagain" button, it chooses a random element from the same array but I want to make sure it's not the same one chosen as last time.
Therefore, when I click playagain, I assign ran to lastran and compare it to the next randomly chosen value from the array and, if they are the same, choose randomly again. However, the way I have it isn't guaranteeing that (upon the completion of playagain) ran is different.
I think I need a recursive function where comment 2 is in the code below, but I keep breaking my code when I try to create it.
Can you comment on the 3 comments in the code below?
Note, I'm a relative newbie, so this code is probably awful...
$("#playagain").click(function(){
lastran = ran;
ran = getRandom(myArray, true);
if (ran === lastran) {
ran = getRandom(myArray, true); //1. do I need to return this?
//2. want to test ran === lastran again.. How to set up recursive function?
} else {
return; //3.is this the right thing to do here?
}
});

while( (ran = getRandom(myArray, true)) === lastran)
;
Is what you want. The statement
ran = getRandom(myArray, true)
does not only set ran to getRandom(), but returns the value of ran. (This is a fairly common idiom in JavaScript, a carry over from C.)
So your full code can be:
$("#playagain").click(function(){
/*var */lastran = ran;
while( (ran = getRandom(myArray, true)) === lastran)
;
// update UI here
});

You can use a while loop instead of the if.
while(ran == lastran)
{
ran = getRandom(myArray, true);
}
It'll keep trying until it gets a different value.

After each run, simply remove that "key" from array and push lastran to the end of it. Then the updated getRandom function as following could be used both for #button and #playagain. http://jsfiddle.net/ghostoy/5L8Q8/32/
function getRandom(array, getVal) {
var key = Math.floor(Math.random() * array.length),
value = array[key];
if (lastran) {
array.push(lastran);
}
array.splice(key, 1);
lastran = value;
if (getVal) {
return value;
}
return key;
}

I think your approach is not the best way to deal with this. In theory you could get the same number many times in a row making this a 'slow' algorythm and you are making it more complex than needed.
An alternative approach in text:
- if no previous element has been picked pick a number between 0 and the number of elements in your array (16) otherwise pick a number between 0 and #elements-1 (15)
- if the chosen element is greater or equal to the last element picked add 1 to it
- store this index number as the last picked element
- return the array[picked-element]'s value

You could make getRandom itself recursive:
function getRandom(array, getVal, lastRan) {
var key = Math.floor(Math.random() * array.length);
if ((!getVal && key == lastRan) || (getVal && array[key] == lastRan))
return getRandom(array, getVal, lastRan);
return getVal ? array[key] : key;
}
Call it passing the last random value:
getRandom(myArray, true, lastran)
It works like this. You always pass getRandom the last random value that was retrieved. In the first conditional, we check to see if we just generated a duplicate of this value (either using the key itself or its corresponding value in the array, depending on whether getVal is true). If so, we return the result of calling getRandom again, once again passing the last random number that was used. This can happen as many times as necessary.
When one of these calls to getRandom produces a new number, then the expression in the first conditional will be false. In this case, we return the wanted value (via the second return statement) and all of the recursive calls to getRandom are "unrolled". (Remember, we returned the value of each call to getRandom at each step.)

Related

Is there a way to discontinue a forEach loop once you reach a certain key:value pair in the array?

I'm still very new to Javascript and software development in general as this is my first job, and right now I am debugging a critical defect related to validation.
I have a scenario where I have an array, and within that array each product has an attribute labeled Unit Volume with a code of ASC_Sales_Volume.
The current defect is that when a user configures more than one product, but leaves one with a unit volume of zero, the user is still allowed to navigate forward to the next page despite there being a validation check in place to prevent this. The condition that would block the user from moving forward is if $scope.bpTree.response.errorCheckVolume = true.
WIth what is currently written by the former dev, the forEach loop keeps changing the $scope.bpTree.response.errorCheckVolume back to false when the user clicks on the button to fire the functions.
The Next button itself is an OOTB component that I cannot modify directly.
What would be the next best step in troubleshooting this? Basically, when the forEach evaluates a product with a Unit Volume value of 0, the $scope.bpTree.response.errorCheckVolume should always equal true regardless of subsequent Unit Volume values, until the user changes the Unit Volume value for that particular product and then clicks the Next button and the logic fires again. If the error records (as in, any product in the array with a Unit Volume of 0) are greater than zero, then errorCheckVolume should always evaluate to true and the user should be blocked from progressing.
Would I use something similar to Array.prototype.some()? I've just been staring at my screen for hours trying different things to no avail.
Here is the code snippet:
function validateAllPlansUnitVolume(triggeredFromNextBtn) {
var coveragesData = $scope.sortedCoverages[$scope.childProduct.instanceKey || $scope.childProduct.productName || $scope.childProduct.Name],
errorRecords = checkForInvalidPlans(coveragesData, triggeredFromNextBtn);
if(errorRecords > 0) {
$scope.bpTree.response.errorCheckVolume = true;
}
else {
$scope.bpTree.response.errorCheckVolume = false;
}
}
function checkForInvalidPlans(coveragesData, triggeredFromNextBtn){
if(!!coveragesData && coveragesData.length) {
coveragesData.forEach(function(product){
if(!!product.attributeCategories){
unitVolumeAttrNodes = product.attributeCategories.records[0].productAttributes.records.filter((ele) => ele.code == "ASC_Sales_Volume");
if(!!unitVolumeAttrNodes && unitVolumeAttrNodes.length){
unitVolumeAttrNodes.forEach(function(attrNode){
if(unitVolumeAttrNodes[0].userValues === 0){
$scope.bpTree.response.errorCheckVolume = true;
product.unitVolumeErr = true;
product.unitVolumeErrMsg = "Unit Volume cannot be zero. Please update before proceeding to the next screen";
}
else {
product.unitVolumeErrMsg = "";
product.unitVolumeErr = false;
$scope.bpTree.response.errorCheckVolume = false;
}
});
}
}
});
return coveragesData.filter((ele) => ele.unitVolumeErr).length;
}
return 0;
}
i will suggest you should use Array.prototype.some() for this purpose. its not good practice to use foreach loop then break it on condition.
Js has different array's method for different work. i think for different requirement they created built methods otherwise we can use one method for all work,
Visit this page select array method that fit in your requirement
You can't do that with .forEach, but you can with .some:
['a', 'b', 'c'].some((element, index) => {
console.log(element);
return element == 'b' || index == 1;
});
This code logs each element of the array, discontinuing the loop after it reaches 'b' or index 1.

indexOf / includes don't do an exact match and return false positives

I want to build an if statement in which the if criteria is based on an equality test of whether a variable equals any of several values. However, I do not want to hardcode the test values, but to pass an array of values that had been randomly subset earlier.
First, I get the set of randomized values by subsetting/sampling 5 values out of an array of 15 values. Basically, I'm using this excellent solution.
function getRandomSubarray(arr, size) {
var shuffled = arr.slice(0), i = arr.length, temp, index;
while (i--) {
index = Math.floor((i + 1) * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(0, size);
}
var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var fiveRandomMembers = getRandomSubarray(x, 5);
Then, I want to pass fiveRandomMembers to test whether a variable is equal to any of the values in fiveRandomMembers's array. Then do something. To this end, I want to use this solution.
var L = function()
{
var obj = {};
for(var i=0; i<arguments.length; i++)
obj[arguments[i]] = null;
return obj;
};
if(foo in L(fiveRandomMembers)) {
/// do something
};
Unfortunately, this doesn't work for me. I must admit that the implementation of this code is within a Qualtrics survey, so the problem might be nuanced to the Qualtrics platform, and that's the reason it isn't working for me. I'm newbie to JavaScript so I apologize if this is a trivial question. But I believe that my code is problematic even in plain JavaScript (that is, regardless of Qualtrics), and I want to figure out why.
UPDATE 2020-05-24
I've been digging into this more deeply, and I have some insights. This looks more like a qualtrics problem rather than plain JS issue. However, the underlying problem might still have to do with some JS mechanism, and that's why I bother to update it here -- maybe someone will know what's causing this behavior.
To recap -- I want to condition an action based on whether a given variable's content matches either of the values in an array. I've tried using both includes and indexOf, but either method fails. The problem boils down to the functions not doing an exact match. For example, if I have an array of 5 numbers such as 8, 9, 12, 13, 14, and I want to test whether 4 exists in the array, then an exact match should return FALSE. However, both indexOf and contains return TRUE because 14 has 4 in it. This is not an exact matching then. Furthermore, I've tried to investigate what is the position indexOf would return for such a false-positive match. Typically, it would return a position that is even larger than the total length of the array, making no sense whatsoever. Here's an example from my Qualtrics survey, demonstrating the problem:
The code giving this is comprised of two qualtrics questions:
(-) First piece
Qualtrics.SurveyEngine.addOnReady(function()
{
/*Place your JavaScript here to run when the page is fully displayed*/
function getRandomSubarray(arr, size) {
var shuffled = arr.slice(0), i = arr.length, temp, index;
while (i--) {
index = Math.floor((i + 1) * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(0, size);
}
var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var fiveRandomMembers = getRandomSubarray(x, 5);
if (Array.isArray(fiveRandomMembers)) Qualtrics.SurveyEngine.setEmbeddedData('is_array', "TRUE");
Qualtrics.SurveyEngine.setEmbeddedData('length', fiveRandomMembers.length);
Qualtrics.SurveyEngine.setEmbeddedData('five_sampled_numbers', fiveRandomMembers);
});
(-) Second piece
Qualtrics.SurveyEngine.addOnReady(function()
{
jQuery("#"+this.questionId).find('.QuestionText:first').css("padding-bottom", "0px");
var currentLoopNum = "${lm://CurrentLoopNumber}";
// var currentLoopNum = parseInt(currentLoopNum, 10); // tried converting to numeric but it doesn't solve the problem
var fiveSampledNumbers = "${e://Field/five_sampled_numbers}";
if (fiveSampledNumbers.includes(currentLoopNum)) {
Qualtrics.SurveyEngine.setEmbeddedData('does_loop_number_appear', "Yes");
} else {
Qualtrics.SurveyEngine.setEmbeddedData('does_loop_number_appear', "No");
}
Qualtrics.SurveyEngine.setEmbeddedData('index_of', fiveSampledNumbers.indexOf(currentLoopNum));
});
Here is a link to the Qualtrics survey, demonstrating the problem, in case it's helpful for troubleshooting: link
However, when testing the same code outside of Qualtrics, the problem doesn't replicate.
Does someone have a clue or even a hypothesis what could be the problem with the matching? Even if you're not necessarily familiar with Qualtrics...
I've never worked with Qualtrics before, but to me it is clear that the line
var fiveSampledNumbers = "${e://Field/five_sampled_numbers}";
will assign a string value to fiveSampledNumbers, not an array value.
Indeed, if you attempt to run the checks you are making on a string rather than an array, you get the unexpected results you saw above, because you are doing string operations rather than array operations:
var fiveSampledNumbers = "6,4,10,11,15";
console.log(fiveSampledNumbers.includes(5)); // logs true (string ends with the character "5")
console.log(fiveSampledNumbers.indexOf(5)); // logs 11 (index of the character "5")
To get around this, you will have to split the string by commas and parse each number within it:
var fiveSampledNumbers = "6,4,10,11,15";
fiveSampledNumbers = fiveSampledNumbers.split(",").map(function (n) { return parseInt(n, 10); });
console.log(fiveSampledNumbers.includes(5)); // logs false
console.log(fiveSampledNumbers.indexOf(5)); // logs -1

Javascript: For Loop, need help understanding this exercise with if/else

I have this exercise that already got the answer, but after hearing the explanations, still don't understand. This is the exercise:
"write a function isUniform() which takes an array as an argument and
returns true if all elements in the array are identical"
This is the solution
function isUniform(numArr) {
var first = numArr[0];
for (var i = 1; i < numArr.length; i++) {
if (numArr[i] !== first) {
return false;
}
}
return true;
}
I got it almost right, but i did a else statement with the "return true" and it didn't work. Why does it work with the "return true" outside of the for loop?
(edited) This is how i did the first time:
function isUniform(numArr) {
var first = numArr[0];
for (var i = 1; i < numArr.length; i++) {
if (numArr[i] !== first) {
return false;
}
else {
return true;
}
}
}
If you return true outside the loop, then it checks every element in the loop until one matches the if test or it gets to the end of the loop.
If you return true inside the loop then it will always hit a return statement for the first element and then stop the loop.
I got it almost right, but i did a else statement with the "return
true" and it didn't work
The solution below would return the wrong results in some cases because all it does is find the first element within the array that's equal to first variable and return true, even though it hasn't searched the entire array.
function isUniform(numArr) {
var first = numArr[0];
for (var i = 1; i < numArr.length; i++) {
if (numArr[i] !== first) {
return false;
}
else {
return true;
}
}
}
I have this exercise that already got the answer, but after hearing
the explanations, still don't understand.
let's assume this is your array:
[10,10,13,10,10]
let's assume this is the variable first:
first = 10;
The if statement below which is within the for loop basically says if the variable first ( 10 ) is not equal to the element at the current index i (nth number within the array) then return false. This makes sense because if at this moment the variable first is not the same with the element at the specified index for example index 2 (number 13) then there is no point to carry on. Hence, it will return false.
if (numArr[i] !== first) {
return false;
}
now let's assume the array is:
[10,10,10,10,10]
let's assume this is the variable first:
first = 10;
now the variable first will be compared against each elementwithin the array and it says "is 10 not equal to the current element". in this case that's false because 10 is equal to 10. This will propagate down the array and control will never pass inside the if block. Eventually, control passes down to the return true statement.
if (numArr[i] !== first) {
return false;
}
It works because is the final statement in your function. Basically your function will return true if the condition inside for loop will not be triggered
Let's say you've got a broken printer which once in a while messes the printout. Now you printed 20 copies and want to know if every paper is fine. So now you would have to iteratively compare every copy until you found one which is not matching (and know it's time to get a new printer?). Or you've gone the way through the hole stack and know every copy is fine (and you've wasted time for nothing).

Text Input Value sometimes does't change even though it thows no errors, and seems to work

So I have a simple program to change the value of an input field every time you blur it. It logs the already used values in an array, an I use that array to check if it's been used. It practically works as intended, but after a few tries it will return true and logs, yet the value wont change.
Updated Code:
var dftvalue = ['Freddy the Grocer', 'Jack the Fiddler', 'Cane the Sheep Herder', 'Arnold the Fish Monger', 'Luke the Car Salesman', 'Josh the Tailor', 'Carol the Baker', 'Tiara the Nacho Vendor', 'example#email.com', 'Your message here.'];
var logused = new Array(); //create new array to log the used indexs
function setdftvalue() {
var newval = dftvalue[Math.floor(Math.random() * 7)];
if (logused.indexOf(newval) == -1) {
this.value=newval;
logused.push(newval);
console.log(logused);
} else if (logused.indexOf(newval) >= 0) {
setdftvalue();
}
if (logused.length == 8) {
for (i=0; i<=7; i++){
logused.pop();
}
}
}
document.getElementById('formname').onblur=setdftvalue;
JSFIDDLE
https://jsfiddle.net/e5pdz37e/8/
Your approach is unnecessarily complicated. At a high level I would recommend an approach that's more like this:
function setdftvalue() {
if (index === (dftvalue.length - 1)) {
// Shuffle your names array
index = -1;
}
input.value = dftvalue[++index];
}
This way you won't need to use any recursion and make unnecessary function calls. And the only time you'll need to randomize is when you've used up all of your available names.
Here's a working example: http://jsfiddle.net/bvaughn/163mqdeL/
Original answer
After a few invocations, your function will fill up the logused Array, at which point calling it again will do nothing. Actually, worse than nothing - it will recursively call itself without end.

Check if number is in an array an generate new number if it is

I'm making a simple script to create a random number. The intent of the script is to ultimately cache each random number in an array and then loop through this array when generating new numbers to see if the previous one had already been used. If it has been used then the script would continue looping until a number is created that is not in the array, and then return that number. I wrote the code below and the logic is simple enough but I don't know why it works when launched sometimes and then blows the stack at other times without returning a number.
var oldNumbs = [1,2,3,4,6,7] // dummy data
var randomNumb = Math.floor((Math.random() * 10) +1);
function checkIfInArray(value, array) {
if(array.indexOf(value) > -1){
randomNumb = Math.floor((Math.random() * 10) +1);
checkIfInArray(value, array)
}
else{
return randomNumb
}
}
console.log(checkIfInArray(randomNumb,oldNumbs)); /* sometimes returns a number not in the array and sometimes blows the stack. It mostly blows the stack. */
Simple mistake it is. You need to pass randomNumb instead of value since randomNumb is the new random number you generated.
checkIfInArray(randomNumb , array)
Your 'undefined' comes because you don't return anything if the number is already in the array.
function checkIfInArray(value, array) {
if(array.indexOf(value) > -1){
var randomNumb = Math.floor((Math.random() * 10) +1);
value = checkIfInArray(randomNumb, array);
}
return value;
}
Is this your actual code because you've passing value in to the recursive call to checkIfInArray, this just passes the same random number over and over again, if that number was already in the array it crashes, change it to randomNumb and you should be fine.

Categories