Choosing a Random Number Out of Many

Hi, I’m adding lightning to my sketch and having trouble accomplishing something.

I have 8 LED channels, in the GUI there will be an option to have lighting strike one, all or a few of the channels.

The value of the option will be 0=yes or 1=no and stored in the eeprom. So if ch1, ch2 and ch7 are the only ones with 0 store then they will be the channels that could possibly get a lightning strike.

What I’m having trouble with is I would like to have only 1 of those 3 channels above to actually get the lightning. Of course those could be different channels.

I have the following which works but as you can see it chooses between channel 0-7 but what if channel 4 was disabled and not suppose to get lightning.

      burst = random(10,200); //Short burst of lightning value
      big = random(500,2000);//long burst of lightning value
      lL = random(lLV1,lLV2);//lower light lighting value
      hL = random(hLV1,hLV2);//higher light lightning value
      lightningPWMr = random(0,7); // random channel for lightning to flash
      mixer = random(burst,big);//random pick of short or long bursts of lightning
      for(int i = 0; i < mixer; i += diming++)
      {
        diming = random(1,150);
        pwm.setPWM(lightningPWMr, 0, random(lL,hL));
        Alarm.delay(diming);
      }

So ultimately what I want is to figure out what channels are option 0(yes) and then choose one of those channels randomly.

share us full code.

Please share us setup diagram make some algorithm layout & share.

So ultimately what I want is to figure out what channels are option 0(yes) and then choose one of those channels randomly.

You could do it the other way round and choose a channel number randomly until you find one that is not disabled or you could copy the active channel numbers into a smaller array and choose from that array with a random number.

A simple non blocking strategy (not completely random) is

  1. get a random one
  2. if disabled calculate a next element from the array (wrap if needed)

The formula to the next element is to take a step size that is prime with the size of the array.
e.g.
Array size: 30 (== 235) take step size 11. (other prime will do to)

random → 9 (disabled)
next = 20, 31 wraps to 1, 12, 23, 34 wraps to 4 etc.

Another approach would be to first build an array of all the usable channels. Then pick one of THOSE at random. So many ways to skin this cat.

Thanks for the replies, I've been trying with an array but can't get it to work. How would I add enabled channels to the array and then randomly choose one? Are there any examples I can follow? I'm trying with the array example in the learning section on arduino.cc.

Untested, but here is the principle

byte sourceArray[] = {1, 1, 0 , 1 , 0, 1, 0, 0};
const byte sourceArraySize = sizeof(sourceArray) / sizeof(sourceArray[0]);
byte targetArray [sourceArraySize];
byte foundCount = 0;

void setup()
{
  Serial.begin(115200);
  for (int i = 0; i < sourceArraySize; i++) //look through the source array
  {
    if (sourceArray[i] == 0) //if we find an active port
    {
      targetArray[foundCount] = i;  //save its position
      foundCount++;  //ready for next save
    }
  }
  
  Serial.print("Port ");
  Serial.print(targetArray[random(foundCount)]);  //choose an active port number in the range found
  Serial.println(" is active");
}

void loop()
{
}

However, with a small array to choose from you may just as well keep looking in it with a random index until you find an active one.

Thank-you, it works great! I moved the print(targetArray) into the loop() and it does exactly what it should. I'll play with it and see what I can do.

I am glad that it works and shows the principle. You may be able to optimise it for your situation. As written it allows for any (reasonable) size of array and for any/all of the source array entries to be active. If only a fixed number of source array entries can be active you could exit the for loop when you have found them all. You would also be wise to allow for the fact that no entries may be found. I assume that this will be impossible in your program, but you never know.....

So I was able to implement that random array however I’m having an issue I’m not able to figure out.

I can only set the variables in the array when the mega turns on, if I update them after using the eeprom the array doesn’t see the changes. This is how I have it in my sketch.

This part is where all the main program variable are, not in any function, just like your example. I added the eeprom.read here as this is the only place that will update the array, this verifies the variables are being read in the array correctly, except the arduino needs to be reset to access this code again. When I call the eeprom.read in a function it doesn’t update?

byte ch1=EEPROM.read(160);
byte ch2=EEPROM.read(161);
byte ch3=EEPROM.read(162);
byte ch4=EEPROM.read(163);
byte ch5=EEPROM.read(164);
byte ch6=EEPROM.read(165);
byte ch7=EEPROM.read(166);
byte ch8=EEPROM.read(167);

byte sourceArray[] = {ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8};
const byte sourceArraySize = sizeof(sourceArray) / sizeof(sourceArray[0]);
byte targetArray [sourceArraySize];
byte foundCount;

Next I have a function call lightningVariables, this function is called first when the lightning storm is started. This is where the array should look at the eeprom and see what channels are 0 and 1. When I add the code above to this function I get compile errors because the variables are only defined in this function but used in another as well. So I define them like above but then the array doesn’t work as expected.

