Question

Considérez ceci:

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

Colors myColor=Colors.Red|Colors.Blue;

À l'heure actuelle, je le fais comme suit:

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

Mais je l'espère, il est un moyen plus efficace de trouver la longueur, peut-être basée sur les opérations BITSET.

S'il vous plaît, si possible, fournir des explications pourquoi et comment fonctionne votre solution.

En outre, si cela un double, s'il vous plaît indiquer et je vais supprimer cette question. Les seules questions similaires sur SO j'ai pu trouver étaient préoccupés de trouver la longueur de toutes les combinaisons possibles de Colors ENUM, mais pas de la variable myColors.

Mise à jour: Je soigneusement étalonnées toutes les solutions (1 000 000 itérations chacun) et voici les résultats:

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

Le Stevo3000 est un gagnant clair (avec Matt Evans tenant la médaille d'argent).

Merci pour votre aide.

UPDATE 2: Cette solution va encore plus rapide: 41 ms pour 100 000 000 itérations (environ 40 fois plus rapide (OS 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; 
Était-ce utile?

La solution

Le code suivant vous donnera le nombre de bits qui sont définis pour un nombre donné de tout type dont la taille varie de l'octet jusqu'à longtemps.

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

Ce code est très efficace car il ne itère une fois pour chaque bit au lieu d'une fois pour chaque bit possible comme dans les autres exemples.

Autres conseils

Voici quelques méthodes d'extension pour manipuler énumérations 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);
    }
}

Dans votre cas, vous pouvez utiliser la méthode GetFlags:

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

Il est probablement pas aussi efficace que la réponse de Luc, mais il est plus facile à utiliser ...

Voici mon avis sur ce ... il compte le nombre de bits défini dans la valeur

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

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

    val = val >> 1;
}

Voici une façon relativement facile de compter les bits. Chaque bit est décalé en tournant le bit de poids faible d'un Int64 qui est AND-ed avec une (pour masquer l'un des autres bits) et ensuite ajouté à la somme courante.

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

Une approximation grossière comptera seulement le nombre de bits mis en myColors, mais cela ne fonctionnera que si la valeur de tous les membres de l'énumération est puissance de 2.

En supposant qu'ils sont des drapeaux, vous pouvez simplement utiliser l'une des méthodes ici , pour compter le nombre de bits établis.

Il fonctionne parce que, tant qu'ils sont des drapeaux, quand chacun est « mis en circuit OU » sur, il définit un bit.

- Modifier

Exemple de code avec l'une des méthodes sur ce lien:

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

Je l'ai fait une méthode d'aide pour moi-même. Ce sera peut-être utile pour les autres.

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

La solution qui est la plus fiable consiste à tester pour chaque valeur dans l'énumération:

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

Cela fonctionne même si la valeur a bits mis où il n'y a pas de valeur définie dans l'énumération, par exemple:

Colors myColor = (Colors)65535;

Cela fonctionne également pour les énumérations avec des valeurs qui utilisent plus d'un seul bit:

[Flags]
enum Colors {
   Red = 0xFF0000,
   Green = 0x00FF00,
   Blue = 0x0000FF
}
int value = Enum.GetNames(typeof(Colors)).Length;
public static int NumberOfOptions(int value)
{
    int result = (int)Math.Pow(2, value-1);
    return result;
}

Essayez ceci ...

Colors.GetValues().Length();

... ou est-ce trop évident?

EDIT:

OK, je viens de lire à nouveau la question, et réalisé que vous avez besoin de la longueur de « mycolors », pas « Couleurs » -. Permettez-moi de penser à ce que

PLUS EDIT:

Maintenant, je suis confus - solution de l'OP affiché le ne marcherait jamais, comme myColor.ToString () retourne « 5 » et l'application de Split (nouveau char [] { « »}) à ce aboutiraient à un tableau avec un longueur de 1. Est-ce que l'OP obtenir ce fait de travailler?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top