Random Option from Array without Repetition

I'm a elementary teacher, just starting with Arduino. As my first project, I have build a button that shows the name from one of my students at random on an LCD display. I have based this on this Instructable by haydians09:

The "main" code is as follows:

void loop() {
  lcd.setCursor(0,0);
  lcd.print("Please press");
  lcd.setCursor(0,1);
  lcd.print("the button!");
  digitalWrite(13, HIGH);
  val1 = digitalRead(BUTTON1);
  unsigned long seed=seedOut(50);
  randomSeed(seed);
  if (val1 == LOW) {
    for (int i=0;i<1;++i) {
      player = (random(sizeof(players)/sizeof(char*)));
      lcd.clear();
      digitalWrite(13, LOW);
      lcd.setCursor(0,0);
      lcd.print("I choose:");
      lcd.setCursor(0, 1);
      lcd.print(players[player]);
      delay(5000);
      lcd.clear();
    }
  }
  else
  {
    return;
  }
}

This works fine! The problem with this is that some students have to wait very long as each press is random. Is there a way to pick students that haven't been selected yet? So each time a student is picked, he or she will not be picked again untill the program is reset.

Thanks in advance, this forum is a great help as a learning tool!

Please can you post the whole of your program.

What is the purpose of the for loop in loop() ?

instead of using the random routine you could implement psudo-random code to fix the number times before it restarts.

Linear-feedback shift register - Wikipedia and check out the section "Some polynomials for maximal LFSRs"

also lets say you have 14 names in your array; you gonna have to implement a "4 bit LFSR" (period 15) which means that ur gonna have 2 invalid cases(14 and 15) since you array goes from 0-13. if that's the case u can set a if-else statement to call back the pseudorandom routine is value if >13

hope that helps

This is the entire code:

#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

const char *players[]={
"Student 1", 
"Student 2", 
"Student 3", 
"Student 4", 
"Student 5", 
"Student 6",
"Student 7", 
"Student 8", 
"Student 9", 
"Student 10", 
"Student 11", 
"Student 12", 
"Student 13", 
"Student 14", 
"Student 15", 
"Student 16", 
"Student 17",
"Student 18", 
"Student 19", 
"Student 20", 
};

long player;

int val1 = 0;
int BUTTON1 = 1;

void setup() {
  lcd.begin(16, 2);
  pinMode(13, OUTPUT);
  pinMode(BUTTON1, INPUT_PULLUP);
}

void loop() {
  lcd.setCursor(0,0);
  lcd.print("Please press");
  lcd.setCursor(0,1);
  lcd.print("the button!");
  digitalWrite(13, HIGH);
  val1 = digitalRead(BUTTON1);
  unsigned long seed=seedOut(50);
  randomSeed(seed);
  if (val1 == LOW) {
    for (int i=0;i<1;++i) {
      player = (random(sizeof(players)/sizeof(char*)));
      lcd.clear();
      digitalWrite(13, LOW);
      lcd.setCursor(0,0);
      lcd.print("I choose:");
      lcd.setCursor(0, 1);
      lcd.print(players[player]);
      delay(5000);
      lcd.clear();
    }
  }
  else
  {
    return;
  }
}

unsigned int bitOut(void) {
  static unsigned long firstTime=1, prev=0;
  unsigned long bit1=0, bit0=0, x=0, port=0, limit=10;
  if (firstTime) {
    firstTime=0;
    prev=analogRead(port);
  }
  while (limit--) {
    x=analogRead(port);
    bit1=(prev!=x?1:0);
    prev=x;
    x=analogRead(port);
    bit0=(prev!=x?1:0);
    prev=x;
    if (bit1!=bit0)
      break;
  }
  return bit1;
}

unsigned long seedOut(unsigned int noOfBits) {
  unsigned long seed=0;
  for (int i=0;i<noOfBits;++i)
    seed = (seed<<1) | bitOut();
  return seed;
}

This code by Paul Badger seems very promising to me, but I have little idea how to edit this for my case:

/* RandomHat
  Paul Badger 2007 - updated for Teensy compile 2017
  choose one from a hat of n consecutive choices each time through loop
  Choose each number exactly once before reseting and choosing again
*/

#define randomHatStartNum 10  // starting number in hat
#define randomHatEndNum 25    // ending number in hat - end has to be larger than start  
#define numberInHat (randomHatEndNum - randomHatStartNum) + 1

void setup()
{
  Serial.begin(9600);
  Serial.println("start ");
}

void loop()
{ // randomHat test
  for (int i = 1; i <= numberInHat; i++) {
    int x = randomHat();
    Serial.print(x);
    Serial.print(", ");
    delay(50);
  }
  Serial.println(" ");
}


int randomHat() {
  int thePick;		//this is the return variable with the random number from the pool
  int theIndex;
  static int currentNumInHat = 0;
  static int randArray[numberInHat];

  if  (currentNumInHat == 0) {                  // hat is emply - all have been choosen - fill up array again
    for (int i = 0 ; i < numberInHat; i++) {    // Put 1 TO numberInHat in array - starting at address 0.
      if (randomHatStartNum < randomHatEndNum) {
        randArray[i] = randomHatStartNum + i;
      }
    }
    currentNumInHat = abs(randomHatEndNum - randomHatStartNum) + 1;   // reset current Number in Hat
    // Serial.print(" hat is empty ");
    // if something should happen when the hat is empty do it here
  }

  theIndex = random(currentNumInHat);	                 //choose a random index from number in hat
  thePick = randArray[theIndex];
  randArray[theIndex] =  randArray[currentNumInHat - 1]; // copy the last element in the array into the the empty slot
  //                                                     // as the the draw is random this works fine, and is faster
  //                                                     // the previous version. - from a reader suggestion on this page
  currentNumInHat--;    // decrement number in hat
  return thePick;
}

