Selecting another value from array in javascript - javascript

What is the best way in JavaScript given an array of ids:
var ids = ['ax6484', 'hx1789', 'qp0532'];
and a current id hx1789 to select another value at random that is not the current from the ids array?

Get the index of the value, generate a random value, if the random is the index, use 1 less (depending on random generated)
var random = Math.floor(Math.random() * ids.length)
var valueIndex = ids.indexOf("hx1789");
if (random === valueIndex) {
if (random === 0) {
random = 1;
} else {
random = random - 1;
}
}
var randomValue = ids[random];
Demo: http://jsfiddle.net/sxzayno7/
And yeah, test the array length, if it's 1 - probably don't want to do this! Thanks #ThomasStringer
if (ids.length > 1) { //do it! }
Or just filter out the value and pull against that:
var randomIndex = Math.floor(Math.random() * (ids.length - 1))
var random = ids.filter(function(id) {
return id !== "hx1789";
})[randomIndex]

I would probably do something along the lines of:
var current = 'hx1789'; // whatever variable stores your current element
var random = Math.floor(Math.random() * ids.length);
if(random === ids.indexOf(current)) {
random = (random + 1) % ids.length;
}
current = ids[random];
Basically if the newly picked element sits on the same index, you just pick the next element from the array, or if that goes out of bounds, pick the first.

UnderscoreJS is your best friend!
_.sample(_.without(['ax6484', 'hx1789', 'qp0532'], 'hx1789'));
or with variables;
var myArray = ['ax6484', 'hx1789', 'qp0532'];
var currentId = 'hx1789';
var newRandomId = _.sample(_.without(myArray , currentId));

You could make a duplicate array, then remove the current indexed element from that array and select an random element from the duplicate with the removed item:
var ids = ['ax6484', 'hx1789', 'qp0532'];
var currentIndex = ids.indexOf('hx1789');
var subA = ids.slice(0);
subA.splice(currentIndex , 1);
var randItem = subA[Math.floor((Math.random() * subA.length))];
Example Here.

This gets trickier if your value is not a simple string such as hx1789 but rather for instance comes from an array within the array you want to generate a different value from:
let weekSegments = [["Su","Mo"],["Tu","We"],["Th","Fr","Sa"]];
If you have the index it's simple, no matter how deeply nested the array (or object) is and/or the types of the values it contains:
let index = 1;
let segment = weekSegments[index];
let randomIndex;
let randomSegment;
if (weekSegments.length >= 1) {
randomIndex = Math.floor(Math.random) * (weekSegments.length - 1);
randomSegment = weekSegments.filter((val, i) => i !== index)[randomIndex];
}
Without the index though things get more complicated due to having to test for array and/or object equality in the filter function. In the case above it's fairly easy because it's an array that is only one-level deep which in turn only contains simple values. First we create a string representation of the sorted segment values we already have, then compare that to a string representation of val on each filter iteration:
let segmentVals = segment.sort().join(',');
if (weekSegments.length > 1) {
randomIndex = Math.floor(Math.random) * (weekSegments.length - 1);
randomSegment = weekSegments.filter((val) => {
return segmentVal !== val.sort().join(',');
})[randomIndex];
}
When dealing with objects rather than arrays and/or arrays/objects that are deeply nested and/or contain non-nested values it devolves into the sticky matter of object equality that is probably not entirely germane to this Q&A

Related

How does randomizing two arrays without mismatching in JavaScript work?

Trying to understand an answer from a previous question.
I found this question and subsequently the answer provided by #obscure, which I was able to modify based on what I needed. It worked perfectly! So I'm just trying to make sure I learn and understand exactly what's happening here.
Best I understand currently:
tempA = fileNames[a]; and tempA = trackTitles[a]; are temporarily storing the current location in the iteration, but I'm not sure what happens at fileNames[tempB] = tempA; and trackTitles[tempB] = tempA;.
tempB = Math.floor(Math.random() * fileNames.length); is generating a random index within the questions array.
fileNames[a] = fileNames[tempB]; and trackTitles[a] = trackTitles[tempB]; are swapping the current index with the randomly generated index.
Any help is appreciated!
this.fileNames = ["fileA", "fileB", "fileC"];
this.trackTitles = ["titleA", "titleB", "titleC"];
function shuffle() {
var tempA;
var tempB;
for (var a = 0; a < fileNames.length; a++) {
tempA = fileNames[a];
tempB = Math.floor(Math.random() * fileNames.length);
fileNames[a] = fileNames[tempB];
fileNames[tempB] = tempA;
tempA = trackTitles[a];
trackTitles[a] = trackTitles[tempB];
trackTitles[tempB] = tempA;
}
}
shuffle();
console.log(fileNames);
console.log(trackTitles);
The code iterates over the array and for each element it stores the element with index a in the variable tempA and a random index is generated and stored in tempB hence the line tempB = Math.floor(Math.random() * fileNames.length);. In the following lines the element at the current Index a and the element at the randomly generated index tempB are swapped, same with thing with the second array. The randomly generated index is used on both arrays so it doesn't missmatch, which is exactly what you want.
This algorithm has a problem tho, because it could potentially reverse itself:
Lets say you have the following array ["a", "b", "c", "d"] and use the above algorithm on that array, then you could potentially swap a → b, b → a, c → d and d → c, ending up with the same array as you began with.
I would recommend you to use the Fisher-Yates Shuffle algorithm. This algorithm also has the chance to return the exacted same array, but it at least doesn't reverse the steps it did before. The idea of the algorithm is that the array builds itself up from a pool of elements you can randomly choose from, and if that element has been chosen, it can't be chosen again and is removed from that pool (It actually works like selection-sort, if you are familiar with that, but instead of choosing the max or min element of the rest, you just choose a random one).
Implementation:
this.fileNames = ["fileA", "fileB", "fileC"];
this.trackTitles = ["titleA", "titleB", "titleC"];
function shuffle() {
let currentIndex = this.fileNames.length-1, randomIndex;
while (currentIndex > 0) {
randomIndex = Math.floor(Math.random() * (currentIndex+1));
[this.fileNames[currentIndex], this.fileNames[randomIndex]] =
[this.fileNames[randomIndex], this.fileNames[currentIndex]];
[this.trackTitles[currentIndex], this.trackTitles[randomIndex]] =
[this.trackTitles[randomIndex], this.trackTitles[currentIndex]];
currentIndex--;
}
}
shuffle();
console.log(fileNames);
console.log(trackTitles);
You could modify the algorithm though so that indices don't choose themselves. This way, you won't get the same exact array.
this.fileNames = ["fileA", "fileB", "fileC", "fileD", "fileE", "fileF"];
this.trackTitles = ["titleA", "titleB", "titleC", "titleD", "titleE", "titleF"];
const generateRandom = (n, ex) => {
const r = Math.floor(Math.random() * (n+1))
return r == ex ? generateRandom(n, ex) : r;
}
function shuffle() {
let currentIndex = this.fileNames.length-1, randomIndex;
while (currentIndex > 0) {
randomIndex = generateRandom(currentIndex, currentIndex);
[this.fileNames[currentIndex], this.fileNames[randomIndex]] =
[this.fileNames[randomIndex], this.fileNames[currentIndex]];
[this.trackTitles[currentIndex], this.trackTitles[randomIndex]] =
[this.trackTitles[randomIndex], this.trackTitles[currentIndex]];
currentIndex--;
}
}
shuffle();
console.log(fileNames);
console.log(trackTitles);

How do I repeat an iteration if a condition is true in JavaScript?

I am trying to display up to three recipes from an API that specifically include bacon as an ingredient. The API only has 10 recipes that meet this criteria, so I am running into an issue where the same recipes are sometimes being repeated on the page if the user wishes to view two or three recipes. How can I set a condition to check if the random number that I am generating and storing in an array is a duplicate value? If a duplicate, then I want for the iterator to be subtracted by 1 and the for loop to continue. I've listed the code I've I appreciate any feedback that is provided!
// The number of recipes the user would like to display//
var recipeNumber = $("#recipe-input").val();
var parsedInput = parseInt(recipeNumber);
// creating an empty array that will story the random numbers that are generated//
var ranNumArr = [];
console.log(ranNumArr);
for (i = 0; i < parsedInput; i++) {
// generate a random number based on the length of the recipe API's array of bacon recipes (10) and push it into the ranNumArr//
var randomNumber = Math.floor(Math.random() * 10);
ranNumArr.push(randomNumber);
// If the value of the index in the array is equal to a previous index's value, repeat the iteration//
if (ranNumArr[i] === ranNumArr[i -1] || ranNumArr[i] === ranNumArr[i -2]){
console.log("this is a duplicate number")
i = i - 1
}
// else, display the recipe on the card//
else {
randomRecipe = ranNumArr[i]
// Create cards that will house the recipes//
var recipeCell = $("<div>").attr("class", "cell");
$("#recipes-here").append(recipeCell);
var recipeCard = $("<div>").attr("class", "card");
recipeCell.append(recipeCard);
var recipeSection = $("<div>").attr("class", "card-section");
recipeCard.append(recipeSection);
var cardTitleE1 = $("<h1>");
cardTitleE1.attr("id", "recipe-title");
var cardImageE1 = $("<img>");
cardImageE1.attr("id", "recipe-image");
var cardTextE1 = $("<a>");
cardTextE1.attr("id", "recipe-link");
// Adding the recipe title, url, and image from the API call//
cardTitleE1.text(response.hits[randomRecipe].recipe.label);
cardTextE1.text("Click here for link to recipe");
cardTextE1.attr("href", response.hits[randomRecipe].recipe.url);
cardTextE1.attr("target", "_blank");
cardImageE1.attr("src", response.hits[randomRecipe].recipe.image);
// Display the recipe on the DOM//
recipeSection.append(cardTitleE1);
recipeSection.append(cardImageE1);
recipeSection.append(cardTextE1);
}
}
You can use a Set to store numbers that have already been chosen.
const set = new Set;
//....
if (set.has(randomNumber)){
console.log("this is a duplicate number");
i--;
} else {
set.add(randomNumber);
//...
Alternatively, as Barmar suggested, you can shuffle the array of integers from 0 to 9 beforehand and then loop over the values for better efficiency. Below I have provided an example using the Fisher-Yates shuffle.
const arr = [...Array(10).keys()];
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.random() * (i + 1) | 0;
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
for(const num of arr){
//do something with num...
}

How to add to an array in Javascript by inserting a number and moving the numbers after to the right

I am trying to insert a value into an array. I am not changing the size of the array. All I want to do is insert a value and then move all numbers after the insertion to the right using this algorighm:
Go to the last element in the array n = (length-1)
If it is not the passed index (n > index), set it's value to the value of the previous element A(n) = A(n-1)
If it is the passed index (n = index), set the value to the passed value A(n) = value and exit
Move left one element n = n-1
Repeat steps 2, 3, and 4
How do I do this? Also, I can't use any built-in array functions. Here is an example of my Javascript code:
var array = [];
for(var i=1; i<=1000; i++) {
array.push(Math.round(Math.random()*100));
}
function InsertIntoArray(array,index,number){
var numCount = 0
var move = 0 ;
for(var move = array.length - 1; move > index; move--)
{
if (move > index)
{
array[i] = array[i-1];
numCount ++;
}
else (move == index)
{
array[index] = number;
numCount++;
break;
}
}
console.log(move);
console.log(numCount);
console.log(array);
}
console.log(array);
InsertIntoArray(array, 1, 11);
You're pretty close but doing a log more than required. Hopefully the comments are sufficient:
// Just use simple test cases initially
var array = [0,1,2,3];
// The original function had some unused and pointless variables,
// they're removed
function insertIntoArray(array, index, value){
// Don't allow index to be greater than length - 1
if (index > array.length - 1) return;
// Loop until the required index is reached, shifting
// values to the next higher index
for(var move = array.length - 1; move > index; move--) {
array[move] = array[move - 1];
}
// Must now have reached required index and have shifted
// values, so just insert
array[index] = value;
}
// Original array
document.write(array + '<br>');
// Do insert
insertIntoArray(array, 2, 15);
// Modified array
document.write(array);
Note that you can have sparse arrays, the above will create new elements in such arrays so they aren't sparse any more. Also, for large arrays, it will be quite inefficient, Barmar's splice + slice answer is likely better in that regard, though it does change length along the way.
You could separate the arrays into a left and right array at the index, add the item onto the left array, and remove the last item from the right one. Then combine the two arrays:
function InsertIntoArray(array,index,number){
var leftArray = array.slice(0, index);
var rightArray = array.slice(index, array.length - 1);
leftArray.push(number);
return leftArray.concat(rightArray);
}
Fiddle Example. Note using return is to change the value of the array given other than the local array value. Simply changin array in the function will not change the global array variable.
The problem with your loop was that you were using array[i] = array[i-1], but the index variable in your loop was move, not i.
You don't need to do the if inside the loop. Just insert the new element when the loop is done. You also had a syntax error in the else -- you don't put a test after else, it's automatically the opposite of the if.
function InsertIntoArray(array, index, number) {
// Move all the elements after index up by 1
for (var move = array.length - 1; move > index; move--) {
array[move] = array[move - 1];
}
// Insert the new element
array[index] = number;
}
var array = [];
for (var i = 1; i <= 30; i++) {
array.push(i);
}
document.getElementById("before").textContent = JSON.stringify(array);
InsertIntoArray(array, 1, 11);
document.getElementById("results").textContent = JSON.stringify(array);
<b>Before:</b>
<div id="before"></div>
<b>After:</b>
<div id="results"></div>

How to get random string by click from array without repetition?

The Idea is: click a button to replace a headline with a unique string of an array. The problem with this is that I've used a string of the array before like this:
headlines = new Array("Good", "Bad", "Ugly", "Random Headline");
var randomNumberBefore = 4;
alert (headlines[randomNumberBefore]);
but I dont want to display the same headline again, thatswhy it is key check that the actual index randomNumberBefore is not the same number like new index randomNumber. The following function makes sense to me, but sometimes it returns the same number, that causes the headline is replaced by itself and the user noticed no change.
function randomNumberByRange (range, number) {
var r;
do {
var r = Math.floor(Math.random() * range);
} while (r == number);
return r;
}
$(document).on('click','.nextquote' , function() {
var randomNumber = randomNumberByRange( headlines.length, randomNumberBefore);
var nextHeadline = headlines[randomNumber];
$(".bannertext").text(nextHeadline);
console.log(nextHeadline);
});
Any Ideas to get unique headlines per click?
Here is my starting fiddle.
--
Here is the final fiddle.
You forgot to assign the old value to randomNumberBefore;
after
var randomNumber = randomNumberByRange( headlines.length, randomNumberBefore);
put
randomNumberBefore = randomNumber;
PS: There is a way to make the randomNumberByRange function more performant:
function randomNumberByRange (range, number) {
var r = Math.floor(Math.random() * (range-1));
if(r >= number)r++;
return r;
}
but, if you have many headlines, your function is good enough (as collision probability drops with the number of items you have in the list)
If you don't want repetition, simply remove the used elements from the array.
var h = new Array("Good", "Bad", "Ugly", "Random Headline");
while (h.length > 0) {
var i = Math.floor(Math.random() * h.length);
var t = h.splice(i, 1);
console.log(t);
}

How to get a unique random integer in a certain range for every number in that range in javascript?

I have:
function getRandomInt(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min;
}
But the problem is I want randomise the population of something with elements in an array (so they do not appear in the same order every time in the thing I am populating) so I need to ensure the number returned is unique compared to the other numbers so far.
So instead of:
for(var i = 0; i < myArray.length; i++) {
}
I have:
var i;
var count = 0;
while(count < myArray.length){
count++;
i = getRandomInt(0, myArray.length); // TODO ensure value is unique
// do stuff with myArray[i];
}
It looks like rather than independent uniform random numbers you rather want a random permutation of the set {1, 2, 3, ..., N}. I think there's a shuffle method for arrays that will do that for you.
As requested, here's the code example:
function shuffle(array) {
var top = array.length;
while (top--) {
var current = Math.floor(Math.random() * top);
var tmp = array[current];
array[current] = array[top - 1];
array[top - 1] = tmp;
}
return array;
}
Sometimes the best way to randomize something (say a card deck) is to not shuffle it before pulling it out, but to shuffle it as you pull it out.
Say you have:
var i,
endNum = 51,
array = new Array(52);
for(i = 0; i <= endNum; i++) {
array[i] = i;
}
Then you can write a function like this:
function drawNumber() {
// set index to draw from
var swap,
drawIndex = Math.floor(Math.random() * (endNum+ 1));
// swap the values at the drawn index and at the "end" of the deck
swap = array[drawIndex];
array[drawIndex] = array[endNum];
array[endNum] = swap;
endNum--;
}
Since I decrement the end counter the drawn items will be "discarded" at the end of the stack and the randomize function will only treat the items from 0 to end as viable.
This is a common pattern I've used, I may have adopted it into js incorrectly since the last time I used it was for writing a simple card game in c#. In fact I just looked at it and I had int ____ instead of var ____ lol
If i understand well, you want an array of integers but sorted randomly.
A way to do it is described here
First create a rand function :
function randOrd(){
return (Math.round(Math.random())-0.5); }
Then, randomize your array. The following example shows how:
anyArray = new Array('1','2','3','4','5');
anyArray.sort( randOrd );
document.write('Random : ' + anyArray + '<br />';);
Hope that will help,
Regards,
Max
You can pass in a function to the Array.Sort method. If this function returns a value that is randomly above or below zero then your array will be randomly sorted.
myarray.sort(function() {return 0.5 - Math.random()})
should do the trick for you without you having to worry about whether or not every random number is unique.
No loops and very simple.

Categories