6

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();
}
Chris Porter
  • 2,449
  • 2
  • 24
  • 26

5 Answers5

11

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
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 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 Jun 09 '10 at 00:07
9
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
  • 263,068
  • 57
  • 365
  • 409
3

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
  • 1,127
  • 6
  • 7
  • If the result of "(int)allValues & rand.Next()" is zero, and you do not have a zero enum member, you get an invalid enum i.e. (Colours)0. – Tim Lloyd Jun 08 '10 at 22:53
  • OK, I think it works now. But it got a little messier and less clear. Only useful if performance is a serious concern. – bbudge Jun 08 '10 at 23:32
  • 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 Jun 09 '10 at 00:04
  • Added code for that, albeit with ugly "magic" numbers. With that, I'm done with this question! – bbudge Jun 09 '10 at 00:28
2

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

Tim Lloyd
  • 37,954
  • 10
  • 100
  • 130
  • 1
    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 Jun 09 '10 at 00:01
  • 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! :) – Tim Lloyd Jun 09 '10 at 00:11
  • Not the answer you were looking for then? – Tim Lloyd Jun 09 '10 at 11:29
  • A good answer for sure but the one I selected answer was a little shorter that's all. – Chris Porter Jun 09 '10 at 12:54
  • 2
    No offence, but I think you'll find that everyone answered the question incorrectly until I answered... – Tim Lloyd Jun 09 '10 at 13:25
-1

In my case - I have enums with missing members e.g 0x01, 0x02, 0x08, and large ulong enum with 0x200000000 maximal value.

This code works for all of my cases:

/// <summary>
///     Gets <see cref="System.Random"/> instance.
/// </summary>
public static Random Random { get; } = new Random(Guid.NewGuid().GetHashCode());

/// <summary>
///     Gets random combination of Flags Enum.
/// </summary>
/// <typeparam name="T">Enum type.</typeparam>
/// <returns>Random Flags Enum combination.</returns>
public static T GetRandomFlagsEnumValue<T>()
    where T : Enum
{
    var allValues = (T[])Enum.GetValues(typeof(T));

    ulong numberValue = allValues.OrderBy(x => Random.Next())
        .Take(GetRandomInteger(1, allValues.Length - 1))
        .Select(e => Convert.ToUInt64(e, CultureInfo.InvariantCulture))
        .Aggregate((a, c) => a + c);

    return (T)Enum.ToObject(typeof(T), numberValue);
}
shatulsky
  • 306
  • 2
  • 10