Anatol Zakrividoroga
Anatol Zakrividoroga

Reputation: 4538

How to Randomly Choose an Element from an Array that Wasn't Chosen before in JavaScript?

I want to run a function that each time randomly chooses an element from an array that wasn't chosen before. And if all elements were chosen, I want to reset the used elements and start from the beginning.

Hope this makes sense.

I already have a function that chooses a random element from an array. But I also don't want it to choose elements that were chosen before unless all elements were already chosen.

Here is what I have got so far (credit to @Kelly):

var item = items[Math.floor(Math.random() * items.length)]

Upvotes: 2

Views: 2771

Answers (4)

Aaron Plocharczyk
Aaron Plocharczyk

Reputation: 2840

Here's a solution that's pretty short. It uses randojs.com to simplify the randomness and make it more readable, though I kinda threw a wrench in the "easy to read" part by making it super short. If you're having trouble understanding, here are some resources that will help explain: declaring variables on one line, the ternary operator, different ways to declare a function, the pop() method, and randojs

You can actually make the JavaScript one line if you want by just removing the line break, but I chose to make it two for the sake of clarity and shorter lines.

var arr = ["one", "two", "three", "four"], shuffled = randoSequence(arr), 
uniqueRandom = () => (shuffled.length ? shuffled : shuffled = randoSequence(arr)).pop().value;
<script src="https://randojs.com/1.0.0.js"></script>
<button onclick="console.log(uniqueRandom());">Log a unique random</button>

NOTE: this code won't work if you forget to import randojs with the script tag, so make sure to paste that in the head of your HTML document if you want to use this code.

Upvotes: 0

Teemu Ikonen
Teemu Ikonen

Reputation: 11929

One possible way is prime walk.

First have the list of e.g. 500 items. Get the next prime number that is greater than 500. Here 503. Select random seed. This seed is any number that is constant for an user.

var prime = 503;
var list = ["item1", "item2", ... "item500"];

function pick_nth(seed, n, p, l) {
    if(!n) return l[seed % p];
    return pick_nth((seed + l.length) % p, n - 1, l);
}

Picking up n:th item from list is easy. For example:

pick_nth(seed, 0, prime, list);  // first item
pick_nth(seed, 1, prime, list);  // second item
...
pick_nth(seed, 499, prime, list);  // 500th item

The order of the items returned are permutated by the seed.

Upvotes: 2

VLAZ
VLAZ

Reputation: 29116

The easiest way to handle this is:

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;

  while (0 !== currentIndex) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}


var items = ["alpha", "beta", "gamma", "delta", "epsilon"];
var index = Infinity;

function start() {
  console.log("----- shuffling -----")
  shuffle(items);
  index = 0;
}

function nextItem() {
  if (index >= items.length) {
    //re-start
    start()
  }
  
  //return current index and increment
  return items[index++];
}

document.getElementById("click_me")
  .addEventListener("click", function() {
    console.log(nextItem())
  })
<button id="click_me">Next random</button>

This can also be converted to a generator function

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;

  while (0 !== currentIndex) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

function* random(array) {
  let index = Infinity;
  const items = array.slice(); //take a copy of the array;
  
  while(true) {
    if (index >= array.length) {
      console.log("----- shuffling -----")
      shuffle(items);
      index = 0;
    }
    
    yield items[index++];
  }
}


var items = ["alpha", "beta", "gamma", "delta", "epsilon"];

//start the generator
const generateRandom = random(items);

document.getElementById("click_me")
  .addEventListener("click", function() {
    console.log(generateRandom.next().value)
  })
<button id="click_me">Next random</button>

Upvotes: 2

Rajesh
Rajesh

Reputation: 24955

You can try something like this:

Idea

  • Create a utility function that takes an array and returns you a random value.
  • Inside this Array, maintain 2 array, choices and data.
  • In every iteration, remove 1 item from data and put it in chosenItems
  • Once the length of data reaches 0, set chosenItems or originalArray as data and repeat process.

Benefit of this approach would be,

  • You dont need to maintain and pass array variable.
  • It can be made generic and used multiple times.

function randomize(arr) {
  let data = [...arr];
  let chosenItems = [];

  function getRandomValue() {
    if (data.length === 0) {
      data = chosenItems;
      chosenItems = [];
    }
    const index = Math.floor(Math.random() * data.length);
    const choice = data.splice(index, 1)[0];

    chosenItems.push(choice);
    return choice;
  }
  
  return {
    randomItem: getRandomValue
  }
}

const dummyData = [ 1,2,3,4,5 ];

const randomizeData = randomize(dummyData);

for (let i = 0; i< 10; i++) {
  console.log(randomizeData.randomItem())
}

Upvotes: 3

Related Questions