Как получить все возможные комбинации перечислений (Flags) - PullRequest
5 голосов
/ 25 мая 2011
[Flags]
public enum MyEnum
{
    None = 0,
    Setting1 = (1 << 1),
    Setting2 = (1 << 2),
    Setting3 = (1 << 3),
    Setting4 = (1 << 4),
}

Мне нужно как-то обойти все возможные настройки и передать комбинацию настроек в функцию.К сожалению, я не смог понять, как это сделать

Ответы [ 8 ]

4 голосов
/ 25 мая 2011

Не проверено, используйте на свой страх и риск, но должны решить проблему достаточно обобщенно. System.Enum не является допустимым ограничением, поскольку технически C # разрешает наследование только в / с class, бэкэнд обходит это для Enum и ValueType. Так что извините за уродливый кастинг. Это также не очень эффективно, но если вы не запускаете это для динамически сгенерированного типа, это нужно делать только один раз за выполнение (или один раз за период, если сохранено).

public static List<T> GetAllEnums<T>()
    where T : struct
    // With C# 7.3 where T : Enum works
{
    // Unneeded if you add T : Enum
    if (typeof(T).BaseType != typeof(Enum)) throw new ArgumentException("T must be an Enum type");

    // The return type of Enum.GetValues is Array but it is effectively int[] per docs
    // This bit converts to int[]
    var values = Enum.GetValues(typeof(T)).Cast<int>().ToArray();

    if (!typeof(T).GetCustomAttributes(typeof(FlagsAttribute), false).Any())
    {
        // We don't have flags so just return the result of GetValues
        return values;
    }

    var valuesInverted = values.Select(v => ~v).ToArray();
    int max = 0;
    for (int i = 0; i < values.Length; i++)
    {
        max |= values[i];
    }

    var result = new List<T>();
    for (int i = 0; i <= max; i++)
    {
        int unaccountedBits = i;
        for (int j = 0; j < valuesInverted.Length; j++)
        {
            // This step removes each flag that is set in one of the Enums thus ensuring that an Enum with missing bits won't be passed an int that has those bits set
            unaccountedBits &= valuesInverted[j];
            if (unaccountedBits == 0)
            {
                result.Add((T)(object)i);
                break;
            }
        }
    }

    //Check for zero
    try
    {
        if (string.IsNullOrEmpty(Enum.GetName(typeof(T), (T)(object)0)))
        {
            result.Remove((T)(object)0);
        }
    }
    catch
    {
        result.Remove((T)(object)0);
    }

    return result;
}

Это работает, собирая все значения и ИЛИ их вместе, а не суммируя, если есть составные числа. Затем оно берет каждое целое число до максимума и маскирует их обратным значением каждого флага, в результате чего действительные биты становятся равными 0, что позволяет нам идентифицировать те биты, которые невозможны.

Проверка в конце предназначена для отсутствия нуля в Enum. Вы можете удалить его, если у вас все нормально, всегда включая в результаты нулевое перечисление.

Дает ожидаемый результат 15, если ему дано перечисление, содержащее 2,4,6,32,34,16384.

