I have a list of team members and a list of 2 substitutes:
team = Samson, Max, Rowan, Flynn, Jack
subs = Struan, Harry
I need to randomly swap the 2 subs into the team. I'm getting stuck because I need to ensure that only these 2 elements are swapped and that both are swapped in. I tried just looping through the subs array and randomly swapping each element with an element from the team array, but too frequently it swapped Struan with Max and then Harry with Struan, so that in the end Struan was still in the subs array and not in the team.
So: I need to exclusively swap the 2 elements in the sub array with random elements from the team array. How can I do that?
You can do this with this function. Give the function current team and substitudes and get new team with random substitution done.
const team = ['Samson', 'Max', 'Rowan', 'Flynn', 'Jack']
const subs = ['Struan', 'Harry']
const substitudePlayers = (team, subs) => {
const newTeam = [...team]
// get 2 random numbers which are less than team members count
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max-1);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 2 random numbers
let num1 = getRandomInt(0, team.length)
let num2 = getRandomInt(0, team.length)
// make sure that numbers are different
while(num2 === num1) num2++
// substitude
newTeam[num1]=subs[0]
newTeam[num2]=subs[1]
return newTeam
}
const substitudedTeam = substitudePlayers(team, subs);
console.log(substitudedTeam)
This should work:
function getRandomItem () {
let randomItem = team[Math.floor(Math.random()*team.length)];
if(subs.indexOf(randomItem)<0){
return randomItem;
} else {
return getRandomItem();
}
}
subs.forEach((item,i)=>{
subs.splice(i,1,getRandomItem());
}
I have a number of embedded images, which I randomize 4 times without replacement (once an image is seen, you cannot see it again). I'd like to add a condition, which suggests that a set of additional images cannot be seen (not only the image that was previously selected). These are images that have similar traits to the one selected.
To demonstrate:
Let's say I have the following array of vars:
BF1, BA1, BF2, BA2, BF3, BA3
I want to randomly draw 3 vars (images) out of the array without replacement, AND I want vars that have the number 2 (same set) to be removed from the next array as well. So, if the first drawn var is BF2, the next draw will be from the following array:
BF1, BA1, BF3, BA3 (only one of these options can randomly appear)
Now let's say I drew the var BF1, so the next set of possible vars will be:
BF3, BA3.
I hope this makes sense. This is the code I have so far for the drawing without replacement:
function shuffle(array){
var counter = array.length,
temp, index;
while (counter > 0){
index = Math.floor(Math.random() * counter);
counter = counter-1;
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
var myArray = [BF1,BA1,BF2, BA2, BF3,BA3, BA4, BF4, BA5, BF5, BF6, BA6, BF7, BA7, BA8, BF8, BA9, BF9, BF10, BA10, BA11, BF11, BA12, BF12, BA13, BF13, BA14, BF14, BA15, BF15, BA16, BF16, BA17, BF17, BA18, BF18, BA19, BF19, BA20, BF20, BA21, BF21, BF22, BA23, BF23, BA24, BF24, BA25, BF25, BA26, BF26, BA27, BF27, BA28, BF28, BA29, BF29, BA30, BF30, BA31, BF31, BA32, BF33, BA33, BA34, BF35, BA35, BA36, BF36];
shuffle(myArray)
You can definitely implement this in any number of ways, but no matter what you use, you'll need to perform the following 3 steps in some capacity (I split them out into separate methods, but you can combine them as you see fit):
Shuffle the list
Pick an item
Filter out the items matching the pick (in this case, those with the same number)
You have the shuffle routine covered, so that just leaves the pick and the filter.
For the pick, I just used Math.random to pull a random member of the list:
return array[Math.floor(Math.random() * array.length)];
For the filter, I used Array.prototype.filter to pull out the desired items. In this case, with the strings, I parse the number out of the string and then remove any items in the array that have the same number as the last pick:
return array.filter(el => +el.match(/\d+/).join() != +picked.match(/\d+/).join());
But with actual images, you'll just replace that with however you read the labels of your images.
Example
Here's the full working example, with the list of picks first, followed by a sorted array of the picks showing they were all used.
var imageList = ['BF1', 'BA1', 'BF2', 'BA2', 'BF3', 'BA3', 'BA4', 'BF4', 'BA5', 'BF5', 'BF6', 'BA6', 'BF7', 'BA7', 'BA8', 'BF8', 'BA9', 'BF9', 'BF10', 'BA10', 'BA11', 'BF11', 'BA12', 'BF12', 'BA13', 'BF13', 'BA14', 'BF14', 'BA15', 'BF15', 'BA16', 'BF16', 'BA17', 'BF17', 'BA18', 'BF18', 'BA19', 'BF19', 'BA20', 'BF20', 'BA21', 'BF21', 'BF22', 'BA23', 'BF23', 'BA24', 'BF24', 'BA25', 'BF25', 'BA26', 'BF26', 'BA27', 'BF27', 'BA28', 'BF28', 'BA29', 'BF29', 'BA30', 'BF30', 'BA31', 'BF31', 'BA32', 'BF33', 'BA33', 'BA34', 'BF35', 'BA35', 'BA36', 'BF36'];
var selection = imageList.slice();
var picked = [];
function shuffle(array) {
var counter = array.length, temp, index;
while (counter > 0) {
index = Math.floor(Math.random() * counter);
counter = counter - 1;
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
}
function pick(array) {
return array[Math.floor(Math.random() * array.length)];
}
function filterPicked(picked, array) {
return array.filter(el => +el.match(/\d+/).join() != +picked.match(/\d+/).join());
}
while (selection.length) {
// 1. Shuffle
shuffle(selection);
// 2. Pick
picked.push(pick(selection));
// 3. Filter
selection = filterPicked(picked[picked.length-1], selection);
}
console.log(`Picks: [${picked.join(', ')}]`);
console.log(`Sorted picks: [${picked.sort((a, b) => +a.match(/\d+/).join() - +b.match(/\d+/).join()).join(', ')}]`);
Step-by-step
Shuffle the selection array (a copy of the full array or all selections)
Pick an item off the selection array, push it onto the array of picks
Filter the selection array to remove items matching the last pick
Repeat 1-3 with each newly filtered array, until no selections remain
You can shuffle array with loop and random numbers, then in another loop extract first image in resulting array, filter array with numbers at the end of string
var myArray="BF1, BA1, BF2, BA2, BF3, BA3, BA4, BF4, BA5, BF5, BF6, BA6, BF7, BA7, BA8, BF8, BA9, BF9, BF10, BA10, BA11, BF11, BA12, BF12, BA13, BF13, BA14, BF14, BA15, BF15, BA16, BF16, BA17, BF17, BA18, BF18, BA19, BF19, BA20, BF20, BA21, BF21, BF22, BA23, BF23, BA24, BF24, BA25, BF25, BA26, BF26, BA27, BF27, BA28, BF28, BA29, BF29, BA30, BF30, BA31, BF31, BA32, BF33, BA33, BA34, BF35, BA35, BA36, BF36";
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
const arr = shuffle(myArray.split(','));
function draw(a, times) {
let res =[]
for (let i = 1; i <= times; i++) {
let str = a[0]
res.push(str)
a = a.filter(a => parseInt(a.match(/\d+$/)[0], 10) !== parseInt(str.match(/\d+$/)[0], 10))
}
return res
}
console.log(draw(arr, 4))
I have a function that generates an array of random numbers. It works, but I feel that it might works slow on big numbers. Is there a way how to optimize it?
function renerateRandomNumbers(maxNumber, randomNumbersCount) {
let i;
const arrResult = [];
for (i = 0; i < randomNumbersCount; i++) {
let rand = Math.random() * (maxNumber);
rand = Math.round(rand);
if (arrResult.indexOf(rand) === -1 ) {
arrResult.push(rand);
} else {
i--;
}
}
return arrResult;
}
EDIT - To any future users, #ScottSauyet's solution should be the accepted answer. It is a more consistently efficient solution than mine.
I think the most algorithmically efficient way to solve this would be to generate the list of all possible numbers from 0-maxNumber, shuffle that array (O(n)), and then take the first randomNumbersCount numbers from the shuffled array. It would look like the following:
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
function generateRandomNumbers(maxNumber, randomNumbersCount) {
var possibleNumbers = [];
// populate array with all possible values
for (var i = 0; i <= maxNumber; i++) { possibleNumbers.push(i); }
// shuffle the array to get a random order of the possible numbers O(n)
shuffleArray(possibleNumbers);
// trim the array down to only the first n numbers where n = randomNumbersCount
possibleNumbers.length = randomNumbersCount;
return possibleNumbers;
}
console.log (generateRandomNumbers(10, 5));
console.log (generateRandomNumbers(10, 5));
console.log (generateRandomNumbers(10, 5));
The problem of your code is that complexity grows geometrically because it have a chance generate number that was already picked multiple times.
What we need to achieve is to get number on every iteration to achieve iterations count to be equal to the randomNumbersCount.
How to avoid multiple same random numbers?
let's say you want to have 5 random numbers from 0-10 range
First iteration
Create an array with values var candidates = [0,1...10]
Generate random number let's say 0
Store the number candidates[0] in results
Remove 0 from candidates. To avaoid reindexing of the candidates array we will put candidates[candidates.length - 1] into candidates[0] and remove candidates[candidates.length - 1]
and then will do this operation randomNumbersCount times.
Second iteration
Our candidates array is now [10,1,2,3,4,5,6,7,8,9]
Generate random number let's say 0 again. Wow we generated similar random number, but so what?
we alreay have 0 in our results, but candidates[0] is not a 0 anymore candidates[0] is 10 right now
so we pick candidates[0] that is 10 and will store it and remove it from candidates. Put candidates[candidates.length - 1] (9) into candidates[0] and remove candidates[candidates.length - 1]
our result is [0, 10] right now
Third iteration
Our candidates is now [9,1,2,3,4,5,6,7,8]
Generate random number let's say 0
we are not worring anymore because we know that candidates[0] is 9
add candidates[0] (witch is 9) we are saving to results, and remove it from candidates
our result is [0,10,9], candidates is [8,1,2,3,4,5,6,7]
And so on
BTW implementation is much shorter than explanation:
function renerateRandomNumbers(maxNumber, randomNumbersCount) {
var candidates = [...Array(maxNumber).keys()];
return Array(randomNumbersCount).fill()
.map(() => {
const randomIndex = Math.floor(Math.random() * candidates.length)
const n = candidates[randomIndex]
candidates[randomIndex] = candidates[candidates.length - 1]
candidates.length = candidates.length - 1
return n
})
.sort((a, b) => a - b) // sort if needed
}
console.log (renerateRandomNumbers(10, 5))
The solution from mhodges is reasonably efficient, but only when the sought count is fairly close to the max number. If your count is significantly smaller, this can be a problem, as the solution is O(m + n) where m is the maximum and n is the desired count. It's also O(m) in space. If m is large, this could be a problem.
A variant would make this approximately O(n) in time and space, by doing the same thing, but stopping the shuffle when when we've reached count items and by not pre-filling the array but instead defaulting to its indices.
function venerateRandomNumbers(max, count) {
// todo: error if count > max
const arr = new Array(max + 1)
for (let i = max; i > max - count; i--) {
const j = Math.floor(Math.random() * (i + 1))
const temp = arr[j] || j
arr[j] = arr[i] || i
arr[i] = temp
}
return arr.slice(-count)
}
console.log(venerateRandomNumbers(1000000, 10))
You can see performance comparisons on repl.it
I am new to Javascript and working with the basics. I am wanting to create an array whose individual elements are randomly drawn, one at a time, with a click of a button, until all array elements are displayed on the screen. The code I have is almost there. But the issue is that when it runs, it always grabs 2 elements on the first button click, rather than 1. It runs well for the remaining elements. Sure would appreciate some insight to this problem. Thank you.
var myArray=['1','2','3','4','5','6','7']
var text = "";
var i;
function RandomDraw() {
for(i = 0; i < myArray.length; i+=text) {
var ri = Math.floor(Math.random() * myArray.length);
var rs = myArray.splice(ri, 1);
document.getElementById("showSplice").innerHTML = text+=rs;
//document.getElementById("showArrayList").innerHTML = myArray;
}
}
It "always" draws 2 elements because of the i+=text. Your array is small thus the loop needs 2 iteration (of cocatinating the strings to get the number i) to go over myArray.length.
First iteration:
i = 0 => 0 < myArray.length => true
prints number
Second iteration: (say '4' get choosen)
i = i + text and text = '4' => i = "04" => "04" < myArray.length => true
prints number
Third iteration: (say '3' get choosen)
i = i + text and text = '43' => i = "0443" => "0443" < myArray.length => false
loop breaks
So there is a possibility that two elements get printed. Depending on the length of the array, there could be more.
You don't need the loop, just choose a number and print it:
function RandomDraw() {
if(myArray.length > 0) { // if there still elements in the array
var ri = Math.floor(Math.random() * myArray.length); // do your job ...
var rs = myArray.splice(ri, 1);
document.getElementById("showSplice").textContent = rs; // .textContent is better
}
else {
// print a message indicating that the array is now empty
}
}
Another solution is to shuffle the array and then, on each click, pop the element from the shuffled array.
function shuffle(array) {
return array.sort(function() { return Math.random() - 0.5; });
}
var button = document.getElementById('button');
var origin = ['1','2','3','4','5','6','7'];
var myArray = shuffle(origin);
var currentValue = null;
button.onclick = function() {
currentValue = myArray.pop();
if(!!currentValue) {
console.log(currentValue);
}
}
<button id='button'>
get element
</button>
You can shuffle the array again on each click, but I think it is not necessary whatsoever...
If you're wondering about Math.random() - 0.5:
[...] Math.random is returning a number between 0 and 1. Therefore, if you call Math.random() - 0.5 there is a 50% chance you will get a negative number and 50% chance you'll get a positive number.
If you run a for loop and add these results in an array, you will effectively get a full distribution of negative and positive numbers.
https://teamtreehouse.com/community/mathrandom05
I would do it this way:
let myArray=['1','2','3','4','5','6','7']
function RandomDraw(){
const selectedIndex = Math.floor(Math.random() * myArray.length);
const selected = myArray[selectedIndex]
myArray = myArray.slice(0, selected).concat(myArray.slice(selected + 1));
return selected;
}
Every time you call RandomDraw it will return a random number, without repeating.
The way I understand it, you want to draw every items from the array after a single click. So the loop is needed.
As others have said, there are several issues in your for loop :
that i+= text makes no sense
you are looping until i reaches the length of your array, but you are splicing that array, hence reducing its length
You could correct your for loop :
function RandomDraw() {
var length = myArray.length;
var ri = 0;
for (var i=0;i<length;i++) {
ri = Math.floor(Math.random() * myArray.length);
console.log("Random index to be drawn : " + ri);
// removing that index from the array :
myArray.splice(ri, 1);
console.log("myArray after a random draw : ", myArray);
}
}
Or, you could use a while loop :
function RandomDraw() {
var ri = 0;
while (myArray.length > 0) {
ri = Math.floor(Math.random() * myArray.length);
console.log("Random index to be drawn : " + ri);
// removing that index from the array :
myArray.splice(ri, 1);
console.log("myArray after a random draw : ", myArray);
}
}