【问题标题】:Counting the number of flags set on an enumeration计算在枚举上设置的标志数
【发布时间】:2009-03-24 12:41:12
【问题描述】:

我确信一定有更好的方法来做到这一点。我正在尝试对 Flags 枚举进行计数操作。在我迭代所有可能的值并计算成功的 AND 操作之前。

例如

[Flags]
public enum Skills
{
    None = 0,
    Skill1 = 1,
    Skill2 = 2,
    Skill3 = 4,
    Skill4 = 8,
    Skill5 = 16,
    Skill6 = 32,
    Skill7 = 64,
    Skill8 = 128
}

public static int Count(Skills skillsToCount)
{
   Skills skill;
   for (int i = 0; i < SkillSet.AllSkills.Count; i++)
   {
      skill = SkillSet.AllSkills[i];
      if ((skillsToCount & skill) == skill && skill != Skills.None)
         count++;
   }
   return count;
}

我确信一定有更好的方法可以做到这一点,但一定是精神障碍。谁能提出更好的解决方案?

【问题讨论】:

  • 您是否想弄清楚技能枚举本身中的标志总数是否会更清楚?或应用于“技能”的技能枚举值的数量?
  • 我绝对看错了。看起来他正在尝试计算打开的位数,而不是枚举中的项目数。我删除了我的帖子。
  • 抱歉不清楚。事实上,我正在尝试计算传递给 Count 方法的活动技能的数量。将稍微编辑问题以使其更清晰。
  • @Ian:我编辑了帖子标题以使其更清晰。如果您认为这不能准确反映您想要的结果,请随意回滚。
  • 从 .NET Core 3.0 开始可以使用 BitOperations.PopCount:docs.microsoft.com/en-us/dotnet/api/…

标签: c# enums


【解决方案1】:

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

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

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

【讨论】:

  • @JohannGerell 您可能会喜欢我刚刚为您的礼包发布的answer! :)
【解决方案2】:

查看 Assaf 建议的网站后,我设法找到了一个稍微不同的解决方案,我为 Int32 工作。

这是其他人的代码:

    internal static UInt32 Count(this Skills skills)
    {
        UInt32 v = (UInt32)skills;
        v = v - ((v >> 1) & 0x55555555); // reuse input as temporary
        v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // temp
        UInt32 c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count
        return c;
    }

【讨论】:

【解决方案3】:

使用BitArray 和 LINQ 的一种非常简洁的方法:

public static int Count(Skills skillsToCount)
{
    return new BitArray(new[] {(int)skillsToCount}).OfType<bool>().Count(x => x);
}

【讨论】:

  • 唯一的问题 - 不适用于基于长的枚举(.NET v 4.7)
  • @NazimTurakulov 实际上确实如此。
【解决方案4】:

count相当于统计枚举的整数值中有多少位被设置为1。

在 C/C++ 中有非常快速的方法可以做到这一点,您可以适应 C#:

例如

int bitcount(unsigned int n) {
   /* works for 32-bit numbers only    */
   /* fix last line for 64-bit numbers */

   register unsigned int tmp;

   tmp = n - ((n >> 1) & 033333333333)
           - ((n >> 2) & 011111111111);
   return ((tmp + (tmp >> 3)) & 030707070707) % 63;
}

取自here

编辑
提供的链接已失效。发现 another one 可能包含相同的内容。

【讨论】:

  • 这就是我所追求的。虽然还不能完全正常工作。
  • 你可能对我有更好的理解...目前我正在尝试使用 UInt32 但 033333333333 等不会转换为 UInt32。
  • 使用该网站,我设法找到了一个方法略有不同的帖子,这让我找到了解决方案。谢谢阿萨夫。
  • 在将掩码更改为八进制后,我设法让 VB 版本正常工作。
  • 我们在现场有更多关于这个算法的信息,所以没有必要跟进那些死的外部链接:stackoverflow.com/q/109023/1132334
【解决方案5】:

如果您的目标是 .NET Core 3.0 或更高版本,则可以使用 BitOperations.PopCount(),它在 uintulong 中运行并返回 1 的位数。

如果您的 CPU 支持 SSE4,它将使用 POPCNT CPU 指令,否则将使用软件回退。

public static int Count(Skills skillsToCount)
{
   return BitOperations.PopCount((ulong)skillsToCount);
}

【讨论】:

    【解决方案6】:

    有一种使用函数式编程 (LINQ) 的直接方法:

    var skillCount = Enum
        .GetValues(typeof(Skills))
        .Cast<Enum>()
        .Count(skills.HasFlag);
    

    它可能比 bit-juggling 解决方案慢一点,但它具有恒定的运行时间并且更直观。

    虽然 GetValues 仍在分配,但编译器很有可能将其优化掉。

    【讨论】:

    • 免分配?我不这么认为。
    • @apocalypse 如果您能指出分配发生的位置会很有帮助。
    • GetValues 当然是分配一个数组。
    • 谢谢我更新了答案。也许我假设要返回一些不可变的数组。
    【解决方案7】:
    <FlagsAttribute()> _
    Public Enum Skills As Byte
        None = 0
        Skill1 = 1
        Skill2 = 2
        Skill3 = 4
        Skill4 = 8
        Skill5 = 16
        Skill6 = 32
        Skill7 = 64
        Skill8 = 128
    End Enum
    
    
        Dim x As Byte = Skills.Skill4 Or Skills.Skill8 Or Skills.Skill6
        Dim count As Integer
        If x = Skills.None Then count = 0 Else _
            count = CType(x, Skills).ToString().Split(New Char() {","c}, StringSplitOptions.RemoveEmptyEntries).Count
    

    取决于“更好”的定义。

    检查 Skills.None 是必需的,因为如果没有位打开,则 string() 返回 Skills.None 导致计数为 1。这对于整数、长整数和它们的无符号亲属同样适用。

    【讨论】:

      【解决方案8】:

      使用此方法的唯一原因是标志不连续并且标志将定期添加。

      <FlagsAttribute()> _
      Public Enum Skills As Integer
          Skill1 = CInt(2 ^ 0) 'bit 0
          Skill2 = CInt(2 ^ 1)
          Skill3 = CInt(2 ^ 2)
          Skill4 = CInt(2 ^ 3)
          Skill5 = CInt(2 ^ 4)
          Skill6 = CInt(2 ^ 5)
          Skill7 = CInt(2 ^ 6)
          Skill8 = CInt(2 ^ 7)
          Skillx = CInt(2 ^ 10) 'bit 10, some bits were skipped
      End Enum
      
      
          Dim mySkills As Integer = Skills.Skillx Or Skills.Skill4 Or Skills.Skill8 Or Skills.Skill6
          Dim count As Integer 'count of bits on
          count = CType(mySkills, Skills).ToString().Split(New Char() {","c}, _
                                                           StringSplitOptions.RemoveEmptyEntries).Count
      

      如果“更好”意味着更快,这不是 ;) 它。

      【讨论】:

        【解决方案9】:
        int count = Enum.GetValues(typeof(Skills)).Length;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多