Элегантно определить, является ли более одного логического значения "истинным"

У меня есть набор из пяти булевых значений. Если более одного из них являются истинными, я хочу превзойти определенную функцию. Какой самый элегантный способ вы можете подумать, что позволит мне проверить это условие в одном выражении if()? Целевой язык - это С#, но меня интересуют также решения на других языках (пока мы не говорим о конкретных встроенных функциях).

Один интересный вариант - хранить булевы в байте, делать правый сдвиг и сравнивать с исходным байтом. Что-то вроде if(myByte && (myByte >> 1)) Но для этого потребуется преобразовать отдельные булевы в байты (через bitArray?), И это кажется немного (каламбур) неуклюжими... [edit] Извините, это должно было быть if(myByte & (myByte - 1)) [/edit ]

Примечание. Это, конечно, очень близко к классической проблеме "подсчет численности населения", "боковое добавление" или "Хэмминг-вес", но не совсем то же самое. Мне не нужно знать, сколько бит установлено, только если оно больше одного. Я надеюсь, что есть намного более простой способ сделать это.

Ответ 1

Как насчет

  if ((bool1? 1:0) + (bool2? 1:0) + (bool3? 1:0) + 
      (bool4? 1:0) + (bool5? 1:0) > 1)
      // do something

или обобщенный метод будет...

   public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
    {
       int trueCnt = 0;
       foreach(bool b in bools)
          if (b && (++trueCnt > threshold)) 
              return true;
       return false;          
    } 

или используя LINQ, как предложено другими ответами:

    public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
    { return bools.Count(b => b) > threshold; }

