- Home /
Selecting random indexes without repetition
I'm writing a flashcard system, every time I generate a question the game needs to look at the flashcards I give it and a) randomly generate 4 answers, b) ensure that ONE of those answers is the correct one, c) make sure that ONLY one card is the correct one.
I started by shuffling the list and selecting the first n entries:
void GenerateNewCards(){
targetWordHolder.card = GenerateCard (); //Select a random flashcard from the deck- this is the card the player will be tested on
SetButtonText (targetWordHolder, questionFormat); //Depending on which elements of the flashcard the player wants to be tested on, display a prompt
cardLibrary = ShuffleList (cardLibrary); //Shuffle the List<Card> of flashcards
for (int i = 0; i < buttonList.Count; i++) { //For every button the player has to pick from...
buttonDict[buttonList[i]].card = cardLibrary[i]; //Assign a random card from the library
SetButtonText(buttonDict[buttonList[i]],answerFormat); //Set that button's text equal to the prompt the player needs to know/guess
}
From here, the problem is preventing repetition: I could select one of my buttons at random, and set that equal to the answer card...
buttonDict [buttonList[Random.Range (0, buttonList.Count)]].card = targetWordHolder.card; //Guarantees that the correct card will be an option
...but the correct answer is in the deck I generated choices from, so there's a possibility that the same answer appears twice, which isn't ideal.
Is there any reasonably straightforward way I can keep this from happening? The ideal scenario is four (or however many) different random choices each time, only one of which is the correct choice.
Answer by Michael-Glen-Montague · May 19, 2015 at 10:10 AM
This function returns an array of unique items taken out of an existing array. Its first parameter is the array of items to choose from, and the second parameter is the size of the array you want to return.
/// <summary> Returns an array of random unique elements from the specified array. </summary>
public static T[] GetRandomArray<T>(T[] array, int size)
{
List<T> list = new List<T>();
T element;
int tries = 0;
int maxTries = array.Length;
while (tries < maxTries && list.Count < size)
{
element = array[UnityEngine.Random.Range(0, array.Length)];
if (!list.Contains(element))
{
list.Add(element);
}
else
{
tries++;
}
}
if (list.Count > 0)
{
return list.ToArray();
}
else
{
return null;
}
}
Thank you... is there a general guideline as to when it's faster to use if (!element) compared to shuffling the list? I was under the impression that the latter gained in efficiency as the size of the list grew past a certain point.
Please note however that that particular implementation generates quite a bit of memory that will leak to the GC, and should not be called every frame.
Answer by FortisVenaliter · May 19, 2015 at 02:40 AM
You could have a list of used indices. With each new card, clear the list. Each time you set an answer, add the index of the answer from the full deck. Then do something like:
int newIndex = RandomFunc();
while(usedIndices.Contains(newIndex))
newIndex = RandomFunc();
Hmm, what do you think- I adapted this a bit, while keeping my need to retain total control over the target card:
void GenerateNewCards(){
targetWordHolder.card = GenerateCard (); //Select a random flashcard from the deck- this is the card the player will be tested on
SetButtonText (targetWordHolder, questionFormat); //Depending on which elements of the flashcard the player wants to be tested on, display a prompt
List<Card> tempList = new List<Card> ();
tempList.Add (targetWordHolder.card);
// cardLibrary = ShuffleList (cardLibrary); //Shuffle the List<Card> of flashcards
while (tempList.Count < buttonList.Count){
int randomNumber = Random.Range (0, cardLibrary.Count);
while (tempList.Contains(cardLibrary[randomNumber]))
randomNumber = Random.Range (0, cardLibrary.Count);
tempList.Add (cardLibrary[randomNumber]);
}
tempList = ShuffleList (tempList);
for (int i = 0; i < buttonList.Count; i++) { //For every button the player has to pick from...
buttonDict[buttonList[i]].card = tempList[i];
SetButtonText(buttonDict[buttonList[i]],answerFormat);
}
}
Answer by shopguy · May 19, 2015 at 02:51 AM
I think you might be making this more complex than it needs to be. If you are shuffling the cards, do you really need to pick random cards also? It should be just as random if you simplify and just take however many cards you need from the top of the pile.
I could definitely be overcomplicating it, but I do need to retain some pretty fine-grained control over which cards you're tested on: the default logic is to iterate through the entire deck until the player has gotten each card right, and it uses some spaced repetition stuff on the cards you're getting wrong, so I need to be able to guarantee that certain cards will come up a certain number of times. To that end, setting the target card and then filling in the rest with randoms seemed like the best bet.
I suppose I could just check the cards that had been dealt to ensure that they didn't match the one was I going to set later...
Your answer
Follow this Question
Related Questions
Pick between two floats 2 Answers
Printing a GUI selection grid in order 1 Answer
How can I display 4 items in random order but never double? 2 Answers
Printing an ordered list 1 Answer
Add random amount of random items from one list to another? 2 Answers