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

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

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

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


person Ola Tuvesson    schedule 18.12.2008    source источник
comment
Как (myByte && (myByte ›› 1)) поможет? Если myByte == 0x08, то устанавливается только один бит, но выражение оценивается как истинное. Если вы имели в виду (myByte & (myByte ›› 1)), то это не работает, когда myByte == 0x0a, например,   -  person Michael Burr    schedule 18.12.2008
comment
Что-то не так с логическим значением или?   -  person Martin York    schedule 18.12.2008
comment
Boolean или возвращает True, если хотя бы одно значение равно True. OP хочет что-то, что возвращает True, если более чем одно значение равно True.   -  person RobH    schedule 19.12.2008
comment
@ Майкл Берр - Извините, это должно было быть (myByte & (myByte - 1)) Очистка LSB и выполнение побитового И определяет, было ли установлено более одного бита (false, если >1). Повторите это для общего количества битов, и вы сможете получить количество установленных битов. Просто один из способов сделать поп-счет.   -  person Ola Tuvesson    schedule 19.12.2008
comment
количество битов представляет собой интересную головоломку: public static int BitCount(int x) { return ((x == 0) ? 0 : ((x ‹ 0) ? 1 : 0) + BitCount(x ‹‹= 1)); }   -  person Charles Bretana    schedule 14.12.2009


Ответы (21)


Как насчет

  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  ...
person Charles Bretana    schedule 18.12.2008
comment
Чтобы быть действительно гладким, используйте переопределение, которое также принимает действие. - person Joel Coehoorn; 18.12.2008
comment
Предложение - разорвать петлю, как только б › порог. - person Vilx-; 18.12.2008
comment
sixlettervariables, что не так с первым примером. Я думаю, что это просто и сделать свою работу. - person Kevin; 18.12.2008
comment
@ Джоэл, что вы подразумеваете под действием ??, делегатом метода для запуска, если условие истинно? - person Charles Bretana; 18.12.2008
comment
пармс-›парамс. Кроме того, вы можете изменить эту функцию, чтобы вернуться раньше. - person mackenir; 18.12.2008
comment
Я собираюсь наградить этот лучший ответ, так как он находится в оболочке страницы aspx, и первое решение идеально подходит для моих нужд. Второй вариант тоже хорош, но не совсем отвечает на вопрос. - person Ola Tuvesson; 18.12.2008
comment
LFSR: больше одного не один или несколько - person ; 18.12.2008
comment
LFSR, лучше работать с уродливым кодом, чем с довольно неправильным кодом. Ваш код игнорирует пороговое требование, что и было целью этого вопроса. - person Rob Kennedy; 18.12.2008
comment
Чарльз, добавленный цикл while делает всю функцию неправильной. После увеличения значения trueCnt проверьте, достигнут ли порог, а затем прервите его. - person Rob Kennedy; 18.12.2008
comment
@Rob, ты прав... если порог никогда не был достигнут, цикл while заставит foreach снова запуститься с самого начала... Я это исправлю.. - person Charles Bretana; 18.12.2008
comment
Замените «break» на «return true», затем замените окончательный return на «return false». Сохраняет, указывая одно и то же условие дважды. - person Daniel Earwicker; 18.12.2008
comment
@Earwicker, да... Боже, это то, на что похоже совместное программирование?? - person Charles Bretana; 18.12.2008
comment
Ваше расширение для IEnumerable и пример использования немного отличаются. Использование вашего метода расширения дословно означает, что мой вызов bools.ExceedsThreshold<bool>(3) Удалите <T> из метода расширения, чтобы вызов соответствовал примеру: bools.ExceedsThreshold(3) - person JL West; 07.06.2018

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

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

Красиво ясно читать и использовать:

if (Truth(m, n, o, p, q) > 2)
person Daniel Earwicker    schedule 18.12.2008
comment
Согласен, блестящий ответ. На случай, если кто-то ищет решение, подходящее для общего сценария, ответ @DanielEarwicker вдохновил меня на это: static int MatchCount<T>(Predicate<T> match, params T[] objs) { return objs.Count(obj => match(obj)); }. Чтобы подсчитать количество положительных значений: double a, b, c, d; ... MatchCount(x => x > 0.0, a, b, c, d); И так далее... - person Anders Gustafsson; 20.06.2012
comment
@AndersGustafsson Или просто new[] { a, b, c, d }.Count(x => x > 0.0) - person Daniel Earwicker; 21.06.2012

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

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

return bools.Count(b => b == true) > 1;
person Garry Shutler    schedule 18.12.2008

Я бы просто привел их к целым числам и сумме.

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