void lightningVariables()
{  
  ch1=EEPROM.read(160);
  ch2=EEPROM.read(161);
  ch3=EEPROM.read(162);
  ch4=EEPROM.read(163);
  ch5=EEPROM.read(164);
  ch6=EEPROM.read(165);
  ch7=EEPROM.read(166);
  ch8=EEPROM.read(167);
  
  for (int i = 0; i < sourceArraySize; i++) //look through the source array
  {
    if (sourceArray[i] == 0) //if we find an active port
    {
      targetArray[foundCount] = i;  //save its position
      foundCount++;  //ready for next save
    }
  }  
}

And finally I have the loop for the lightning, this is where I call the random number from the array. This loop gets started from the function above, I omitted all the other code in there. When it gets to the serial.print target array it always sees the channels that were loaded on boot up. For some reason the function above isn’t updating it. I’ve tried all kinds of combinations to update the array but can’t get it.

void lightningStorm()
{  
  unsigned long currentMillis = millis();  

  if(currentMillis - stormStartTime < stormLengthF) 
  {        
    if(currentMillis - previousMillis2 > lightningInterval) 
    {
      lightningInterval = random(LSInt1F,LSInt2F);//random pick of time gap for lightning
      previousMillis2 = currentMillis;   

      byte burst2 = random(0,100);
      burst = random(10,200); //Short burst of lightning value
      big = random(500,1500);//long burst of lightning value
      lL = random(lLV1,lLV2);//lower light lighting value
      hL = random(hLV1,hLV2);//higher light lightning value
      int lL2 = random(lLV1,lLV2);//lower light lighting value
      int hL2 = random(hLV1,hLV2);//higher light lightning value
      lightningPWMr = random(0,4);
      byte lightningPWMr2 = random(0,4);
      mixer = random(burst,big);//random pick of short or long bursts of lightning
      int mixer2 = random(burst,big);//random pick of short or long bursts of lightning
      for(int i = 0; i < mixer; i += diming++)
      {
        diming = random(1,100);
        diming2 = random(1,100);
        pwm.setPWM(lightningPWMr, 0, random(lL,hL));
        Alarm.delay(diming);//random number delay to remain at set brightness value
        if (burst2<30)
        {
          pwm.setPWM(lightningPWMr2, 0, random(lL2,hL2));
          Alarm.delay(diming2);//random number delay to remain at set brightness value
        }   
        
      Serial.print("Port ");
      Serial.print(targetArray[random(foundCount)]);  //choose an active port number in the range found
      Serial.println(" is active");     
      }
        if (tempLiteMode==1)
        {
          pwm.setPWM(0, 0, cloudTR);
          pwm.setPWM(1, 0, cloudTG);
          pwm.setPWM(2, 0, cloudTG2);
          pwm.setPWM(3, 0, cloudTB);
          pwm.setPWM(4, 0, cloudTB2);
          pwm.setPWM(5, 0, cloudTW);
          pwm.setPWM(6, 0, cloudTV);
          pwm.setPWM(7, 0, cloudTM);
        }
    }
  }
  else // end of lightning storm
  {
    lightningStormStart=0;
    cloudDurationMillis = random(35000,40000);
    cloudStartTime = millis(); 
    stormEnd=1;
  }
}

So the above code as posted does work as I want it to but I need to restart the arduino for it to see the changes? Any ideas what I’m doing wrong?

byte sourceArray[] = {ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8};

This creates a new array but it will only be available in the function in which it was declared. If you have another array of the same name but with a different scope then its values will not change. You could have a global array and populate it like this.

sourceArray[0] = ch1;
sourceArray[1] = ch2;
sourceArray[2] = ch3;
etc etc

This is very inefficient but would work. This would be better.

for (int ch = 0; ch < 8; ch++)
{
  sourceArray[ch] = EEPROM.read(160 + ch);
}

The scope of the variables also explains your second and probably the third problem. The simplest way to deal with this is to make all variables global although this is not always a good idea.

Sorry that confuses me, I can't even get the array working with those suggestions.

I thought this was set as a global variable as it's not located in a function. I only have this one array with this name.

byte sourceArray[] = {ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8};

It does work and the only problem is when ch1, ch2, ch3 etc... change from 0 to 1 or vise versa the array isn't updated.

Right, you put the values of those variables in the array. Nothing links changes in those variables back to the array.

int a = 1;
int b = a;
a = 2;
Serial.println(b);

It will still print 1. That's what a was when I assigned it to b. It is the same with arrays.

If you want to change the values in the array, then ditch all the other variables and just stick with the array. If you have channel 0 through 7 instead of 1 through 8 then you can use the channel number to index the array. Anytime you see variable names with numbers on the end you should be thinking array.

