2

How do I get 5 elements from an array without repetition?

I'm trying to create an online version of story dice using Tumult Hype. All that I need to do is choose 5 image names from an array without repetition. But I just can't get it to work.

I've tried borrowing code from other stackoverflow answers and I can't get them working.

The code below is currently working but gives me repeats. How do I tinker with it to eliminate the repeats?

(You can see it in action here: https://davebirss.com/storydice/)

I know that it's probably verbose and inelegant. I only speak pidgin javascript, I'm afraid. And what I'm trying to do is currently beyond my ability.

Thank you so much in advance for your help.

var diceRoll = ['${resourcesFolderName}/story_dice1.png',
        '${resourcesFolderName}/story_dice2.png',
        '${resourcesFolderName}/story_dice3.png',
        ...,
        '${resourcesFolderName}/story_dice51.png']

function choose(n, arr) {
    while (arr.length > n) {
        var del = Math.floor(Math.random() * arr.length);
        arr = arr.filter(function(item, i) {
            return i !== del;
        });
    }
    return arr;}

var result1 = [choose(1, diceRoll)];
var result2 = [choose(1, diceRoll)];
var result3 = [choose(1, diceRoll)];
var result4 = [choose(1, diceRoll)];
var result5 = [choose(1, diceRoll)];

hypeDocument.getElementById("dice1").innerHTML = "<img src='"+result1+" 'height='125' width='125'>";
hypeDocument.getElementById("dice2").innerHTML = "<img src='"+result2+" 'height='125' width='125'>";
hypeDocument.getElementById("dice3").innerHTML = "<img src='"+result3+" 'height='125' width='125'>";
hypeDocument.getElementById("dice4").innerHTML = "<img src='"+result4+" 'height='125' width='125'>";
hypeDocument.getElementById("dice5").innerHTML = "<img src='"+result5+" 'height='125' width='125'>";

Update

Thank you all for your help. I'm sure all the answers were great but the snippet from U25lYWt5IEJhc3RhcmQg is the code that I managed to successfully incorporate. For the record, this is how I did it:

const rollTheDice = (arr, n) => {
  const randomN = [];
  while(randomN.length < n){
    const randomIndex = Math.floor(Math.random()*arr.length);
    randomN.push(arr[randomIndex]);
    arr.splice(randomIndex, 1);
  }
  return randomN;}

var result1 = (rollTheDice(images,1)); 
var result2 = (rollTheDice(images,1));
var result3 = (rollTheDice(images,1));
var result4 = (rollTheDice(images,1)); 
var result5 = (rollTheDice(images,1));

I've been repeatedly reloading the page and haven't seen any repeats yet. Perfect!

Dave Birss
  • 23
  • 3
  • 1. make a copy of diceRoll array. – pRmdk May 30 '19 at 08:43
  • Possible duplicate of [Generating non-repeating random numbers in JS](https://stackoverflow.com/questions/18806210/generating-non-repeating-random-numbers-in-js) – sstruct May 30 '19 at 09:05

5 Answers5

1

You could take an array of indices and check if the index exist, then get a new index or push this index.

var length = 51,  // your count of items
    indices = [], // the result set with indices
    count = 5,    // the amount of wanted indices
    random;       // guess what?
    
while (indices.length < count) {                 // check length
    random = Math.floor(Math.random() * length); // get random value
    if (indices.includes(random)) continue;      // continue if already selected
    indices.push(random);                        // if not take it
}

console.log(indices);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • If your `count` is small enough compared to `length` there's a high probability of repeating `Math.random()` outcomes and consequent `continue` which is a waste of performance. – Yevhen Horbunkov May 30 '19 at 10:15
0
  1. Make a copy of diceRoll array (diceRollCopy).
  2. Use the new array(diceRollCopy) as argument of choose method.
  3. Whenever you get a result using choose method remove that result from the Copy array (diceRollCopy).
  4. You would need to reset the diceRollCopy to diceRoll after each set of results have been accessed.
pRmdk
  • 151
  • 1
  • 10
0

Copy it, then shuffle the copy, and remove the first element from the array each time:

const copy = [...diceRoll].sort(e => 0.5 - Math.random());

And in your choosing function:

const chosen = copy.shift();
Jack Bashford
  • 43,180
  • 11
  • 50
  • 79
0

I guess, the trickiest part here is not to waste the performance, limiting possible options to those, not previously chosen:

const images = ['a','b','c','d','e','f'];

const rollTheDice = (arr, n) => {
  const randomN = [];
  while(randomN.length < n){
    const randomIndex = Math.floor(Math.random()*arr.length);
    randomN.push(arr[randomIndex]);
    arr.splice(randomIndex, 1);
  }
  return randomN;
}

console.log(rollTheDice(images, 5));
Yevhen Horbunkov
  • 14,965
  • 3
  • 20
  • 42
  • Thank you so much! Your approach did it for me! I'm adding an update to my question to show how I slotted it into my code. – Dave Birss May 30 '19 at 16:20
0

You want a random permutation which all elements is uniq and from one dataset, here is my implementation:

var array = [1, 2, 3, 4, 5, 6];

/**
 * uniqGet
 * @param {*} array source array
 * @param {*} num how many elements to get
 */
function uniqGet(array, num) {
  if (array.length < num) {
    throw new Error("num should less than options");
  }
  let res = [];
  while (num > 0) {
    let index = Math.floor(Math.random() * array.length);
    res.push(array[index]);
    array.splice(index, 1);
    num--;
  }
  return res;
}

let result = uniqGet(array, 3); // [x, y, z]
sstruct
  • 481
  • 1
  • 5
  • 7