Assigning a random enum value a property

521 views Asked by At

Hi have been given a task that I am struggling with. I need to assign a random enum to a property. The code I have is this.

public enum PegColour
{
    Red, Green, Blue, Yellow, Black, White
}

And other class which looks like this

public class PegContainer
{
   /// <summary>
   /// Dfines the colour of the first peg
   /// </summary>
    public Peg Colour1 { get; set; }

    /// <summary>
    /// Dfines the colour of the secod peg
    /// </summary>
    public Peg Colour2 { get; set; }

    /// <summary>
    /// Dfines the colour of the third peg
    /// </summary>
    public Peg Colour3 { get; set; }

    /// <summary>
    /// Dfines the colour of the forth peg
    /// </summary>
    public Peg Colour4 { get; set; }

    public void GeneratePegs()
    {

    }
}

my GeneratePegs() method should, each time it is called randomly assign one of the enum colours to one of the properties (Colour1, Colour2 etc.) to complicate matters I need the randomizer to ignore Black and White.

4

There are 4 answers

3
Icemanind On BEST ANSWER

Enums are just integers and therefore integers can be cast to an enum. I would do this:

Random rnd = new Random();

public enum PegColour
{
    Red, Green, Blue, Yellow, Black, White
}

private PegColour GetRandomColoredPeg()
{
    PegColour color = (PegColour)rnd.Next(0, Enum.GetNames(typeof(PegColour)).Length - 2);
    return color;
}

Black and White will never get selected because it only picks randomly from the first 4 colors. As long as you add pegs BEFORE the black and white pegs, this code should work every time, even if you add or remove pegs from the enumeration. So if you wanted to add new colors, you'd just change PegColour to something like this:

public enum PegColour
{
    Red, Green, Blue, Yellow, Purple, Orange, Pink, Black, White
}

You wouldn't need to change anything else!

So your GeneratePegs() method should just look like this:

public void GeneratePegs()
{
    Colour1 = GetRandomColoredPeg();
    Colour2 = GetRandomColoredPeg();
    Colour3 = GetRandomColoredPeg();
    Colour4 = GetRandomColoredPeg();
}
1
O. R. Mapper On

A simple solution could be creating an array that contains all the eligible values:

PegColour[] eligibleValues = new[] { PegColour.Red, PegColour.Blue, PegColour.Green, PegColour.Yellow };

You could then use an instance of Random to randomly pick an index in that array:

var myRandomColour = eligibleValues[myRandom.Next(eligibleValues.Length)];

An advantage of this is that you do not have to assign any specific numeric values to the enum constants for the random selection to work. This way, you are still free to define and use the numeric values for another purpose, if required.


Now, this might still be a little inconvenient in case PegColour frequently gets extended with new elements. In that case, you can retrieve the full list of currently defined constants once upon initialization from the Enum.GetValues method (note that this code snippet assumes the System.Linq namespace has been imported to allow accessing the extension methods from System.Linq.Enumerable):

PegColour[] eligibleValues = Enum.GetValues(typeof(PegColour)).Cast<PegColour>().ToArray();

Obviously, this doesn't yet fulfil the requirement that certain colours be excluded. You can, thus, either hard-code this restriction directly in the array creation expression:

PegColour[] eligibleValues = Enum.GetValues(typeof(PegColour)).Cast<PegColour>().Where(pc => (pc != PegColour.Black) && (pc != PegColour.White)).ToArray();

... or store the colours to exclude in some set to make things more extensible:

PegColour[] eligibleValues = Enum.GetValues(typeof(PegColour)).Cast<PegColour>().Where(pc => !myExcludedColours.Contains(pc)).ToArray();

Note that you can always place this code so as to initialize eligibleValues just once, rather than each time you randomly retrieve a value. This avoids unnecessary possibly costly calls to GetValues, as well as needlessly generating the array anew all the time.

0
David Arno On

I suggest you create a list:

var pegs = new List<Peg> { Peg.Red, Peg.Green, Peg.Blue, Peg.Yellow };

Then using the accepted answer to this question, randomly order it. Finally, assign the values to the pegs:

Colour1 = pages[0];
Colour2 = pages[1];
Colour3 = pages[2];
Colour4 = pages[3];
1
ryanyuyu On

I made a very general extension method for a Random object that produces a random value of an Enum type. This works for all sorts of messed-up enums, including ones with terrible numbering schemes. This is achieved by using Enum.GetValues to get all possible values of the Enum. Then the random just picks a random possible value.

public static TEnum GetRandomEnum<TEnum>(this Random rand, IEnumerable<TEnum> excludedValues)
{
    var type = typeof(TEnum);
    if (!type.IsEnum)
        throw new ArgumentException("Not an enum type");


    var values = Enum.GetValues(type).Cast<TEnum>();

    if (excludedValues != null && excludedValues.Any())
        values = values.Except(excludedValues); 
    //if you call this a lot, you could consider saving this collection in memory
    //   and separate the logic to avoid having to re-generate the collection


    //create a random index for each possible Enum value 
    //will never be out of bounds because it NextDouble returns a value
    //between 0 (inclusive) and 1 (exclusive)  or [0, 1)
    int randomIndex = (int) (rand.NextDouble() * values.Count());
    return values.ElementAt(randomIndex);
}   

This extension method is call like this:

var randomColor = rand.GetRandomEnum<ColorType>(new List<Colors> 
{ 
    Colors.White, 
    Colors.Black
});

A demo.