Shuffle numbers when button is pressed

Greetings!
Recently I promised myself that I would make a project that involves arrays and random (two things that give me headache) so I can learn how to use them. A week ago I finished all but one part of the code... The one that involves arrays and random. You see basically I have an array of numbers 1 - 5 and I want the code to print a random number from that array each time the button is pressed. Also the numbers printed shouldn't repeat. I thought that would be easy but then pointers introduced themselves and now I'm depressed. So I stopped trying to do it myself and I looked online for similar codes. And that is how I created this Frankenstein of a code:

#include <Bounce2.h>
Bounce b = Bounce(); 

int button = 8;
int mySwitchPinArray[] = {1, 2, 3, 4, 5};
int n = sizeof(mySwitchPinArray)/ sizeof(mySwitchPinArray[0]);
  
void setup() {
  
  Serial.begin(9600);

  b.attach(button,INPUT);
  b.interval(25);
}


void loop() {
  int i;

  b.update();
  randomisation(mySwitchPinArray, n);
  if(b.rose() && i < n){         // wrong stuff here
    i++;
    Serial.print(mySwitchPinArray[i]);
    Serial.print("   ");
  }
  

}



void swap (int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}



void randomisation( int mySwitchPinArray[], int n )
{

    randomSeed(analogRead(A0));

    for (int i = n-1; i > 0; i--)
    {
        long randNumb = random(0, n);
        swap(&mySwitchPinArray[i], &mySwitchPinArray[randNumb]);
    }
}

It doesn't work of course. The original code had a for loop that printed all the shuffled numbers and it worked perfectly, but when I did my thing the printed numbers repeat. Probably my nonexistent understanding of pointers contributed to the code not working correctly, so I beg you to help me fix it.

Thanks in advance,
Me

you need to clarify this statement

Also the numbers printed shouldn't repeat

do you mean 2 in a row or that you should have printed the whole array before going back to one of the previously printed element?

It looks like you expect 'i' to start at some value and, after you increment it, to have the incremented value the next time loop() is called. To do that, use "static int i = 0;". The 'static' lets it retain value between calls (like a global variable). The "= 0" isn't strictly necessary but it makes your intention clearer,

void loop() {
  int i;

  b.update();
  randomisation(mySwitchPinArray, n);
  if(b.rose() && i < n){         // wrong stuff here
    i++;
    Serial.print(mySwitchPinArray[i]);
    Serial.print("   ");
  }
}

Here's an algorithm that will generate 10 random numbers of values from 0 to 9, without repeating one of them

	uint8_t tmp = 0;
	uint8_t cnt = 0;
	uint8_t i = 0;
	uint8_t data[] = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100};

	do
	{
		// generate a random number from 0 to 9
		tmp = rand()%10;
		
		// check array for repeated number, break if match found
		for(cnt=0; cnt<10; cnt++)
		{
			if(tmp == data[cnt]) {
				// match found
				break;
			}
		}
                // match wasn't found, add tmp to array
		if(cnt == 10) {
			data[i++] = tmp;
                        // print tmp value here
		}
	} while(i<10);

Sample output

aaa.png

aaa.png

hzrnbgy:
Here's an algorithm that will generate 10 random numbers of values from 0 to 9, without repeating one of them

this is pretty inefficient as it gets increasingly complicated to find something you did not use yet and the further you go in the array the more difficult it is.

If you try your algorithm with a much larger array (say 100 entries) you'll see your code will become super slow or might even not terminate in a reasonable time.

The easiest way to do this is to create an array with {0,1,2,3,4,5,6,7,8,9} and to exchange two entries selected randomly multiple times.

if you perform 5 to 10 exchanges (half the size to the size of the array) you'll get already some good entropy.

Here is an example

uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

void swapEntries(byte a, byte b)
{
  byte temp = data[a];
  data[a] = data[b];
  data[b] = temp;
}

void printData()
{
  for (const auto& v : data) {
    Serial.print(v);
    Serial.write(' ');
  }
  Serial.println();
}

void setup() {
  Serial.begin(115200);
}

void loop() {
  for (int i = 0; i < 10; i++) swapEntries(random(0, 10), random(0, 10)); // perform 10 swaps
  printData();
  delay(1000);
}

what's interesting in this is that you know how long it will take to generate the new array.

My solution is the one I came up with after reading the post.

Your solution is probably the product of a Computer Science doctoral dissertation. I'm pretty sure there are other more efficient/faster code than what I came up with, but that is a secondary concern at the moment

Make it work, make it right, then make it fast

Your solution is probably the product of a Computer Science doctoral dissertation

not really, just a simple approach to avoiding duplicates. it also has the advantage of shuffling whatever array you have, the content does not matter.

I'm pretty sure there are other more efficient/faster code than what I came up with

out of curiosity try your algorithm with your data array holding 100 values instead of 10.

One way to pick numbers from an array in random order without repeats:

int array[] = {1, 2, 3, 4, 5};
const int arraySize = sizeof array / sizeof array[0];

int remainingNumbers = 0;

int getRandomEntry()
{
  if (remainingNumbers == 0)
    remainingNumbers = arraySize; // Start over with a full array

  // Pick a number from those remaining
  int index = random(remainingNumbers); // 0 to remainingNumbers - 1
  int returnValue = array[index];

  // Swap the chosen number with the number at the end of the current array
  // (unless the chosen entry is already at the end)
  if (index < remainingNumbers - 1)
  {
    // Move the last entry into the chosen index
    array[index] = array[remainingNumbers - 1];
    // Move the value from the chosen index into the last entry
    array[remainingNumbers - 1] = returnValue;
  }
  
  // Shorten the list so the picked number is not picked again
  remainingNumbers--;

  return returnValue;
}

