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



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



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



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

170   22  

22 ответов:

как о

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

EDIT (чтобы добавить предложение Joel Coehoorn: (в .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  ...

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

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

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

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

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

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

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

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

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

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

работать больше, чтобы сделать его ясным, а не умным!

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

    return result;
}

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

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

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

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

короче и уродливее, чем версия Vilx-s:

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

Если вы имеете в виду больше или равно одному булеву равно 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

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

хотя мне нравится 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/Most проверкой. [/Plug]

приведение к 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 ...
}

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

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

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

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

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

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

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

Я хотел дать ответ на вариационный шаблон C++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 )
{
  ....
}

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

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

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

Если ((b1.CompareTo (false) + b2.CompareTo (false) + b3.CompareTo (false ) + ...) > 1)

// более одного из них истинны

...

другое

...

Вы упомянули

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

Я не думаю, что выражение даст вам результат, который вы хотите (по крайней мере, используя семантику C, так как выражение не является допустимым 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);
}

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

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

Comments

    Ничего не найдено.