8

考虑一下:

[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 - 8ms
  2. 马特埃文斯 - 10 毫秒
  3. 丝滑 - 34 毫秒
  4. 卢克 - 1757 毫秒
  5. 古法 - 4226ms
  6. Tomas Levesque - 32810 毫秒

Stevo3000 无疑是赢家(Matt Evans 获得银牌)。

非常感谢您的帮助。

更新 2:此解决方案运行得更快:100 000 000 次迭代需要 41 毫秒(大约比 Stevo3000 快 40 倍(32 位操作系统))

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

10 回答 10

11

以下代码将为您提供为给定数量的任何类型设置的位数,这些类型的大小从字节到长不等。

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

这段代码非常高效,因为它只为每个位迭代一次,而不是像其他示例中那样为每个可能的位迭代一次。

于 2009-08-26T07:48:26.103 回答
3

以下是一些操作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();

它可能没有卢克的答案那么有效,但它更容易使用......

于 2009-08-26T07:54:59.057 回答
2

这是计算位的一种相当简单的方法。每个位依次移动到以 1Int64AND-ed 的 LSB(以屏蔽任何其他位),然后添加到运行总数中。

int length = Enumerable.Range(0, 64).Sum(x => ((long)myColor >> x) & 1);
于 2009-08-26T07:42:32.700 回答
2

这是我对此的看法......它计算值中设置的位数

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

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

    val = val >> 1;
}
于 2009-08-26T07:51:30.793 回答
1

假设它们是标志,您可以只使用此处的一种方法来计算设置的位数。

它之所以有效,是因为只要它们是标志,当每个标志都“或”打开时,它就会设置一位。

- 编辑

使用该链接上的一种方法的示例代码:

[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();
    }
}
于 2009-08-26T07:31:56.263 回答
1

一个粗略的近似值只是计算 中设置的位数myColors,但这仅在每个枚举成员的值为 2 的幂时才有效。

于 2009-08-26T07:32:11.957 回答
1

我为自己做了一个辅助方法。也许它对其他人有用。

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;
    }
}
于 2013-02-05T17:19:12.140 回答
0

最可靠的解决方案是测试枚举中的每个值:

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
}
于 2009-08-26T08:25:26.903 回答
0
int value = Enum.GetNames(typeof(Colors)).Length;
public static int NumberOfOptions(int value)
{
    int result = (int)Math.Pow(2, value-1);
    return result;
}
于 2016-05-19T12:16:10.637 回答
-3

试试这个...

Colors.GetValues().Length();

……还是太明显了?

编辑:

好的,我刚刚再次阅读了这个问题,并意识到你需要'mycolors'的长度,而不是'Colors' - 让我考虑一下。

进一步编辑:

现在我很困惑 - OP 发布的解决方案永远不会起作用,因为 myColor.ToString() 返回 '5' 并将 Split(new char[]{','}) 应用于这将导致一个长度为 1 的数组. OP真的让这个工作了吗?

于 2009-08-26T07:35:59.373 回答