Елегантно визначте, чи є більше логічного значення «істинним»


79

У мене є набір з п’яти булевих значень. Якщо кілька з них відповідають дійсності, я хочу виконати певну функцію. Який найелегантніший спосіб ви можете придумати, що дозволить мені перевірити цю умову в одному твердженні if ()? Мовою перекладу є C #, але мене цікавлять рішення і іншими мовами (якщо ми не говоримо про конкретні вбудовані функції).

Цікавий варіант - зберегти логічні значення в байті, зробити правильний зсув і порівняти з вихідним байтом. Щось на кшталт if(myByte && (myByte >> 1))Але для цього потрібно буде перетворити окремі булеві значення в байт (за допомогою bitArray?), І це здається трохи (призначеним каламбуром) незграбним ... [редагувати] Вибачте, це мало бути if(myByte & (myByte - 1)) [/ редагувати]

Примітка: Це, звичайно, дуже близько до класичної проблеми програмування "підрахунок населення", "додавання вбік" або "вага Хеммінга" - але не зовсім однакове. Мені не потрібно знати, скільки бітів встановлено, лише якщо їх більше одного. Я сподіваюся, що для цього є набагато простіший спосіб.


Як допоможе (myByte && (myByte >> 1))? Якщо myByte == 0x08, тоді встановлюється лише один біт, але вираз має значення true. Якщо ви мали на увазі (myByte & (myByte >> 1)), то це не вдається, коли myByte == 0x0a, наприклад,
Michael Burr

Щось не так з логічним значенням чи?
Мартін Йорк,

1
Логічне або повертає значення True, якщо принаймні одне значення має значення True. OP хоче щось, що повертає True, якщо більше одного значення True.
RobH

@ Michael Burr - Вибачте, це мало бути (myByte & (myByte - 1)) Очищення LSB та побітове виконання І визначає, чи встановлено більше одного біта (false, якщо> 1). Повторіть це для загальної кількості бітів, і ви зможете отримати підрахунок встановлених бітів. Тільки один із способів зробити поп-кол.
Ола Тувессон,

кількість бітів - цікава головоломка: загальнодоступний статичний int BitCount (int x) {return ((x == 0)? 0: ((x <0)? 1: 0) + BitCount (x << = 1)); }
Чарльз Бретана

Відповіді:


91

Як щодо

  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  ...

Щоб бути дійсно витонченим, майте заміну, яка також приймає дію.
Joel Coehoorn

2
Пропозиція - розірвіть цикл, як тільки b> порог.
Вількс

1
шість літерних змінних, що не так із першим прикладом. Я думаю, що це просто, і виконайте роботу.
Кевін

@ Джоел, що ти маєш на увазі під "дією" ??, делегатом методу, який потрібно запустити, якщо умовна істина?
Чарльз Бретана

parms-> params. Крім того, ви можете змінити цю функцію, щоб повернутися раніше.
mackenir

118

Я збирався написати версію Linq, але п’ять людей близько того мене побили. Але мені дуже подобається підхід params, щоб уникнути необхідності вручну створювати масив. Тож я думаю, що найкращий гібрид - це, базуючись на відповіді rp із заміною кузова на очевидний Linqness:

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

Чудово зрозумілий для читання та використання:

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

1
Погоджена, блискуча відповідь. У разі , якщо хто - то шукають рішення , яке відповідає загальним сценарієм, то @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);І так далі ...
Андерс Густафссон,

1
@AndersGustafsson Або простоnew[] { a, b, c, d }.Count(x => x > 0.0)
Даніель Ервікер

19

Настав час обов'язкової відповіді LINQ, яка насправді досить акуратна.

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

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

16

Я б просто кинув їх на інти і суму.

Якщо ви не в надзвичайно щільному внутрішньому циклі, це має перевагу, що його легко зрозуміти.


2
Хтось повинен додати версію linq цього: myBools.Cast <int> () .Sum ()!
Дженніфер

2
@Jennifer трохи запізнився з цим (!), Але, на жаль Cast<int>().Sum(), викине виняток із послідовності булів. Хоча ви можете виконати актори bool-> int, ви не можете актори bool-> object-> int, саме це відбувається за лаштунками тут.
Даніель Ервікер

