Подсчет количества флагов, установленных в перечислении

Я уверен, что должен быть гораздо лучший способ сделать это. Я пытаюсь выполнить операцию подсчета в перечислении Flags. Раньше я перебирал все возможные значения и подсчитывал успешные операции И.

e.g.

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

Я уверен, что должен быть лучший способ сделать это, но я должен страдать от психического блока. Может ли кто-нибудь посоветовать более красивое решение?


person Ian    schedule 24.03.2009    source источник
comment
Не могли бы вы уточнить, пытаетесь ли вы выяснить общее количество флагов в самом перечислении навыков? или количество значений перечисления Skills, применяемых к навыкам?   -  person dance2die    schedule 24.03.2009
comment
Я определенно неправильно это прочитал. Похоже, он пытается подсчитать количество включенных битов, а не количество элементов в перечислении. Я удалил свой пост.   -  person John Feminella    schedule 24.03.2009
comment
Извините за неясность. Действительно, я пытаюсь подсчитать количество активных навыков, переданных в метод Count. Немного отредактирую вопрос, чтобы было понятнее.   -  person Ian    schedule 24.03.2009
comment
@Ian: я отредактировал заголовок сообщения, чтобы сделать его более понятным. Не стесняйтесь откатывать, если считаете, что это не точное отражение того, что вы хотели.   -  person John Feminella    schedule 24.03.2009
comment
Вы можете использовать BitOperations.PopCount начиная с .NET Core 3.0: docs.microsoft.com/en-us/dotnet/api/   -  person Skarllot    schedule 18.06.2021


Ответы (8)


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

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

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

person stevehipwell    schedule 26.08.2009
comment
@JohannGerell Вам может понравиться ответ, который я только что разместил для вашей подарочной сумки! :) - person Neo; 02.03.2017

Посмотрев на сайт, Ассаф предположил, что мне удалось найти немного другое решение, которое я получил, работая для 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;
    }
person Ian    schedule 24.03.2009
comment
Но перечисления Flags в любом случае ограничены 32 параметрами в соответствии со спецификацией, так что это не проблема. - person Ian; 29.04.2016
comment
Если кому-то интересно, это называется Hemming wight. Более подробная информация из Википедии: en.wikipedia.org/wiki/Hamming_weight - person SOReader; 05.05.2017
comment
enum может быть унаследован от long - person Slava; 26.04.2018
comment
Вы можете использовать BitOperations.PopCount начиная с .NET Core 3.0: docs.microsoft.com/en-us/dotnet/api/ - person Skarllot; 18.06.2021

Очень лаконичный способ сделать это с помощью BitArray и LINQ:

public static int Count(Skills skillsToCount)
{
    return new BitArray(new[] {(int)skillsToCount}).OfType<bool>().Count(x => x);
}
person Neo    schedule 02.03.2017
comment
Единственная проблема - не будет работать с длинными перечислениями (.NET v 4.7) - person Nozim Turakulov; 12.08.2018
comment
@NazimTurakulov На самом деле так и есть. - person Neo; 12.08.2018
comment
работает как шарм в моих тестах - person Walter Vehoeven; 20.05.2021

Подсчет эквивалентен подсчету того, сколько битов установлено в 1 в целочисленном значении перечисления.

Есть очень быстрые способы сделать это в C/C++, которые вы можете адаптировать к C#:

e.g.

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

Взято с здесь.

EDIT
Предоставленная ссылка не работает. Найден еще один, который, вероятно, содержит тот же контент.

person Assaf Lavie    schedule 24.03.2009
comment
Это больше, что я после да. Хотя пока не могу заставить его работать. - person Ian; 24.03.2009
comment
Возможно, вы лучше понимаете это, чем я... В настоящее время я пытаюсь использовать UInt32, но 033333333333 и т. д. не будет преобразован в UInt32. - person Ian; 24.03.2009
comment
Используя этот сайт, мне удалось найти сообщение с немного другим подходом, который привел меня к решению. Спасибо Ассаф. - person Ian; 24.03.2009
comment
мне удалось заставить работать версию VB после изменения маски на восьмеричное. - person dbasnett; 24.03.2009
comment
у нас есть больше об этом алгоритме на месте, поэтому нет необходимости следить за этими мертвыми внешними ссылками: stackoverflow.com/q/109023 /1132334 - person Cee McSharpface; 07.08.2017

Существует прямой способ использования функционального программирования (LINQ):

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

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

person Kai Giebeler    schedule 15.03.2018
comment
Без выделения? Я так не думаю. - person apocalypse; 13.05.2019
comment
@apocalypse Было бы полезно, если бы вы указали, где происходит выделение. - person Kai Giebeler; 13.05.2019

Если вы ориентируетесь на .NET Core 3.0 или выше, вы можете использовать BitOperations.PopCount(), он работает в uint или ulong и возвращает число 1 бит.

Если ваш ЦП поддерживает SSE4, он будет использовать инструкцию ЦП POPCNT, в противном случае будет использоваться программный резерв.

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

<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. Это будет работать одинаково для целых, длинных и их беззнаковых родственников.

person dbasnett    schedule 24.03.2009

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

<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

если «лучше» означает быстрее, это не так;) это не так.

person dbasnett    schedule 24.03.2009