2009-08-26 25 views
7

Considera:¿Manera eficiente de encontrar la longitud de enum de banderas?

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

Colors myColor=Colors.Red|Colors.Blue; 

Actualmente, lo estoy haciendo de la siguiente manera:

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

pero espero que hay una manera más eficiente de encontrar la longitud, tal vez basado en operaciones BitSet.

Por favor, si es posible, explique por qué y cómo funciona su solución.

Además, si es un duplicado, apúntelo y eliminaré esta pregunta. Las únicas preguntas similares en SO que he podido encontrar estaban preocupadas por encontrar la longitud de todas las combinaciones posibles de Colors enum, pero no de la variable myColors.

ACTUALIZACIÓN: He Benchmarked cuidadosamente cada solución (1 000 000 repeticiones cada una) y aquí está el resultado:

  1. Stevo3000 - 8 ms
  2. MattEvans - 10 ms
  3. sedoso - 34ms
  4. Lucas - 1757ms
  5. Guffa - 4226ms
  6. Tomas Levesque - 32810ms

El Stevo3000 es un claro ganador (con Matt Evans con medalla de plata).

Muchas gracias por su ayuda.

ACTUALIZACIÓN 2: Esta solución funciona incluso más rápido: 41 ms para 100 000 000 iteraciones (aproximadamente 40 veces más rápido (OS de 32 bits) que Stevo3000)

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

duplicado? http://stackoverflow.com/questions/677204 – strager

+0

Sí, lo es, pero esta pregunta tiene respuestas más interesantes que la original. ¿Debo eliminar la pregunta? – Valentin

Respuesta

9

El siguiente código le dará el número de bits que se establecen para un número dado de cualquier tipo que varían en tamaño desde bytes hasta mucho tiempo.

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; 
} 

Este código es muy eficiente ya que sólo repite una vez para cada bit en lugar de una vez por cada bit sea posible, como en los otros ejemplos.

+0

BTW: se supone que cada indicador establece un solo bit y no se solapan banderas. – devstuff

+0

@Luke - No podría decirlo mejor. – stevehipwell

+0

@devstuff - @ El comentario de Luke parece haber sido eliminado. Un indicador válido es un bit o un compuesto compuesto por varios bits (utilizado como abreviatura). Esta función es solo para calcular los bits inderviduales. – stevehipwell

1

suponiendo que son las banderas, sólo se puede utilizar uno de los métodos here, para contar el número de bits establecidos.

Funciona porque, siempre que sean banderas, cuando cada una de ellas está 'OR' encendida, establece un bit.

- Editar

código

la muestra usando uno de los métodos en ese enlace:

[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(); 
    } 
} 
1

Una aproximación será simplemente contando el número de bits puestos en myColors, pero que sólo funcionará si valor de cada miembros de la enumeración es potencia de 2.

-3

Prueba esto ...

Colors.GetValues().Length(); 

... ¿o es demasiado obvio?

EDIT:

bien, acabo de leer la pregunta de nuevo, y se dio cuenta de que lo que necesita la longitud de 'Mycolors, no 'colores' - déjame pensar en eso.

hacer otras modificaciones:

Ahora estoy confundido - solución publicados de la OP nunca funcionaría, ya que myColor.ToString() devuelve '5' y la aplicación de Split (nuevo char [] { ''}) a esto daría como resultado una matriz con una longitud de 1. ¿OP hizo realmente que esto funcione?

+0

¡No es obvio porque está mal! El OP intenta determinar cuántos indicadores se establecen en una variable enum, no cuántos se definen en la enumeración en sí. – LukeH

+1

Creo que su código es un error, lo siento. Lo que quiso decir fue: Enum.GetValues ​​(typeof (Colors)). Length; Pero esto calcula la longitud de los colores, no myColors. Por favor lea la pregunta. – Valentin

+0

belugabob: Él está contando el número de comas. Cuando hay más de un elemento en el valor (a | b), tendrá dos comas.Es una muy mala forma de hacer el recuento, de ahí la razón por la que hizo su pregunta :) –

2

Aquí hay una forma razonablemente fácil de contar los bits. Cada bit se desplaza, por turno, al LSB de Int64 que es AND -ed con 1 (para enmascarar cualquiera de los otros bits) y luego se agrega al total acumulado.

int length = Enumerable.Range(0, 64).Sum(x => ((long)myColor >> x) & 1); 
+0

No es más difícil; sigue cambiando hasta que llegues a 0, suponiendo que un cambio no lleve el MSB. – strager

+0

+1, breve y eficiente (aunque no es muy legible ...) –

+0

@strager: No estoy seguro de lo que quiere decir con "hasta que llegue a 0". ¿Puedes elaborar? – LukeH

2

Aquí está mi opinión sobre esto ... que cuenta el número de bits puestos en el valor

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

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

    val = val >> 1; 
} 
+0

@Matt - Parece que esto funciona, pero parece que necesita más iteraciones que bits, lo que hace que necesite un bloque if. – stevehipwell

+0

@ Stevo3000 - Sí, tienes razón, iterará todos los bits hasta el bit de conjunto más significativo. Tu solución no, ¡buena! :) Definitivamente recordaré que para el futuro – Matt

3

Éstos son algunos métodos de extensión para manipular Flags enumeraciones:

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); 
    } 
} 

En su caso puede utilizar el método GetFlags:

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

Es pro bably no es tan eficiente como la respuesta de Lucas, pero es más fácil de usar ...

0

La solución que sea más fiable es poner a prueba para cada valor de la enumeración:

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

Esto funcionará incluso si el valor tiene bits puestos donde hay valor no se define en la enumeración, por ejemplo:

Colors myColor = (Colors)65535; 

Esto también funciona para enumeraciones con valores que utilizan más de un solo bit:

[Flags] 
enum Colors { 
    Red = 0xFF0000, 
    Green = 0x00FF00, 
    Blue = 0x0000FF 
} 
+0

BTW: si se usa esto en un bucle, se ejecutará mucho más rápido si el resultado Enum.GetValues ​​() reflejado se recupera de una variable estática. – devstuff

1

He hecho un método de ayuda para mí. Tal vez sea útil para otros.

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; 
    } 
} 
0
int value = Enum.GetNames(typeof(Colors)).Length; 
public static int NumberOfOptions(int value) 
{ 
    int result = (int)Math.Pow(2, value-1); 
    return result; 
} 
Cuestiones relacionadas