So instead of altering ch1 and expecting sourceArray[0] to know wanything about that, just modify sourceArray[0] and get rid of the ch1 variable all together. Use sourceArray[0] anywhere you would have used that.

robsworld78:
Sorry that confuses me, I can’t even get the array working with those suggestions.

I thought this was set as a global variable as it’s not located in a function. I only have this one array with this name.

byte sourceArray[] = {ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8};

It does work and the only problem is when ch1, ch2, ch3 etc… change from 0 to 1 or vise versa the array isn’t updated.

Now the problem makes more sense. I thought that you had 2 arrays with the same name but different scope and were updating one and expecting the other one to reflect any changes. Perhaps it is time for you to post the latest version of your program.

Note that you could still do

for (int ch = 0; ch < 8; ch++)
{
  sourceArray[ch] = EEPROM.read(160 + ch);
}

to populate the array with the current values held in EEPROM.

Well I’m getting closer but there’s another strange problem. :-[

I have this declared globally.

byte sourceArray[8];
const byte sourceArraySize = sizeof(sourceArray) / sizeof(sourceArray[0]);
byte targetArray [sourceArraySize];
byte foundCount;

To load the array I can use either sourceArray[0] = EEPROM.read(160) or what I have used, both do the same. I added the serial print to verify the changes are taking.

  Serial.print("ch0 = ");
  Serial.println(EEPROM.read(160));
  Serial.print("ch1 = ");
  Serial.println(EEPROM.read(161));
  Serial.print("ch2 = ");
  Serial.println(EEPROM.read(162));
  Serial.print("ch3 = ");
  Serial.println(EEPROM.read(163));
  Serial.print("ch4 = ");
  Serial.println(EEPROM.read(164));
  Serial.print("ch5 = ");
  Serial.println(EEPROM.read(165));
  Serial.print("ch6 = ");
  Serial.println(EEPROM.read(166));
  Serial.print("ch7 = ");
  Serial.println(EEPROM.read(167));

for (int ch = 0; ch < 8; ch++)
{
  sourceArray[ch] = EEPROM.read(160 + ch);

    if (sourceArray[ch] == 0) //if we find an active port
    {
      targetArray[foundCount] = ch;  //save its position
      foundCount++;  //ready for next save
    }
}

I’ve combined the 2 for statements as it works the same and this is cleaner.

The code in snippet 2 is called when a button is pressed, so I expect it should repopulate the array every time that button is pressed.

When I press that button it does run the code properly and every time I change a channel to value 0 the array does see the change however when I change that channel back to value 1 it doesn’t see the change, at least not until I restart the arduino?

Example.

I start with only channel 0 with a value of 0, and run my storm and only ch0 is selected from using the following.

      Serial.print("Port ");
      Serial.print(targetArray[random(foundCount)]);  //choose an active port number in the range found
      Serial.println(" is active");

Then I stop the storm, make ch0 and ch1 with value of 0 and run the storm again and it will randomly choose from ch0 and ch1.

Stop again add ch2 with value 0, run storm again and it randomly chooses from ch0, ch1 and ch3. Perfect! It works, but this is where it gets strange. I can add as many as I want and it will always do what it should but once I start changing channel values back to 1 it still selects them and assumes they are value 0.

If we continue with the example above where only ch0, ch1 and ch2 have a value 0, I can then change there value to 1 and change ch3 and ch4 to value 0, then run the storm it choose out of the 5 channels. It won’t pick ch5, ch6 or ch7 because they were never set to 0 since the last arduino restart. Once all values have been set to 0 the unit has to be restarted.

The arduino can be restarted with all the values still at 0, I just need to make sure I change them all back to 1 before I run the code to repopulate the array or the array will always thing the are value 0. Is there a way to “flush” the array before repopulating it?

Hope all that makes sense.

I can add as many as I want and it will always do what it should but once I start changing channel values back to 1 it still selects them and assumes they are value 0.

Are you clearing the values from targetArray anywhere in the program before looking for active channels a second time ?

No I'm not.

robsworld78: No I'm not.

Then it will still contain the values from the previous selection. Another thought. Where do you initialise foundcount before the search ?

Not sure, I have the code exactly like post 14. The search is done when the button is pressed.

It is time you posted the whole of your program.

If you never set foundcount back to zero then when you increment it "ready for next save" it will already be at the previous value so will keep getting larger and larger and targetArray will already have values in its lower positions that were valid from last time. Worse, as foundcount gets bigger each time you search and you use it as an index to targetArray then you will eventually stray outside the memory set aside for the array.

Some of this is speculation based on the code snippets that you have posted. Please post the whole program or a smaller but working program that illustrates the problem.