person recursive    schedule 18.12.2008
comment
Кто-то должен добавить версию этого linq: myBools.Cast‹int›().Sum() ! - person Jennifer; 18.12.2008
comment
@Jennifer немного опоздала с этим (!), но, к сожалению, Cast<int>().Sum() выдаст исключение для последовательности логических значений. Хотя вы можете разыграть bool -> int, вы не можете разыграть bool -> object -> int, что здесь и происходит за кулисами. - person Daniel Earwicker; 21.06.2012

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

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
person faulty    schedule 18.12.2008

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

Работайте усерднее, чтобы было понятно, а не умно!

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

    return result;
}
person rp.    schedule 18.12.2008

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

public static bool MoreThanOne (IEnumerable<bool> booleans)
{
    return booleans.SkipWhile(b => !b).Skip(1).Any(b => b);
}
person Ian Mercer    schedule 06.06.2010
comment
Предлагаемая более краткая версия этого: return booleans.Where(b => b).Skip(1).Any() Это также обобщается на любой случай, когда мы хотим знать, есть ли более N элементов, удовлетворяющих некоторому условию. - person Steve; 06.04.2018

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

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
person finnw    schedule 10.05.2011

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

if (((a||b||c)&&(d||e))||((a||d)&&(b||c||e))||(b&&c)) {}
person some    schedule 18.12.2008
comment
Да, это ужасно, но это работает (я проверил все комбинации). - person some; 19.12.2008

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

person Victor    schedule 18.12.2008

Хотя мне нравится 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);

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

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

person Cameron MacFarland    schedule 19.12.2008

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

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

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

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

if (morethanone[bool1][bool2][bool3][bool4][bool5]) {
 ... do something ...
}
person frankodwyer    schedule 18.12.2008
comment
Да, я тоже. ржу не могу. в некоторых случаях предварительные вычисления помогают производительности, хотя я не думаю, что это один из них :-) - person frankodwyer; 18.12.2008

Я бы сделал что-то подобное, используя аргумент 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;

        }
person John Sonmez    schedule 18.12.2008
comment
вы можете уменьшить это выражение if до: return trueCount ›= 2 - person frankodwyer; 18.12.2008
comment
Кроме того, я не уверен, что это особенно соответствует определению элегантного - person stephenbayer; 18.12.2008
comment
@frankodwyer Это правда, я изменил это. Я думал, что это может быть более читабельно с правдой, ложью, но, глядя на это снова, другое определенно лучше. - person John Sonmez; 18.12.2008

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

if (
    (a && (b || c || d || e)) ||
    (b && (c || d || e)) ||
    (c && (d || e)) ||
    (d && e)
)
person Vilx-    schedule 18.12.2008
comment
Как насчет (a && (b || c || d || e)) || (b & ( c || d || 3)) || (c && (d || e)) || (д&& д) ? - person stephenbayer; 18.12.2008
comment
Хорошо... Я мог бы использовать это как вопрос для интервью... Что это делает? измерить чистый интеллект, и что вы думаете об этом как о решении? чтобы отсеять всех, кому это понравилось. - person ChrisA; 18.12.2008
comment
Вау! Разговор о грубой силе и кровавом невежестве! :-) - person RobH; 19.12.2008

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

bool[] bools = { b1, b2, b3, b4, b5 };
if (bools.Where(x => x).Count() > 1)
{
   //do stuff
}
person John Sonmez    schedule 18.12.2008
comment
Несколько человек уже написали это, хотя и с предикатом, переданным в Count вместо того, чтобы нуждаться в Where. - person Daniel Earwicker; 18.12.2008

Я хотел дать ответ с вариативным шаблоном С++ 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;
}

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

if ( countBool( bool1, bool2, bool3 ) > 1 )
{
  ....
}
person Scott Aron Bloom    schedule 10.06.2017

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

if ((bool1 * 1) + (bool2 * 1) + (bool3 * 1) > 2)
{
    //statements here
}
person Bork Blatt    schedule 18.12.2008
comment
Недействительно на любом языке, где true эквивалентно любому ненулевому значению. (Пример работает только в том случае, если 1 всегда используется для true.) - person RobH; 19.12.2008

Если у вас есть только пять разных значений, вы можете легко выполнить тест, упаковав биты в тип short или 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 бит, но у вас есть только пять.

person Community    schedule 18.12.2008

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

// Более одного из них верны

...

еще

...

person Partha Choudhury    schedule 18.12.2008

Ты упомянул

Один интересный вариант — сохранить логические значения в байте, сделать сдвиг вправо и сравнить с исходным байтом. Что-то вроде if (myByte && (myByte >> 1))

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

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

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

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

Хаски с битами — подсчет битов

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

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

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

person Michael Burr    schedule 19.12.2008

Недавно у меня была такая же проблема, когда у меня было три логических значения, которые мне нужно было проверить, чтобы только одно из них было истинным за раз. Для этого я использовал оператор 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 в С#, если кто-нибудь знает о каких-либо подводных камнях этой стратегии, пожалуйста, дайте мне знать.

person dmoore1181    schedule 24.12.2013