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

StackOverflow https://stackoverflow.com/questions/377990

  •  22-08-2019
  •  | 
  •  

Вопрос

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

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

Другие советы

Я собирался написать версию 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;
}

если вы имеете в виду больше или равно одному логическому значению, равному 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

Если бы там были миллионы , а не всего 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)) {}

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

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

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

Метод расширения Any() подойдет, если вы просто хотите проверить наличие 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);

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

[Подключи] Мой проект, Следующее расширение содержит AtLeast, AtMost и переопределения, которые позволяют смешивать предикат с проверкой AtLeast / Most.[/Штекер]

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

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

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

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

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

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

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

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

if((b1.compareTo( ложь ) + b2.compareTo( ложь ) + b3.compareTo( ложь ) + ...) > 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 #, если кто-нибудь знает о каких-либо недостатках этой стратегии, пожалуйста, дайте мне знать.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top