Эффективный способ найти длину перечисления флагов? - PullRequest
7 голосов
/ 26 августа 2009

Учтите это:

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

Colors myColor=Colors.Red|Colors.Blue;

В настоящее время я делаю это следующим образом:

int length=myColors.ToString().Split(new char[]{','}).Length;

Но я надеюсь, что есть более эффективный способ определения длины, возможно, основанный на битовых операциях.

Пожалуйста, если возможно, объясните, почему и как работает ваше решение.

Кроме того, если это дубликат, укажите на него, и я удалю этот вопрос. Единственные похожие вопросы по SO, которые я смог найти, касались определения длины всех возможных комбинаций перечисления Colors, но не переменной myColors.

ОБНОВЛЕНИЕ: я тщательно проверил каждое решение (1 000 000 итераций каждое), и вот результаты:

  1. Stevo3000 - 8 мс
  2. MattEvans - 10 мс
  3. Шелковистая - 34мс
  4. Люк - 1757мс
  5. Гуффа - 4226мс
  6. Томас Левеск - 32810мс

Stevo3000 - явный победитель (с Мэттом Эвансом, держащим серебряную медаль).

Большое спасибо за помощь.

ОБНОВЛЕНИЕ 2: Это решение работает еще быстрее: 41 мс на 100 000 000 итераций (примерно в 40 раз быстрее (32-разрядная ОС), чем Stevo3000)

UInt32 v = (UInt32)co;
v = v - ((v >> 1) & 0x55555555); 
v = (v & 0x33333333) + ((v >> 2) & 0x33333333); 
UInt32 count = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; 

Ответы [ 10 ]

10 голосов
/ 26 августа 2009

Следующий код даст вам количество битов, которые установлены для данного числа любого типа, размер которого варьируется от байта до длинного.

public static int GetSetBitCount(long lValue)
{
  int iCount = 0;

  //Loop the value while there are still bits
  while (lValue != 0)
  {
    //Remove the end bit
    lValue = lValue & (lValue - 1);

    //Increment the count
    iCount++;
  }

  //Return the count
  return iCount;
}

Этот код очень эффективен, поскольку он повторяется только один раз для каждого бита, а не один раз для каждого возможного бита, как в других примерах.

3 голосов
/ 26 августа 2009

Вот несколько методов расширения для манипулирования перечислениями Flags:

public static class EnumExtensions
{
    private static void CheckEnumWithFlags<T>()
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (!Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
    {
        CheckEnumWithFlags<T>();
        foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
        {
            if (value.IsFlagSet(flag))
                yield return flag;
        }
    }

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flags);
        if (on)
        {
            lValue |= lFlag;
        }
        else
        {
            lValue &= (~lFlag);
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static T SetFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, true);
    }

    public static T ClearFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, false);
    }

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = 0;
        foreach (T flag in flags)
        {
            long lFlag = Convert.ToInt64(flag);
            lValue |= lFlag;
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }
}

В вашем случае вы можете использовать метод GetFlags:

int count = myColors.GetFlags().Count();

Это, вероятно, не так эффективно, как ответ Люка, но проще в использовании ...

2 голосов
/ 26 августа 2009

Вот мой взгляд на это ... он подсчитывает количество установленных бит в значении

int val = (int)myColor;
int count = 0;

while (val > 0)
{
    if((val & 1) != 0)
    {
        count++;
    }

    val = val >> 1;
}
2 голосов
/ 26 августа 2009

Вот довольно простой способ подсчета битов. Каждый бит смещается по очереди к младшему разряду Int64, который * AND -едан с 1 (чтобы скрыть любые другие биты), а затем добавляется к промежуточному итогу.

int length = Enumerable.Range(0, 64).Sum(x => ((long)myColor >> x) & 1);
1 голос
/ 05 февраля 2013

Я сделал вспомогательный метод для себя. Может быть, это будет полезно для других.

public static class EnumHelper 
{
    public static UInt32 NumFlags(this Enum e)
    {
        UInt32 v = Convert.ToUInt32(e);
        v = v - ((v >> 1) & 0x55555555);
        v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
        UInt32 count = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
        return count;
    }
}
1 голос
/ 26 августа 2009

Грубая аппроксимация будет просто подсчитывать количество битов, установленных в myColors, но это будет работать, только если значение каждого члена перечисления имеет степень 2.

1 голос
/ 26 августа 2009

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

Это работает, потому что, пока они являются флагами, когда каждый из них 'OR'd', он устанавливает один бит.

- Правка

Пример кода с использованием одного из методов по этой ссылке:

[Flags]
enum Test
{
    F1 = 1,
    F2 = 2,
    F3 = 4
}


class Program
{
    static void Main(string[] args)
    {
        int v = (int) (Test.F1 | Test.F2 | Test.F3); // count bits set in this (32-bit value)
        int c = 0; // store the total here
        int[] S = {1, 2, 4, 8, 16}; // Magic Binary Numbers
        int[] B = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF};

        c = v - ((v >> 1) & B[0]);
        c = ((c >> S[1]) & B[1]) + (c & B[1]);
        c = ((c >> S[2]) + c) & B[2];
        c = ((c >> S[3]) + c) & B[3];
        c = ((c >> S[4]) + c) & B[4];

        Console.WriteLine(c);
        Console.Read();
    }
}
0 голосов
/ 19 мая 2016
int value = Enum.GetNames(typeof(Colors)).Length;
public static int NumberOfOptions(int value)
{
    int result = (int)Math.Pow(2, value-1);
    return result;
}
0 голосов
/ 26 августа 2009

Наиболее надежным решением является проверка каждого значения в перечислении:

int len = 0;
foreach (Colors color in Enum.GetValues(typeof(Colors))) {
   if ((myColor & color) == color) {
      len++;
   }
}

Это будет работать, даже если значение имеет биты, установленные в перечислении, где нет определенного значения, например:

Colors myColor = (Colors)65535;

Это также будет работать для перечислений со значениями, которые используют более одного бита:

[Flags]
enum Colors {
   Red = 0xFF0000,
   Green = 0x00FF00,
   Blue = 0x0000FF
}
0 голосов
/ 26 августа 2009

Попробуйте это ...

Colors.GetValues().Length();

... или это слишком очевидно?

РЕДАКТИРОВАТЬ:

Хорошо, я просто прочитал вопрос еще раз и понял, что вам нужна длина «mycolors», а не «Colours» - позвольте мне подумать об этом.

ДОПОЛНИТЕЛЬНОЕ РЕДАКТИРОВАНИЕ:

Теперь я в замешательстве - опубликованное решение OP никогда не будет работать, так как myColor.ToString () возвращает '5', а применение Split (new char [] {','}) приведет к созданию массива с длина 1. Оператор действительно заставил это работать?

...