ИЗМЕНИТЬ (добавить предложение Джоэля Коэхорна: (в .Net 2.x и более поздних версиях)

    public void ExceedsThreshold<T>(int threshold, 
                      Action<T> action, T parameter, 
                      IEnumerable<bool> bools)
    { if (ExceedsThreshold(threshold, bools)) action(parameter); }

или в .Net 3.5 и более поздних версиях:

    public void ExceedsThreshold(int threshold, 
            Action action, IEnumerable<bool> bools)
    { if (ExceedsThreshold(threshold, bools)) action(); }

или как расширение до IEnumerable<bool>

  public static class IEnumerableExtensions
  {
      public static bool ExceedsThreshold<T> 
         (this IEnumerable<bool> bools, int threshold)
      { return bools.Count(b => b) > threshold; }
  }

будет:

  var bools = new [] {true, true, false, false, false, false, true};
  if (bools.ExceedsThreshold(3))
      // code to execute  ...

Ответ 2

Я собирался написать версию Linq, но пять или около того меня избивали. Но мне очень нравится подход params, чтобы избежать необходимости вручную создавать массив. Поэтому я считаю, что лучший гибрид, основанный на ответе rp с телом, заменяет очевидную Linqness:

public static int Truth(params bool[] booleans)
{
    return booleans.Count(b => b);
}

Прекрасно понятно читать и использовать:

if (Truth(m, n, o, p, q) > 2)

Ответ 3

Это время для обязательного ответа LINQ, который в этом случае на самом деле довольно аккуратный.

var bools = new[] { true, true, false, false, false };

return bools.Count(b => b == true) > 1;

Ответ 4

Я бы просто бросил их в ints и суммировал.

Если вы не находитесь в суперсовременном внутреннем цикле, это может быть легко понятным.

Ответ 5

Я бы написал функцию для получения любого количества логических значений. Это вернет число тех значений, которые являются истинными. Проверьте результат на количество значений, которые вам нужно сделать положительными, чтобы что-то сделать.

Усильнее, чтобы сделать это ясным, а не умным!

private int CountTrues( params bool[] booleans )
{
    int result = 0;
    foreach ( bool b in booleans )
    {
        if ( b ) result++;
    }

    return result;
}

Ответ 6

Если бы были миллионы вместо 5, вы могли бы избежать Count() и сделать это вместо этого...

public static bool MoreThanOne (IEnumerable<bool> booleans)
{
    return booleans.SkipWhile(b => !b).Skip(1).Any(b => b);
}

Ответ 7

Если ваши флаги упакованы в одно слово, то Michael Burr solution будет работать. Однако цикл не нужен:

int moreThanOneBitSet( unsigned int v)
{
    return (v & (v - 1)) != 0;
}

Пример

 v (binary) | v - 1 | v&(v-1) | result
------------+-------+---------+--------
       0000 |  1111 |    0000 |  false
       0001 |  0000 |    0000 |  false
       0010 |  0001 |    0000 |  false
       0011 |  0010 |    0010 |   true
       .... |  .... |    .... |   ....
       1000 |  0111 |    0000 |  false
       1001 |  1000 |    1000 |   true
       1010 |  1001 |    1000 |   true
       1011 |  1010 |    1010 |   true
       1100 |  1011 |    1000 |   true
       1101 |  1100 |    1100 |   true
       1110 |  1101 |    1100 |   true
       1111 |  1110 |    1110 |   true

Ответ 8

если вы имеете в виду больше или равно одному логическому значению, равному true, вы можете сделать это как

if (bool1 || bool2 || bool3 || bool4 || bool5)

Если вам нужно больше одного (2 и выше) логических значений, равных true, вы можете попробовать

int counter = 0;
if (bool1) counter++;
if (bool2) counter++;
if (bool3) counter++;
if (bool4) counter++;
if (bool5) counter++;
if (counter >= 2) //More than 1 boolean is true

Ответ 9

Короче и уродливее версии Vilx:

if (((a||b||c)&&(d||e))||((a||d)&&(b||c||e))||(b&&c)) {}

Ответ 10

из верхней части головы, быстрый подход для этого конкретного примера; вы можете преобразовать bool в int (0 или 1). затем пройдите через термометр и добавьте их вверх. если результат >= 2, вы можете выполнить свою функцию.

Ответ 11

Хотя мне нравится LINQ, в нем есть некоторые дыры, как эта проблема.

Выполнение счета в целом нормально, но может стать проблемой, когда элементы, на которые выполняется подсчет времени, вычисляют/извлекают.

Метод расширения Any() прекрасен, если вы просто хотите проверить его, но если вы хотите проверить, по крайней мере, нет встроенной функции, которая будет делать это и быть ленивой.

В конце я написал функцию для возврата true, если в списке есть хотя бы определенное количество элементов.

public static bool AtLeast<T>(this IEnumerable<T> source, int number)
{
    if (source == null)
        throw new ArgumentNullException("source");

    int count = 0;
    using (IEnumerator<T> data = source.GetEnumerator())
        while (count < number && data.MoveNext())
        {
            count++;
        }
    return count == number;
}

Для использования:

var query = bools.Where(b => b).AtLeast(2);

Это полезно, если вам не нужно оценивать все элементы перед возвратом результата.

[Plug] Мой проект, NExtension содержит AtLeast, AtMost и переопределения, которые позволяют смешивать в предикат с AtLeast/Большая проверка. [/Штекер]

Ответ 12

Приведение к ints и суммированию должно работать, но это немного уродливо и на некоторых языках может быть невозможно.

Как насчет чего-то вроде

int count = (bool1? 1:0) + (bool2? 1:0) + (bool3? 1:0) + (bool4? 1:0) + (bool5? 1:0);

Или, если вам небезразлично пространство, вы можете просто прекомпопировать таблицу истинности и использовать bools в качестве индексов:

if (morethanone[bool1][bool2][bool3][bool4][bool5]) {
 ... do something ...
}

Ответ 13

Я бы сделал что-то вроде этого, используя аргумент params.

        public void YourFunction()
        {
            if(AtLeast2AreTrue(b1, b2, b3, b4, b5))
            {
                // do stuff
            }
        }

        private bool AtLeast2AreTrue(params bool[] values)
        {
            int trueCount = 0;
            for(int index = 0; index < values.Length || trueCount >= 2; index++)
            {
                if(values[index])
                    trueCount++;
            }

            return trueCount > 2;

        }

Ответ 14

Не совсем красиво... но вот еще один способ сделать это:

if (
    (a && (b || c || d || e)) ||
    (b && (c || d || e)) ||
    (c && (d || e)) ||
    (d && e)
)

Ответ 15

if (NumberOfTrue(new List<bool> { bool1, bool2, bool3, bool4 }) >= 2)
{
    // do stuff
}

int NumberOfTrue(IEnumerable<bool> bools)
{
    return bools.Count(b => b);
}

Ответ 16

У меня теперь намного лучший и очень короткий!

bool[] bools = { b1, b2, b3, b4, b5 };
if (bools.Where(x => x).Count() > 1)
{
   //do stuff
}

Ответ 17

В большинстве языков true эквивалентно ненулевому значению, а false - нулю. У меня нет точного синтаксиса для вас, но в псевдокоде, как насчет:

if ((bool1 * 1) + (bool2 * 1) + (bool3 * 1) > 2)
{
    //statements here
}

Ответ 18

if ((b1.CompareTo(false) + b2.CompareTo(false) + b3.CompareTo(false) +...) > 1)

//Более одного из них являются истинными

...

еще

...

Ответ 19

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

0x 0000 0000 
0x 0000 0001
0x 0000 0010
0x 0000 0100
0x 0000 1000
0x 0001 0000

Это дает вам шесть значений для поиска, помещает их в таблицу поиска и, если она там отсутствует, у вас есть свой ответ.

Это дает вам простой ответ.

   public static boolean moreThan1BitSet(int b)
   {
      final short multiBitLookup[] = { 
            1, 1, 1, 0, 1, 0, 0, 0,
            1, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            1, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0
      };
      if(multiBitLookup[b] == 1)
         return false;
      return true;
   }

Это не масштабируется задолго до 8 бит, но у вас только пять.

Ответ 20

Вы упомянули

Один интересный вариант - хранить булевы в байте, выполните правую смену и сравните с исходным байтом. Что-то вроде if (myByte && (myByte >> 1))

Я не думаю, что это выражение даст вам результат, который вы хотите (по крайней мере, используя семантику C, поскольку выражение недействительно С#):

Если (myByte == 0x08), тогда выражение вернет true, даже если установлен только один бит.

Если вы имели в виду "if (myByte & (myByte >> 1))", то если (myByte == 0x0a) выражение вернет false, даже если установлено 2 бита.

Но вот несколько методов подсчета числа бит в слове:

Бит Twiddling Hacks - счетные биты

Вариант, который вы можете рассмотреть, - использовать метод подсчета Kernighan, но выпустите его раньше, так как вам нужно только знать, установлено ли более одного бита:

int moreThanOneBitSet( unsigned int v)
{
    unsigned int c; // c accumulates the total bits set in v

    for (c = 0; v && (c <= 1); c++)
    {
      v &= v - 1; // clear the least significant bit set
    }

    return (c > 1);
}

Конечно, использование таблицы поиска не является плохим вариантом.

Ответ 21

Я хотел дать ответ на шаблонный вариант С++ 11.

template< typename T>
T countBool(T v)
{
    return v;
}

template< typename T, typename... Args>
int countBool(T first, Args... args)
{
    int boolCount = 0;
    if ( first )
        boolCount++;
    boolCount += countBool( args... );
    return boolCount;
}

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

if ( countBool( bool1, bool2, bool3 ) > 1 )
{
  ....
}

Ответ 22

Недавно у меня была такая же проблема, когда у меня было три булевых значения, и мне нужно было проверить, что только одно из них было истинным одновременно. Для этого я использовал оператор xor следующим образом:

bool a = true;
bool b = true;
bool c = false;

if (a || b || c)
{
    if (a ^ b ^ c){
        //Throw Error
    }
}

Этот код выдает ошибку, так как a и b являются истинными.

Для справки: http://www.dotnetperls.com/xor

Я только что нашел оператор xor в С#, если кто-нибудь знает о любых падениях этой стратегии, пожалуйста, дайте мне знать.