2 голосов
/ 25 октября 2012
    public IEnumerable<TEnum> AllCombinations<TEnum>() where TEnum : struct
    {
        Type enumType = typeof (TEnum);
        if (!enumType.IsEnum)
            throw new ArgumentException(string.Format("The type {0} does not represent an enumeration.", enumType), "TEnum");

        if (enumType.GetCustomAttributes(typeof (FlagsAttribute), true).Length > 0) //Has Flags attribute
        {
            var allCombinations = new HashSet<TEnum>();

            var underlyingType = Enum.GetUnderlyingType(enumType);
            if (underlyingType == typeof (sbyte) || underlyingType == typeof (short) || underlyingType == typeof (int) || underlyingType == typeof (long))
            {
                long[] enumValues = Array.ConvertAll((TEnum[]) Enum.GetValues(enumType), value => Convert.ToInt64(value));
                for (int i = 0; i < enumValues.Length; i++)
                    FillCombinationsRecursive(enumValues[i], i + 1, enumValues, allCombinations);
            }
            else if (underlyingType == typeof (byte) || underlyingType == typeof (ushort) || underlyingType == typeof (uint) || underlyingType == typeof (ulong))
            {
                ulong[] enumValues = Array.ConvertAll((TEnum[]) Enum.GetValues(enumType), value => Convert.ToUInt64(value));
                for (int i = 0; i < enumValues.Length; i++)
                    FillCombinationsRecursive(enumValues[i], i + 1, enumValues, allCombinations);
            }
            return allCombinations;
        }
        //No Flags attribute
        return (TEnum[]) Enum.GetValues(enumType);
    }

    private void FillCombinationsRecursive<TEnum>(long combination, int start, long[] initialValues, HashSet<TEnum> combinations) where TEnum : struct
    {
        combinations.Add((TEnum)Enum.ToObject(typeof(TEnum), combination));
        if (combination == 0)
            return;

        for (int i = start; i < initialValues.Length; i++)
        {
            var nextCombination = combination | initialValues[i];
            FillCombinationsRecursive(nextCombination, i + 1, initialValues, combinations);
        }
    }

    private void FillCombinationsRecursive<TEnum>(ulong combination, int start, ulong[] initialValues, HashSet<TEnum> combinations) where TEnum : struct
    {
        combinations.Add((TEnum)Enum.ToObject(typeof(TEnum), combination));
        if (combination == 0)
            return;

        for (int i = start; i < initialValues.Length; i++)
        {
            var nextCombination = combination | initialValues[i];
            FillCombinationsRecursive(nextCombination, i + 1, initialValues, combinations);
        }
    }
2 голосов
/ 25 мая 2011

Вот решение для вашего примера кода, использующее простой цикл for (не используйте, см. Обновление ниже)

int max = (int)(MyEnum.Setting1 | MyEnum.Setting2 | MyEnum.Setting3 | MyEnum.Setting4);
for (int i = 0; i <= max; i++)
{
    var value = (MyEnum)i;
    SomeOtherFunction(value);
}

Обновление: Вот общий метод, который будет возвращать все возможные комбинации.И спасибо @ David Yaw за идею использовать очередь для построения каждой комбинации.

IEnumerable<T> AllCombinations<T>() where T : struct
{
    // Constuct a function for OR-ing together two enums
    Type type = typeof(T);
    var param1 = Expression.Parameter(type);
    var param2 = Expression.Parameter(type);
    var orFunction = Expression.Lambda<Func<T, T, T>>(
        Expression.Convert(
            Expression.Or(
                Expression.Convert(param1, type.GetEnumUnderlyingType()),
                Expression.Convert(param2, type.GetEnumUnderlyingType())),
            type), param1, param2).Compile();

    var initalValues = (T[])Enum.GetValues(type);
    var discoveredCombinations = new HashSet<T>(initalValues);
    var queue = new Queue<T>(initalValues);

    // Try OR-ing every inital value to each value in the queue
    while (queue.Count > 0)
    {
        T a = queue.Dequeue();
        foreach (T b in initalValues)
        {
            T combo = orFunction(a, b);
            if (discoveredCombinations.Add(combo))
                queue.Enqueue(combo);
        }
    }

    return discoveredCombinations;
}
1 голос
/ 10 ноября 2014

Поскольку перечисление помечено, почему бы не просто:

  1. Получите самое высокое значение в перечислении.
  2. Рассчитать количество комбинаций, то есть верхнюю границу.
  3. Перебирать каждую комбинацию, то есть цикл от 0 до верхней границы.

Пример будет выглядеть так

var highestEnum = Enum.GetValues(typeof(MyEnum)).Cast<int>().Max();
var upperBound = highestEnum * 2;    
for (int i = 0; i < upperBound; i++)
{
    Console.WriteLine(((MyEnum)i).ToString());
}
1 голос
/ 25 мая 2011

Во-первых, возьмите список всех отдельных значений. Поскольку у вас есть 5 значений, это (1 << 5) = 32 комбинации, поэтому итерируйте от 1 до 31. (Не начинайте с нуля, это будет означать, что ни одно из значений enum не будет включено.) При итерации изучите биты в число, каждый бит в переменной итерации означает включение этого значения перечисления. Поместите результаты в HashSet, чтобы не было дубликатов, поскольку включение значения None не меняет результирующее перечисление.

List<MyEnum> allValues = new List<MyEnum>(Enum.Getvalues(typeof(MyEnum)));
HashSet<MyEnum> allCombos = new Hashset<MyEnum>();