I added my Array to Paul Badgers code, using his random numbers without repeat to index my array:

/* RandomHat
  Paul Badger 2007 - updated for Teensy compile 2017
  choose one from a hat of n consecutive choices each time through loop
  Choose each number exactly once before reseting and choosing again
*/

#define randomHatStartNum 0  // starting number in hat
#define randomHatEndNum 19    // ending number in hat - end has to be larger than start  
#define numberInHat (randomHatEndNum - randomHatStartNum) + 1

const char *players[]={
"Student 1", 
"Student 2", 
"Student 3", 
"Student 4", 
"Student 5", 
"Student 6",
"Student 7", 
"Student 8", 
"Student 9", 
"Student 10", 
"Student 11", 
"Student 12", 
"Student 13", 
"Student 14", 
"Student 15", 
"Student 16", 
"Student 17",
"Student 18", 
"Student 19", 
"Student 20", 
};

void setup()
{
  Serial.begin(9600);
  Serial.println("start ");
}

void loop()
{ // randomHat test
  for (int i = 1; i <= numberInHat; i++) {
    int x = randomHat();
    Serial.print(players[x]);
    Serial.print(", ");
    delay(50);
  }
  Serial.println(" ");
}

int randomHat() {
  int thePick;    //this is the return variable with the random number from the pool
  int theIndex;
  static int currentNumInHat = 0;
  static int randArray[numberInHat];

  if  (currentNumInHat == 0) {                  // hat is emply - all have been choosen - fill up array again
    for (int i = 0 ; i < numberInHat; i++) {    // Put 1 TO numberInHat in array - starting at address 0.
      if (randomHatStartNum < randomHatEndNum) {
        randArray[i] = randomHatStartNum + i;
      }
    }
    currentNumInHat = abs(randomHatEndNum - randomHatStartNum) + 1;   // reset current Number in Hat
    Serial.print(" hat is empty ");
    delay(5000);
    // if something should happen when the hat is empty do it here
  }

  theIndex = random(currentNumInHat);                  //choose a random index from number in hat
  thePick = randArray[theIndex];
  randArray[theIndex] =  randArray[currentNumInHat - 1]; // copy the last element in the array into the the empty slot
  //                                                     // as the the draw is random this works fine, and is faster
  //                                                     // the previous version. - from a reader suggestion on this page
  currentNumInHat--;    // decrement number in hat
  return thePick;
}

The output is as follows:

start
  hat is empty Student 11, Student 24, Student 17, .......
  hat is empty Student 4, Student 19, Student 23, .......

This gives me the result I need with two problems:
The first being the code first returns "hat is empty" before the numbers. Is there a way to reverse this?

And secondly, I would like define the randomHatEndNum automatic by the lenght of my custom array, but I have trouble implementing that code. What is the right way to approach this?

your unnatural use of starting the array indices at 1 makes it hard to see your issue...

here is a refactored, functional approach:

#define NUMBER_OF_STUDENTS 20

const char *players[NUMBER_OF_STUDENTS]={
"Student 0",
"Student 1", 
"Student 2", 
"Student 3", 
"Student 4", 
"Student 5", 
"Student 6",
"Student 7", 
"Student 8", 
"Student 9", 
"Student 10", 
"Student 11", 
"Student 12", 
"Student 13", 
"Student 14", 
"Student 15", 
"Student 16", 
"Student 17",
"Student 18", 
"Student 19",  
};

int choices[NUMBER_OF_STUDENTS];
int choicesMade;

void setup() 
{
  Serial.begin(9600);
  randomSeed(analogRead(A0));
  resetNames();
}

void loop() 
{
  int studentId = selectRandomNameFromRemaining();
  if(studentId == -1)
  {
    Serial.println(F("hat is empty"));
    resetNames();
    delay(5000);
  }
  else
  {
    Serial.println(players[studentId]);
  }

  delay(1000);
}

int selectRandomNameFromRemaining()
{
  if(choicesMade >= NUMBER_OF_STUDENTS)
  {
    return -1;
  }
  int selection = random(choicesMade, NUMBER_OF_STUDENTS);
  int temp = choices[choicesMade];
  choices[choicesMade] = choices[selection];
  choices[selection] = temp;
  return choices[choicesMade++]; //moved incrementing choicesMade to here
}

bool resetNames()
{
  for (int i = 0; i < NUMBER_OF_STUDENTS; i++)
  {
    choices[i] = i;
  }
  for (int i = 0; i < NUMBER_OF_STUDENTS; i++)
  {
    int index = random(i, NUMBER_OF_STUDENTS);
    int temp = choices[i];
    choices[i] = choices[index];
    choices[index] = temp;
  }
  choicesMade = 0;
}

This is very readable and understandable, exacly what I was looking for!

Thank you very much BullDogLowell!

PieterJanSterk:
This as very readable and understandable, exacly what I was looking for!

and yet, it had a bug such that only 19 players were chosen :confused:
I fixed above!

sorry about that...