6

Я б написав функцію для отримання будь-якої кількості булевих значень. Це поверне кількість тих істинних значень. Перевірте результат на кількість значень, які ви повинні бути позитивними, щоб щось зробити.

Працюйте більше, щоб зробити це зрозумілим, а не розумним!

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

    return result;
}

6

якщо ви маєте на увазі більше або дорівнює одному логічному значення, рівному істині, ви можете зробити це як

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

Якби мільйони були замість лише 5, ви могли б уникнути Count () і зробити це замість цього ...

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

Запропонована більш стисла версія цього: return booleans.Where(b => b).Skip(1).Any()Це також узагальнює будь-який випадок, коли ми хочемо знати, чи є більше ніж N членів, які відповідають певним умовам.
Стів

5

Якщо ваші прапори упаковані в одне слово, тоді рішення Майкла Берра спрацює. Однак цикл не є необхідним:

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

4

Коротший і потворніший, ніж версія Vilx-s:

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

1
Так, це жахливо, але це працює (я перевірив усі комбінації).
дещо

2

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


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. [/ Вилка]


1

Кастинг на 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 ...
}

1
так, я теж. Лол. в деяких випадках попередні обчислення допомагають продуктивності - хоча я не думаю, що це один з них :-)
frankodwyer

1

Я б зробив щось подібне, використовуючи аргумент 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 на: return trueCount> = 2
frankodwyer

крім того, я не впевнений, що це особливо відповідає визначенню "елегантний"
stephenbayer

@frankodwyer Це правда, я змінив це. Я думав, що це може бути читабельніше з істинним, хибним, але, переглядаючи це знову, інший, безумовно, кращий.
Джон Сонмез,

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

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

1

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

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

А як щодо (a && (b || c || d || e)) || (b & (c || d || 3)) || (c && (d || e)) || (d && e)?
stephenbayer

1
Приємно ... Я можу використати це як запитання для співбесіди ... "Що це робить?" щоб виміряти чистий інтелект, і "Що ви думаєте про це як про рішення?" відсіяти всіх, кому це сподобалось.
ChrisA

Оце Так! Розмова про грубу силу та криваве незнання! :-)
RobH

1

У мене зараз набагато кращий і дуже короткий!

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

2
Декілька людей вже писали, що один, хоча і з предикатом, переданим Графу, замість того, щоб потребувати Де.
Даніель Ервікер

1

Я хотів дати варіатичну відповідь на шаблон 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 )
{
  ....
}

0

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

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

Недійсне в жодній мові, де true відповідає будь-якому ненульовому значенню. (Приклад працює лише в тому випадку, якщо 1 завжди використовується як істина.)
RobH

0

Якщо у вас є лише п’ять різних значень, ви можете легко виконати тест, упакувавши біти в шорт або int і перевіривши, чи є це будь-яка з нульових або однобітових відповідей. Єдиними недійсними номерами, які ви могли б отримати, були б ..

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

Це дає вам шість значень для пошуку, помістіть їх у таблицю пошуку, і якщо їх там немає, у вас є відповідь.

Це дає вам просту відповідь.

   загальнодоступний статичний boolean moreThan1BitSet (int b)
   {
      остаточний короткий 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
      };
      якщо (multiBitLookup [b] == 1)
         повернути false;
      повернути істинно;
   }

Це не значно перевищує 8 біт, але у вас є лише п’ять.



0

Ви згадали

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

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

Якщо (myByte == 0x08), тоді вираз поверне true, хоча існує лише один біт.

Якщо ви мали на увазі " if (myByte & (myByte >> 1))", тоді якщо (myByte == 0x0a)вираз поверне false, навіть якщо встановлено 2 біти.

Але ось кілька методів підрахунку кількості бітів у слові:

Bit Twiddling Hacks - підрахунок біт

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

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

Нещодавно у мене була та сама проблема, де у мене було три логічні значення, які мені потрібно було перевірити, чи одночасно відповідало лише 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 #, якщо хтось знає про якісь падіння цієї стратегії, будь ласка, повідомте мене.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.