for(int i = 1; i < (1<<allValues.Count); i++)
{
    MyEnum working = (MyEnum)0;
    int index = 0;
    int checker = i;
    while(checker != 0)
    {
        if(checker & 0x01 == 0x01) working |= allValues[index];
        checker = checker >> 1;
        index++;
    }
    allCombos.Add(working);
}
0 голосов
/ 06 декабря 2018

Заключено с этой версией:

Без чеков:

public IEnumerable<T> AllCombinations<T>() where T : struct
{
    var type = typeof(T);
    for (var combination = 0; combination < Enum.GetValues(type).Cast<int>().Max()*2; combination++)
    {
        yield return (T)Enum.ToObject(type, combination);
    }
}

С некоторыми чеками :

public IEnumerable<T> AllCombinations<T>() where T : struct
{
    var type = typeof(T);
    if (!type.IsEnum)
    {
        throw new ArgumentException($"Type parameter '{nameof(T)}' must be an Enum type.");
    }

    for (var combination = 0; combination < Enum.GetValues(type).Cast<int>().Max()*2; combination++)
    {
        var result = (T)Enum.ToObject(type, combination);

        // Optional check for legal combination.
        // (and is not necessary if all flag a ascending exponent of 2 like 2, 4, 8...
        if (result.ToString() == combination.ToString() && combination != 0)
        {
            continue;
        }

        yield return result;
    }
}
0 голосов
/ 06 августа 2018

Я, вероятно, немного опоздал на вечеринку. Я хотел бы оставить свое решение, которое также включает значения плюс возможный текст комбинации в форме ("V1 | V2", "V1 | V2 | V3",и т.д.).

Я рассмотрел некоторые аспекты предложенных выше решений, поэтому спасибо всем, кто опубликовал предыдущие ответы: D.

Примечание: работает только с Enumустановить в качестве базы 2 комбинации.

public static Dictionary<int,string> GetCombinations( this Enum enu)
    {
        var fields = enu.GetType()
                        .GetFields()
                        .Where(f => f.Name != "value__")
                        .DistinctBy(f=> Convert.ToInt32(f.GetRawConstantValue()));

        var result = fields.ToDictionary(f=>Convert.ToInt32(f.GetRawConstantValue()), f => f.Name);

        int max = Enum.GetValues(enu.GetType()).Cast<int>().Max();
        int upperBound = max * 2;

        for (int i = 0 ; i <= upperBound ; i += 2)
        {
            string s = Convert.ToString(i, 2).PadLeft(Math.Abs(i-max),'0');
            Boolean[] bits = s.Select(chs => chs == '1' ? true : false)
                             .Reverse()
                             .ToArray();

            if (!result.ContainsKey(i))
            {
                var newComb = string.Empty;
                for (int j = 1; j < bits.Count(); j++)
                {
                    var idx = 1 << j;
                    if (bits[j] && result.ContainsKey(idx))
                    {
                        newComb = newComb + result[idx] + " | ";
                    }
                }                                      
                newComb = newComb.Trim(new char[] { ' ', '|' });
                if (!result.ContainsValue(newComb) && !string.IsNullOrEmpty(newComb))
                {
                    result.Add(i, newComb);
                }                    
            }
        }
        return result;
    }
0 голосов
/ 25 мая 2011

Обычно я не хочу обновлять каждую переменную, представляющую максимум перечисления, когда я добавляю новый член в перечисление. Например, мне не нравится высказанное Грегом утверждение:

int max = (int)(MyEnum.Setting1 | MyEnum.Setting2 | ... | MyEnum.SettingN);

Подумайте, когда у вас есть несколько таких переменных, разбросанных по всему вашему решению, и вы решили изменить свое перечисление. Это, конечно, нежелательный сценарий.

Я заранее признаю, что мой код медленнее, но он автоматически корректируется после изменения перечисления, и я стараюсь кодировать таким надежным способом. Я готов заплатить за это некоторые вычислительные затраты, C # все равно об этом. Я предлагаю:

public static IEnumerable<T> GetAllValues<T>() where T : struct
{
    if (!typeof(T).IsEnum) throw new ArgumentException("Generic argument is not an enumeration type");
    int maxEnumValue = (1 << Enum.GetValues(typeof(T)).Length) - 1;
    return Enumerable.Range(0, maxEnumValue).Cast<T>();
}

Это предполагает, что перечисление содержит членов для всех степеней от 2 до определенной степени (включая 0), точно так же, как обычно используется перечисление флага.

...