// Sketch for testing
void setup()
{
  Serial.begin(115200);
}

void loop()
{
  for (int i = 0; i < arraySize; i++)
  {
    Serial.print(getRandomEntry());
    Serial.print(' ');
  }
  Serial.println();
  delay(1000);
}

Output:

4 3 5 2 1 
5 1 2 3 4 
4 5 3 1 2 
1 4 5 2 3 
3 1 5 4 2 
5 3 2 1 4 
3 1 4 2 5 
1 2 3 4 5 
1 2 5 4 3 
3 5 1 4 2 
2 4 1 5 3 
4 2 1 3 5 
3 5 2 1 4 
1 3 2 4 5

that works too and is O(n) if you have n data entries - so timing is also under control.

It looks like you expect 'i' to start at some value and, after you increment it, to have the incremented value the next time loop() is called. To do that, use "static int i = 0;". The 'static' lets it retain value between calls (like a global variable). The "= 0" isn't strictly necessary but it makes your intention clearer

Wow thanks! I had no idea static exists. It does work a lot better but it (mostly) failes at the last int.
For example it prints:

3 1 2 4 1

1 5 3 4 1

2 4 5 3 1

1 2 3 5 1

The last number is always 1, no matter if it is already printed. Do you know why that is?

Also, thanks hzrnbgy for your code! I might use it in the future couz I think it's easier to understand. No pointers.

do you mean 2 in a row or that you should have printed the whole array before going back to one of the previously printed element?

Oh and, the second one.

Thank you all for the help so far. Didn't expect so many replies so soon :slight_smile:

Best regards,
Me

lj_sk:
Wow thanks! I had no idea static exists. It does work a lot better but it (mostly) failes at the last int.

post your new code... (if you keep increasing i then you'll be out of bounds as static will keep it's value over each loops. In the first code the variable was reset at every loop and since it was not initialized would take whatever value is on the stack at that time/location. static and globals are initialized with 0)

Ah yes, the new code. Here it is. And uh, sorry for the late reply heh... school and stuff...

#include <Bounce2.h>
Bounce b = Bounce(); 

int button = 8;
int mySwitchPinArray[] = {1, 2, 3, 4, 5};
int n = sizeof(mySwitchPinArray)/ sizeof(mySwitchPinArray[0]);
  
void setup() {
  
  Serial.begin(9600);

  b.attach(button,INPUT);
  b.interval(25);
}


void loop() {
  static int i = 0;

  b.update();
  randomisation(mySwitchPinArray, n); 
  if(b.rose() && i < n){                   // wrong stuff here
    i++;
    Serial.print(mySwitchPinArray[i]);
    Serial.print("   ");
  }
  

}



void swap (int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}



void randomisation( int mySwitchPinArray[], int n )
{

    randomSeed(analogRead(A0));

    for (static int i = n-1; i > 0; i--)
    {
        long randNumb = random(0, n);
        swap(&mySwitchPinArray[i], &mySwitchPinArray[randNumb]);
    }
}

I changed both -i- to static because it gives me the best results. If I don't put the -i- below to static as well the last number that it would print would be something like -917127. So the printed numbers would look something like 5 3 1 2 -98136. Its actually not that big of a deal anymore since I got so many good code ideas but I am just curious about why it is doing that.

Best regards,
Me

lj_sk:
Its actually not that big of a deal anymore since I got so many good code ideas but I am just curious about why it is doing that.

there are many things wrong in your code but to the specific question you raise, the issue is here

if(b.rose() && i < n){                   
    i++;         // <<====== incrementing i too early !!!
    Serial.print(mySwitchPinArray[i]);
    Serial.print("   ");
  }

you test the variable 'i' for bounds (less than array size) which is OK but in the brackets you increment it before printing. thus when you enter the test at the end of the array you print whatever is beyond the boundaries of the array. hence the garbage you see for the last value.

=> If you move the i++ after the prints then you'll be fine

Other comments:

  • there is no need for a static local variable in a for loop. if you come back the for statement will initialize it again.

  • you shuffle your array all the time as the loop spins. that's not useful. You probably would want to shuffle it only when your button is pressed for example.

  • note that your code only prints 1 value when the button is pressed. As you shuffle values all the time you are not guaranteed to see the 5 digits only once in the console.

  • it's a bad idea to use re-use global variable names for functions parameters as it brings confusion. Don't do void randomisation( int mySwitchPinArray[], int n ) but dovoid randomisation( int anArray[], int numberOfElements)

  • You are using C++, if pointers are confusing to you, you can pass stuff by reference.

Ah! Thanks a lot it works now :slight_smile: . Yeah, I guess I'll take a look at references pointers are weird man.

Anyway thank you all for the help!

I'll just leave this here: Fisher–Yates shuffle. It's fast and extremely simple, so there's no need to reinvent the wheel poorly.

christop:
I'll just leave this here: Fisher–Yates shuffle. It's fast and extremely simple, so there's no need to reinvent the wheel poorly.

It requires n-1 permutations. If you are time constrained, although not as statistically unbiased, picking n/2 random swaps leads to an often good enough permutation of the array and is twice as fast.

J-M-L:
It requires n-1 permutations. If you are time constrained, although not as statistically unbiased, picking n/2 random swaps leads to an often good enough permutation of the array and is twice as fast.

It's more likely that the random function itself takes the lion's share of the computation time (with its multiplies and additions and divisions), so calling it the same number of times would make that shuffle algorithm take roughly the same amount of time as a proper Fisher-Yates shuffle. If I had to choose between two algorithms in which the only significant difference between the two is the quality of the shuffle, I'd take the algorithm that produces a better shuffle.

That’s a very fair point
I’ve not looked at the complexity of the random function