views:

122

answers:

4

Say I have a function that accepts an enum decorated with the Flags attribute. If the value of the enum is a combination of more than one of the enum elements how can I extract one of those elements at random? I have the following but it seems there must be a better way.

[Flags]
enum Colours
{
    Blue = 1,
    Red = 2,
    Green = 4
}

public static void Main()
{
    var options = Colours.Blue | Colours.Red | Colours.Green;
    var opts = options.ToString().Split(',');
    var rand = new Random();
    var selected = opts[rand.Next(opts.Length)].Trim();
    var myEnum = Enum.Parse(typeof(Colours), selected);
    Console.WriteLine(myEnum);
    Console.ReadLine();
}
+7  A: 

You can call Enum.GetValues to get an array of the enum's defined values, like this:

var rand = new Random();

Colors[] allValues = (Colors[])Enum.GetValues(typeof(Colors));
Colors value = allValues[rand.Next(allValues.Length)];
SLaks
I would like a random value of only a subset of the enum as defined by a bitwise combination like "Blue | Red". Sorry for not being clearer.
Chris Porter
+1  A: 

If I understand correctly, the question is about returning a random enum value from a flags enum value, not returning a random member from a flags enum.

    [Flags]
    private enum Shot
    {
        Whisky = 1,
        Absynthe = 2,
        Pochin = 4,
        BrainEraser = Whisky | Absynthe | Pochin
    }

    [Test]
    public void Test()
    {
        Shot myCocktail = Shot.Absynthe | Shot.Whisky;

        Shot randomShotInCocktail = GetRandomShotFromCocktail(myCocktail);
    }

    private static Shot GetRandomShotFromCocktail(Shot cocktail)
    {
        Random random = new Random();

        Shot[] cocktailShots = Enum.GetValues(typeof(Shot)).
           Cast<Shot>().
           Where(x => cocktail.HasFlag(x)).ToArray();

        Shot randomShot = cocktailShots[random.Next(0, cocktailShots.Length)];

        return randomShot;
    }

Edit

And obviously you should check that the enum is a valid value, e.g.:

 Shot myCocktail = (Shot)666;

Edit

Simplified

chibacity
You are correct in stating the problem. However I think your random function will not return the values with an even distribution. At a 50/50 chance for each value through the loop subsequent values have a lower chance of being selected ?
Chris Porter
I see your point. Strictly speaking it is still random and answers your question. I have updated it to an even distro. It is late and I'm sure I have probably stuffed up! :)
chibacity
And now simplified.
chibacity
Thanks for your efforts
Chris Porter
Not the answer you were looking for then?
chibacity
A good answer for sure but the one I selected answer was a little shorter that's all.
Chris Porter
No offence, but I think you'll find that everyone answered the question incorrectly until I answered...
chibacity
+2  A: 

If you don't mind a little casting, and your enum is of underlying int type, the following will work and is fast.

var rand = new Random();
const int mask = (int)(Colours.Blue | Colours.Red | Colours.Green);
return (Colours)(mask & (rand.Next(mask) + 1));

If you only want a single flag to be set, you could do the following:

var rand = new Random();
return (Colours)(0x1 << (rand.Next(3)));
bbudge
chibacity
True. Let me see if it can be fixed...
bbudge
OK, I think it works now. But it got a little messier and less clear. Only useful if performance is a serious concern.
bbudge
This works, but I particularly wanted only one value returned. Sorry if that wasn't clear. Your solution returns combinations like "Blue | Red | Green"
Chris Porter
Added code for that, albeit with ugly "magic" numbers. With that, I'm done with this question!
bbudge
Great solution, thanks.
Chris Porter
+4  A: 
var options = Colours.Blue | Colours.Green;

var matching = Enum.GetValues(typeof(Colours))
                   .Cast<Colours>()
                   .Where(c => (options & c) == c)    // or use HasFlag in .NET4
                   .ToArray();

var myEnum = matching[new Random().Next(matching.Length)];
LukeH