Проверьте, верно ли хотя бы два из трех логических значений

579

Интервьюер недавно задал мне этот вопрос: учитывая три логические переменные, a, b и c, вернуть true, если хотя бы две из трех верны.

Мое решение следующее:

boolean atLeastTwo(boolean a, boolean b, boolean c) {
    if ((a && b) || (b && c) || (a && c)) {
        return true;
    }
    else{
        return false;
    }
}

Он сказал, что это может быть улучшено дальше, но как?

user282886
источник
170
Встроенный ответ.
Finglas
45
Звучит как интервью "у кого самый высокий IQ". Я бы потерпел неудачу.
Крис Датроу
79
atLeastTwo(iWantYou, iNeedYou, imEverGonnaLoveYou)
Эндрю Гримм
92
Почему люди поднимают самые тривиальные вопросы?
BlueRaja - Дэнни Пфлугхофт
46
Вопросы, которые являются общими и легкими для понимания, получают много голосов. Вопросы, которые очень специфичны, а технические нет.
Джей

Ответы:

820

Вместо того чтобы писать:

if (someExpression) {
    return true;
} else {
    return false;
}

Написать:

return someExpression;

Что касается самого выражения, примерно так:

boolean atLeastTwo(boolean a, boolean b, boolean c) {
    return a ? (b || c) : (b && c);
}

или это (что вам легче понять):

boolean atLeastTwo(boolean a, boolean b, boolean c) {
    return a && (b || c) || (b && c);
}

Он тестирует aи bровно один раз, и cсамое большее один раз.

Ссылки

полигеномасляных смазок
источник
144
+1: прекрасное решение головоломки, но, надеюсь, мы не увидим ничего подобного в реальном мире :)
Джульетта
124
@Juliet: я не знаю, я думаю, что если бы это было реальным требованием (с реальными именами переменных), оно бы читалось довольно хорошо. Подумайте return hasGoodAttendance ? (passedCoursework || passed Exam) : (passedCoursework && passedExam), это выглядит хорошо для меня.
Анджей Дойл
18
Я не думаю, что это выглядит плохо , но если понимать, что требование в области «как минимум два», я думаю, что его будет легче читать atLeastTwo(hasgoodAttendance, passedCoursework, passedExam). Идея «по крайней мере 2 bools являются правдой» является достаточно общей, чтобы заслужить свою собственную функцию.
Кен
17
@Lese: Запрашивать самый микрооптимизированный код для личных интервью нецелесообразно и, смею сказать, бесполезно. Микрооптимизация, основанная на необходимости, руководствуется результатами профилирования во время выполнения, а не человеческими инстинктами (которые, как известно, ужасны). Вы, конечно, можете спросить у респондентов процесс, с помощью которого вы бы оптимизировали это дальше; это важнее, чем сам результат.
полигенные смазки
17
Тернарный оператор - это распространенная идиома, которую вы должны уметь читать. Если вы не можете прочитать это, вы должны изучить его, пока не сможете. Использование троичного оператора - это не то, что я считаю «умным» в уничижительном смысле. Но да, я бы поставил это как тело вызова метода, если вы обычно используете логику «как минимум два».
Стивен П
494

Просто ради использования XOR для решения относительно простой задачи ...

return a ^ b ? c : a
Тим Стоун
источник
160
Вау, классное решение. Но для меня его перевернутая версия легче понять: a == b? a: c
Rotsor
5
а ^ б? с: а ^ б? с: а ^ б? c: a
alexanderpas
4
Да, у XOR такая плохая пресса, и у тебя редко появляется возможность ее использовать.
EightyOne Unite
19
@ Stimul8d, может быть, потому что для логических значений он такой же, как! =, Но менее читабельный? Понимание этого было для меня моментом эврики ...
Тихон Джелвис
2
Я предпочитаю чисто двоичную форму: return ((a ^ b) & c) | (а и б). Он не содержит ветвей (быстрее) и легко читается: (a или b соответствует true и c соответствует true) или (a и b оба имеют значение true). Обратите внимание, что (a | b) и (a ^ b) оба работают с этой формулой.
Фланглет
217

Почему бы не реализовать это буквально? :)

(a?1:0)+(b?1:0)+(c?1:0) >= 2

В C вы можете просто написать a+b+c >= 2(или !!a+!!b+!!c >= 2быть в безопасности).

В ответ на сравнение байт-кода Java TofuBeer , вот простой тест производительности:

class Main
{
    static boolean majorityDEAD(boolean a,boolean b,boolean c)
    {
        return a;
    }

    static boolean majority1(boolean a,boolean b,boolean c)
    {
        return a&&b || b&&c || a&&c;
    }

    static boolean majority2(boolean a,boolean b,boolean c)
    {
        return a ? b||c : b&&c;
    }

    static boolean majority3(boolean a,boolean b,boolean c)
    {
        return a&b | b&c | c&a;
    }

    static boolean majority4(boolean a,boolean b,boolean c)
    {
        return (a?1:0)+(b?1:0)+(c?1:0) >= 2;
    }

    static int loop1(boolean[] data, int i, int sz1, int sz2)
    {
        int sum = 0;
        for(int j=i;j<i+sz1;j++)
        {
            for(int k=j;k<j+sz2;k++)
            {
                sum += majority1(data[i], data[j], data[k])?1:0; 
                sum += majority1(data[i], data[k], data[j])?1:0; 
                sum += majority1(data[j], data[k], data[i])?1:0; 
                sum += majority1(data[j], data[i], data[k])?1:0; 
                sum += majority1(data[k], data[i], data[j])?1:0; 
                sum += majority1(data[k], data[j], data[i])?1:0; 
            }
        }
        return sum;
    }

    static int loop2(boolean[] data, int i, int sz1, int sz2)
    {
        int sum = 0;
        for(int j=i;j<i+sz1;j++)
        {
            for(int k=j;k<j+sz2;k++)
            {
                sum += majority2(data[i], data[j], data[k])?1:0; 
                sum += majority2(data[i], data[k], data[j])?1:0; 
                sum += majority2(data[j], data[k], data[i])?1:0; 
                sum += majority2(data[j], data[i], data[k])?1:0; 
                sum += majority2(data[k], data[i], data[j])?1:0; 
                sum += majority2(data[k], data[j], data[i])?1:0; 
            }
        }
        return sum;
    }

    static int loop3(boolean[] data, int i, int sz1, int sz2)
    {
        int sum = 0;
        for(int j=i;j<i+sz1;j++)
        {
            for(int k=j;k<j+sz2;k++)
            {
                sum += majority3(data[i], data[j], data[k])?1:0; 
                sum += majority3(data[i], data[k], data[j])?1:0; 
                sum += majority3(data[j], data[k], data[i])?1:0; 
                sum += majority3(data[j], data[i], data[k])?1:0; 
                sum += majority3(data[k], data[i], data[j])?1:0; 
                sum += majority3(data[k], data[j], data[i])?1:0; 
            }
        }
        return sum;
    }

    static int loop4(boolean[] data, int i, int sz1, int sz2)
    {
        int sum = 0;
        for(int j=i;j<i+sz1;j++)
        {
            for(int k=j;k<j+sz2;k++)
            {
                sum += majority4(data[i], data[j], data[k])?1:0; 
                sum += majority4(data[i], data[k], data[j])?1:0; 
                sum += majority4(data[j], data[k], data[i])?1:0; 
                sum += majority4(data[j], data[i], data[k])?1:0; 
                sum += majority4(data[k], data[i], data[j])?1:0; 
                sum += majority4(data[k], data[j], data[i])?1:0; 
            }
        }
        return sum;
    }

    static int loopDEAD(boolean[] data, int i, int sz1, int sz2)
    {
        int sum = 0;
        for(int j=i;j<i+sz1;j++)
        {
            for(int k=j;k<j+sz2;k++)
            {
                sum += majorityDEAD(data[i], data[j], data[k])?1:0; 
                sum += majorityDEAD(data[i], data[k], data[j])?1:0; 
                sum += majorityDEAD(data[j], data[k], data[i])?1:0; 
                sum += majorityDEAD(data[j], data[i], data[k])?1:0; 
                sum += majorityDEAD(data[k], data[i], data[j])?1:0; 
                sum += majorityDEAD(data[k], data[j], data[i])?1:0; 
            }
        }
        return sum;
    }

    static void work()
    {
        boolean [] data = new boolean [10000];
        java.util.Random r = new java.util.Random(0);
        for(int i=0;i<data.length;i++)
            data[i] = r.nextInt(2) > 0;
        long t0,t1,t2,t3,t4,tDEAD;
        int sz1 = 100;
        int sz2 = 100;
        int sum = 0;

        t0 = System.currentTimeMillis();

        for(int i=0;i<data.length-sz1-sz2;i++)
            sum += loop1(data, i, sz1, sz2);

        t1 = System.currentTimeMillis();

        for(int i=0;i<data.length-sz1-sz2;i++)
            sum += loop2(data, i, sz1, sz2);

        t2 = System.currentTimeMillis();

        for(int i=0;i<data.length-sz1-sz2;i++)
            sum += loop3(data, i, sz1, sz2);

        t3 = System.currentTimeMillis();

        for(int i=0;i<data.length-sz1-sz2;i++)
            sum += loop4(data, i, sz1, sz2);

        t4 = System.currentTimeMillis();

        for(int i=0;i<data.length-sz1-sz2;i++)
            sum += loopDEAD(data, i, sz1, sz2);

        tDEAD = System.currentTimeMillis();

        System.out.println("a&&b || b&&c || a&&c : " + (t1-t0) + " ms");
        System.out.println("   a ? b||c : b&&c   : " + (t2-t1) + " ms");
        System.out.println("   a&b | b&c | c&a   : " + (t3-t2) + " ms");
        System.out.println("   a + b + c >= 2    : " + (t4-t3) + " ms");
        System.out.println("       DEAD          : " + (tDEAD-t4) + " ms");
        System.out.println("sum: "+sum);
    }

    public static void main(String[] args) throws InterruptedException
    {
        while(true)
        {
            work();
            Thread.sleep(1000);
        }
    }
}

На моем компьютере будет напечатано следующее (работает Ubuntu на Intel Core 2 + Sun java 1.6.0_15-b03 с виртуальной машиной сервера HotSpot (14.1-b02, смешанный режим)):

Первая и вторая итерации:

a&&b || b&&c || a&&c : 1740 ms
   a ? b||c : b&&c   : 1690 ms
   a&b | b&c | c&a   : 835 ms
   a + b + c >= 2    : 348 ms
       DEAD          : 169 ms
sum: 1472612418

Более поздние итерации:

a&&b || b&&c || a&&c : 1638 ms
   a ? b||c : b&&c   : 1612 ms
   a&b | b&c | c&a   : 779 ms
   a + b + c >= 2    : 905 ms
       DEAD          : 221 ms

Интересно, что может сделать Java VM, которая со временем снижает производительность для (a + b + c> = 2) случая.

И вот что происходит, если я запускаю Java с -clientкоммутатором VM:

a&&b || b&&c || a&&c : 4034 ms
   a ? b||c : b&&c   : 2215 ms
   a&b | b&c | c&a   : 1347 ms
   a + b + c >= 2    : 6589 ms
       DEAD          : 1016 ms

Тайна ...

И если я запускаю его в GNU Java Interpreter , он работает почти в 100 раз медленнее, но тогда a&&b || b&&c || a&&cвыигрывает версия.

Результаты от Tofubeer с последним кодом под управлением OS X:

a&&b || b&&c || a&&c : 1358 ms
   a ? b||c : b&&c   : 1187 ms
   a&b | b&c | c&a   : 410 ms
   a + b + c >= 2    : 602 ms
       DEAD          : 161 ms

Результаты Пола Уогланда с Mac Java 1.6.0_26-b03-383-11A511

a&&b || b&&c || a&&c : 394 ms 
   a ? b||c : b&&c   : 435 ms
   a&b | b&c | c&a   : 420 ms
   a + b + c >= 2    : 640 ms
   a ^ b ? c : a     : 571 ms
   a != b ? c : a    : 487 ms
       DEAD          : 170 ms
Ротсор
источник
4
a+b+c >= 2: это не работает с негативами, верно? Возможно, вам придется сделать !!aэто, я не уверен.
полигенасмазочные материалы
8
<ы> -1. Вы никогда не должны делать это для C. Вы не знаете, каково значение true (это может быть так же легко, как -1). </ S> На самом деле, я предполагаю, что C99 включает в свой стандарт, что true определяется как 1. Но Я все еще не сделал бы это.
Марк Питерс
1
Возможно ли это, если ваш ввод является результатом логических операций? И возможно ли это для типа "bool" в C ++?
Rotsor
2
@Rotsor: Никто не говорил, что входные данные должны быть результатом логических операций. Даже без негативов вы играете с огнем, как будто вы определяете его как 2, ваше состояние будет иметь ложные срабатывания. Но мне это безразлично, так как мне не нравится идея смешивания логических выражений в арифметику. Ваше Java-решение очевидно в том, что оно не основано на нюансных преобразованиях из логического в целочисленный тип.
Марк Питерс
7
Будьте осторожны с микробенчмарками
BalusC
143

Этот тип вопросов можно решить с помощью карты Карно :

      | C | !C
------|---|----
 A  B | 1 | 1 
 A !B | 1 | 0
!A !B | 0 | 0
!A  B | 1 | 0

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

(C && (A || B)) || (A && B)  <---- first row
       ^
       |
   first column without third case
Джек
источник
10
@Justin, карта Карно уменьшила количество логических операций с 3 AND и 2 OR до 2 AND и 2 OR. @ Джек, Спасибо, что напомнили мне о существовании Карты Карно.
Тахи
14
+1 за что-то новое. Моя следующая функциональная спецификация будет включать K-карту, нужна ли она ей или ей.
Джастин Р.
2
Возможно, плохая читабельность может быть компенсирована (1) соответствующей таблицей в комментарии и (2) соответствующим модульным тестом ... +1 за что-то полезное, усвоенное в школе.
Моала
140

Читабельность должна быть целью. Кто-то, кто читает код, должен немедленно понять ваше намерение. Так вот мое решение.

int howManyBooleansAreTrue =
      (a ? 1 : 0)
    + (b ? 1 : 0)
    + (c ? 1 : 0);

return howManyBooleansAreTrue >= 2;
данатель
источник
21
Я согласен с предпосылкой, но (a && b) || (b && c) || (a && c) гораздо более читабельно, чем ваше решение ИМХО.
Адриан Григоре
62
Хм, теперь мне нужна версия "два из четырех логических" ... версия danatel теперь намного проще.
Арафангион
6
Или в Scala:Seq(true, true, false).map(if (_) 1 else 0).sum >= 2
ретроним
5
@ ретроним: Хм, нет. Способ Java прекрасно работает в Scala, он более читабелен и эффективен.
Сеун Осева
134
return (a==b) ? a : c;

Объяснение:

Если a==b, тогда оба являются истинными или оба являются ложными. Если оба имеют значение true, мы нашли два наших истинных логических значения и можем вернуть true (путем возврата a). Если оба являются ложными, не может быть двух истинных логических значений, даже если cэто правда, поэтому мы возвращаем ложное (возвращая a). Это (a==b) ? aчасть. Как насчет : c? Что ж, если a==bfalse, то в точности одно из aили bдолжно быть true, поэтому мы нашли первое истинное логическое значение, и единственное, что остается важным, это если cтакже true, поэтому мы возвращаемся cв качестве ответа.

Boann
источник
8
с никогда даже не проверял ... гениально!
CurtainDog
Использует транзитивную связь равенства и тот факт, что логическое значение является истинным или ложным +1
Кристоф Русси
3
Так элегантно! Я должен был проверить ручку и бумагу, чтобы поверить в это :) Слава вам, сэр!
Адриан
3
Я думаю об этом как «если aи bсогласны, они имеют большинство голосов, так что идите с тем, что есть, иначе они не согласны, так же cкак и решающий голос»
Бен Миллвуд
34

Вам не нужно использовать формы короткого замыкания операторов.

return (a & b) | (b & c) | (c & a);

Он выполняет то же количество логических операций, что и ваша версия, но совершенно без ветвей.

Лунная тень
источник
11
Почему вы хотите заставить 5 оценок, когда я мог сделать? На самом деле он не выполняет столько же логических операций по правде. На самом деле, это всегда будет выполнять больше.
Марк Питерс
2
Я думаю, что смешивать двоичную арифметику и логическую арифметику - плохая идея. Это как винт в стене с помощью гаечного ключа. Хуже всего то, что они имеют разную семантику.
Питер Тиллеманс
12
@Mark - это может быть быстрее ... в зависимости от влияния неправильного прогноза ветвления на конвейер ЦП. Однако лучше всего оставить такие микрооптимизации JIT-компилятору.
Стивен С
4
Можно сделать что-то подобное в Java (или любом другом языке) ... с несколькими оговорками: 1) это должно быть быстрее (в этом случае, я думаю, см. Мой второй ответ) 2) предпочтительнее значительно быстрее (не уверен, если это так), 3) наиболее важно документировано, поскольку это «странно». Пока это служит цели и задокументировано, можно «нарушать правила», когда это имеет смысл.
TofuBeer
11
@Peter Tillemans Нет смешивания с бинарными операторами, в Java это булевы операторы.
звездный синий
27

Вот общий подход, основанный на тестировании. Не настолько «эффективный», как большинство предлагаемых решений, но понятный, проверенный, работающий и обобщенный.

public class CountBooleansTest extends TestCase {
    public void testThreeFalse() throws Exception {
        assertFalse(atLeastTwoOutOfThree(false, false, false));
    }

    public void testThreeTrue() throws Exception {
        assertTrue(atLeastTwoOutOfThree(true, true, true));
    }

    public void testOnes() throws Exception {
        assertFalse(atLeastTwoOutOfThree(true, false, false));
        assertFalse(atLeastTwoOutOfThree(false, true, false));
        assertFalse(atLeastTwoOutOfThree(false, false, true));
    }

    public void testTwos() throws Exception {
        assertTrue(atLeastTwoOutOfThree(false, true, true));
        assertTrue(atLeastTwoOutOfThree(true, false, true));
        assertTrue(atLeastTwoOutOfThree(true, true, false));
    }

    private static boolean atLeastTwoOutOfThree(boolean b, boolean c, boolean d) {
        return countBooleans(b, c, d) >= 2;
    }

    private static int countBooleans(boolean... bs) {
        int count = 0;
        for (boolean b : bs)
            if (b)
                count++;
        return count;
    }
}
Карл Манастер
источник
8
Вау, я никогда не видел полностью проверенный метод, прежде чем увидеть этот.
Rotsor
51
Лично я считаю этот код ужасным по многим причинам. Я не собираюсь понижать голос, но если бы я когда-либо увидел это в рабочем коде, я бы проклял. Чрезвычайно простая логическая операция не должна быть такой сложной.
CaptainCasey
10
Мне было бы очень интересно узнать ваши причины, @CaptainCasey. Я думаю, что это довольно хороший код. Есть хорошая обобщенная функция, которую легко понять, легко проверить, и специальная функция, которая использует ее, также легко понять и проверить. В реальном мире я бы сделал их публичными и поместил их в другой класс; кроме этого - я бы с радостью запустил этот код в производство. О, да, я бы переименовал countBooleans () в countTrue ().
Карл Манастер
5
если речь не идет о производительности, это решение выглядит для меня почти идеально: очень легко читаемое и расширяемое. Именно для этого и создаются var-args.
Атаманроман
7
Что, черт возьми, люди? Это понятный и хорошо протестированный код, и единственная причина, по которой он выглядит много, состоит в том, что он включает в себя тесты. A +++, снова буду голосовать.
Кристофер Хаммарстрем
24

Суммировать. Это называется булевой алгеброй по причине:

  0 x 0 = 0
  1 x 0 = 0
  1 x 1 = 1

  0 + 0 = 0
  1 + 0 = 1
  1 + 1 = 0 (+ carry)

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

Чтобы ответить на ваш вопрос:

return (a + b + c) >= 2
Memet
источник
2
Это самое элегантное решение, на мой взгляд.
Торбьерн Кристофферсен
9
Ошибка новичка, хотя, логическое значение НЕ 0, что не означает его всегда 1.
tomdemuyt
13
За исключением того, что тег на посте говорит «Java», и вы не можете написать «a + b + c», когда они определены как булевы в Java.
Джей
Чтобы работать на Java, это должно быть return ((a?1:0) + (b?1:0) + (c?1:0)) >= 2.
Дэвид Р. Триббл
Да, я проголосовал за это, потому что я думал, что это вопрос C ++ ... почему я читаю вопросы Java? : /
Карло Вуд
15
boolean atLeastTwo(boolean a, boolean b, boolean c) 
{
  return ((a && b) || (b && c) || (a && c));
}
мал
источник
15

Это действительно зависит от того, что вы подразумеваете под «улучшенным»:

Яснее?

boolean twoOrMoreAreTrue(boolean a, boolean b, boolean c)
{
    return (a && b) || (a && c) || (b && c);
}

Terser?

boolean moreThanTwo(boolean a, boolean b, boolean c)
{
    return a == b ? a : c;
}

Более общий?

boolean moreThanXTrue(int x, boolean[] bs)
{
    int count = 0;

    for(boolean b : bs)
    {
        count += b ? 1 : 0;

        if(count > x) return true;
    }

    return false;
}

Более масштабируемый?

boolean moreThanXTrue(int x, boolean[] bs)
{
    int count = 0;

    for(int i < 0; i < bs.length; i++)
    {
        count += bs[i] ? 1 : 0;

        if(count > x) return true;

        int needed = x - count;
        int remaining = bs.length - i;

        if(needed >= remaining) return false;
    }

    return false;
}

Быстрее?

// Only profiling can answer this.

Какой из них «улучшен», сильно зависит от ситуации.

kerkeslager
источник
14

Вот еще одна реализация, использующая карту / уменьшить. Это хорошо масштабируется до миллиардов логических величин © в распределенной среде. Использование MongoDB:

Создание базы данных valuesлогических значений:

db.values.insert({value: true});
db.values.insert({value: false});
db.values.insert({value: true});

Создавая карту, уменьшите функции:

Редактировать : мне нравится ответ CurtainDog о применении map / lower для общих списков, так что здесь идет функция карты, которая принимает обратный вызов, который определяет, следует ли считать значение или нет.

var mapper = function(shouldInclude) {
    return function() {
        emit(null, shouldInclude(this) ? 1 : 0);
    };
}

var reducer = function(key, values) {
    var sum = 0;
    for(var i = 0; i < values.length; i++) {
        sum += values[i];
    }
    return sum;
}

Запуск карты / уменьшить:

var result = db.values.mapReduce(mapper(isTrue), reducer).result;

containsMinimum(2, result); // true
containsMinimum(1, result); // false


function isTrue(object) {
    return object.value == true;
}

function containsMinimum(count, resultDoc) {
    var record = db[resultDoc].find().next();
    return record.value >= count;
}
Анураг
источник
@Anurag: насколько бы я ни любил M / R и информацию, которую Google недавно дал ему (даже если это не единственное настоящее M / R от FP), я бы склонен назвать ерунду! T в вашем ответе. Существуют миллиарды и миллиарды строк кода, выполняющих «вещи» из Real-World [TM], где не используется ни одна строка отображения / сокращения. Кто - то ответить на такой вопрос с этим , безусловно , попадает в моей книге , как: «пытается играть Smartie» . Не говоря уже о том, что большинство интервьюеров не смогли бы определить, пытаетесь ли вы их обмануть или нет, потому что на самом деле они никогда не писали ни одной программы, использующей M / R в своей карьере.
СинтаксисT3rr0r
2
@ Синтаксис - Каждый имеет право на свое мнение. Мой ответ - это еще один подход к проблеме. Конечно, это звучит преувеличено для 3-х логических значений, но это не значит, что я пытаюсь быть здесь умным. Это общий подход к решению проблем, который используют все - разбить проблему на мелкие кусочки. Так работает математическая индукция, так работает большинство рекурсивных алгоритмов, и так люди решают проблемы в целом.
Анураг
13

Принимая ответы (пока) здесь:

public class X
{
    static boolean a(final boolean a, final boolean b, final boolean c)
    {
    return ((a && b) || (b && c) || (a && c));
    }

    static boolean b(final boolean a, final boolean b, final boolean c)
    {
    return a ? (b || c) : (b && c);
    }

    static boolean c(final boolean a, final boolean b, final boolean c)
    {
    return ((a & b) | (b & c) | (c & a));
    }

    static boolean d(final boolean a, final boolean b, final boolean c)
    {
    return ((a?1:0)+(b?1:0)+(c?1:0) >= 2);
    }
}

и запустить их через декомпилятор (javap -c X> results.txt):

Compiled from "X.java"
public class X extends java.lang.Object{
public X();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

static boolean a(boolean, boolean, boolean);
  Code:
   0:   iload_0
   1:   ifeq    8
   4:   iload_1
   5:   ifne    24
   8:   iload_1
   9:   ifeq    16
   12:  iload_2
   13:  ifne    24
   16:  iload_0
   17:  ifeq    28
   20:  iload_2
   21:  ifeq    28
   24:  iconst_1
   25:  goto    29
   28:  iconst_0
   29:  ireturn

static boolean b(boolean, boolean, boolean);
  Code:
   0:   iload_0
   1:   ifeq    20
   4:   iload_1
   5:   ifne    12
   8:   iload_2
   9:   ifeq    16
   12:  iconst_1
   13:  goto    33
   16:  iconst_0
   17:  goto    33
   20:  iload_1
   21:  ifeq    32
   24:  iload_2
   25:  ifeq    32
   28:  iconst_1
   29:  goto    33
   32:  iconst_0
   33:  ireturn

static boolean c(boolean, boolean, boolean);
  Code:
   0:   iload_0
   1:   iload_1
   2:   iand
   3:   iload_1
   4:   iload_2
   5:   iand
   6:   ior
   7:   iload_2
   8:   iload_0
   9:   iand
   10:  ior
   11:  ireturn

static boolean d(boolean, boolean, boolean);
  Code:
   0:   iload_0
   1:   ifeq    8
   4:   iconst_1
   5:   goto    9
   8:   iconst_0
   9:   iload_1
   10:  ifeq    17
   13:  iconst_1
   14:  goto    18
   17:  iconst_0
   18:  iadd
   19:  iload_2
   20:  ifeq    27
   23:  iconst_1
   24:  goto    28
   27:  iconst_0
   28:  iadd
   29:  iconst_2
   30:  if_icmplt   37
   33:  iconst_1
   34:  goto    38
   37:  iconst_0
   38:  ireturn
}

Вы можете видеть, что?: Немного лучше, чем исправленная версия вашего оригинала. Лучшим является тот, который вообще избегает ветвления. Это хорошо с точки зрения меньшего количества инструкций (в большинстве случаев) и лучше для частей ЦП с предсказанием переходов, поскольку неправильное предположение в прогнозе ветвления может привести к остановке ЦП.

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

Чтобы быть на 100% уверенным, вам понадобится выяснить стоимость (в циклах ЦП) для каждой инструкции, которая, к сожалению, недоступна (вам нужно будет посмотреть источник горячей точки, а затем спецификации поставщиков ЦП на время берется за каждую сгенерированную инструкцию).

Смотрите обновленный ответ Rotsor для анализа кода во время выполнения.

оборота тофуБир
источник
5
Вы только смотрите на байт-код. Насколько вам известно, JIT возьмет версию с ветвями в байт-коде и превратит ее в версию без ветвей в нативном коде. Но можно подумать, что чем меньше ветвей в байт-коде, тем лучше.
Дэвид Конрад
13

Еще один пример прямого кода:

int  n = 0;
if (a) n++;
if (b) n++;
if (c) n++;
return (n >= 2);

Это не самый лаконичный код, очевидно.

добавление

Еще одна (немного оптимизированная) версия этого:

int  n = -2;
if (a) n++;
if (b) n++;
if (c) n++;
return (n >= 0);

Это может выполняться немного быстрее, если предположить, что сравнение с 0 будет использовать более быстрый (или, возможно, менее) код, чем сравнение с 2.

Дэвид Р. Триббл
источник
+1 @Loadmaster, извини, но ты не прав! Это самый краткий ответ здесь. (т.е. кратко И четко выражено);)
Эш
@ M.Mimpen: только для объектов класса. Для примитивных типов (как nвыше) любой приличный компилятор скомпилирует каждую ++операцию в одну инструкцию CPU, будь то до или после.
Дэвид Р. Триббл
12

Еще один способ сделать это, но не очень хороший:

return (Boolean.valueOf(a).hashCode() + Boolean.valueOf(b).hashCode() + Boolean.valueOf(c).hashCode()) < 3705);

Значения Booleanхеш-кода фиксированы на 1231 для истины и 1237 для ложных, поэтому могли бы в равной степени использовать<= 3699

barrowc
источник
1
или (a? 1: 0) + (b? 1: 0) + (c? 1: 0)> = 2
Питер Лори
12

Наиболее очевидный набор улучшений:

// There is no point in an else if you already returned.
boolean atLeastTwo(boolean a, boolean b, boolean c) {
    if ((a && b) || (b && c) || (a && c)) {
        return true;
    }
    return false;
}

а потом

// There is no point in an if(true) return true otherwise return false.
boolean atLeastTwo(boolean a, boolean b, boolean c) {
    return ((a && b) || (b && c) || (a && c));
}

Но эти улучшения незначительны.

TofuBeer
источник
10

Мне не нравится троичный ( return a ? (b || c) : (b && c);из верхнего ответа), и я не думаю, что видел, чтобы кто-то упоминал об этом. Это написано так:

boolean atLeastTwo(boolean a, boolean b, boolean c) {
    if (a) {
        return b||c;
    } 
    else {
        return b&&C;
    }
Роман А. Тайчер
источник
8

В Clojure :

(defn at-least [n & bools]
  (>= (count (filter true? bools)) n)

Применение:

(at-least 2 true false true)
Vagif Verdi
источник
2
+1 Отличная универсальная версия показывает силу Лиспов. Спасибо,
Дсмит
6

Я не думаю, что видел это решение еще:

boolean atLeast(int howMany, boolean[] boolValues) {
  // check params for valid values

  int counter = 0;
  for (boolean b : boolValues) {
    if (b) {
      counter++;

      if (counter == howMany) {
        return true;
      }
    }
  }
  return false;
}

Его преимущество в том, что как только он достигает нужного вам числа, он ломается. Таким образом, если это «как минимум 2 из этих 1 000 000 значений истинны», где первые два на самом деле верны, то это должно идти быстрее, чем некоторые из более «нормальных» решений.

Джо Энос
источник
Вероятно, это должно быть так: if (++ counter == howMany) вместо увеличения, а затем проверки отдельно.
Джо Энос
2
Или даже короче: если (b && (++ counter == howMany))
Джо Энос
1
Я бы сделал boolean ... boolValuesтак, что проще позвонить, но все равно принимает массив
Стивен
Я не в курсе своей Java - не знал, что существовало. Какой-то странный синтаксис, но он полезен - время от времени я делаю это в C # (ключевое слово params), и это делает вещи приятнее для вызова. Или я не знаю о Java, но в .NET массивы и все коллекции реализуют IEnumerable <T>, поэтому я, вероятно, использовал бы любой эквивалент Java.
Джо Энос
Как производительность сравнивается с примером 2of3? вернуть? (b || c): (b && c);
Iain Sproat
6

Мы можем преобразовать bools в целые числа и выполнить эту простую проверку:

(int(a) + int(b) + int(c)) >= 2
vine'th
источник
6

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

boolean atLeastTwo(boolean t, boolean f, boolean True) {
    boolean False = True;
    if ((t || f) && (True || False)) 
        return "answer" != "42";
    if (t && f) 
        return !"France".contains("Paris");
    if (False == True) 
        return true == false;
    return Math.random() > 0.5;
}

Если кому-то интересно, работает ли этот код, приведем упрощение с использованием той же логики:

boolean atLeastTwo(boolean a, boolean b, boolean c) {
    if ((a || b) && (c)) 
        return true;
    if (a && b) 
        return true;
    if (true) 
        return false;
    // The last line is a red herring, as it will never be reached:
    return Math.random() > 0.5; 

}

Это может быть сведено к следующему:

return ((a || b) && (c)) || (a && b);

Но теперь это уже не смешно.

Брюс Атта
источник
5
Function ReturnTrueIfTwoIsTrue(bool val1, val2, val3))
{
     return (System.Convert.ToInt16(val1) +
             System.Convert.ToInt16(val2) +
             System.Convert.ToInt16(val3)) > 1;
}

Слишком много способов сделать это ...

Duby
источник
3
Больше похоже на C #. Это должно быть упомянуто как таковое в ответе, так как вопрос ориентирован на Java :)
BalusC
5

Решение переменного тока.

int two(int a, int b, int c) {
  return !a + !b + !c < 2;
}

или вы можете предпочесть:

int two(int a, int b, int c) {
  return !!a + !!b + !!c >= 2;
}
Марк Эдгар
источник
4
return 1 << $a << $b << $c >= 1 << 2;
Кевин
источник
Не видел ответа Сувеги, прежде чем изобразить это, почти то же самое.
Кевин
Это действительно работает? Я предполагаю, что это PHP, но у меня нет доступа к нему, но я просто спрошу вас: что произойдет, если $ a равен 0?
Марк Эдгар
@Mark Это на самом деле не работает, если $ a равно 0. Это был недосмотр. Спасибо что подметил это. :)
Кевин
4

Самый простой способ (IMO), который не смущает и легко читается:

// Three booleans, check if two or more are true

return ( a && ( b || c ) ) || ( b && c );
abelito
источник
Функционально это то же самое. Синтаксически, это облегчает чтение для тех, кто не привык к использованию условного оператора вопросительного знака. Я готов поспорить, что больше людей знают, как использовать операторы AND и OR, чем количество людей, которые знают, как использовать условные операторы с вопросительным знаком. Оригинальный вопрос требует «улучшенного ответа». Принятый ответ упрощает ответ, но поднимает очень интересный вопрос о том, что считается улучшением. Вы программируете для универсальной читабельности или для простоты? Для меня это улучшение по сравнению с принятым ответом :)
abelito
Личные предпочтения. Для меня это намного легче понять, чем этот троичный оператор чище, чем это решение.
Нико
1
Ах да, я видел эту проблему и задавался вопросом, почему никто не упомянул это решение. Если вы записываете логику OP как булеву алгебру, вы получаете A B + A C + B C, которая имеет пять операций. По ассоциативному свойству вы можете написать A * (B + C) + B C, который имеет четыре операции.
Река Вивиан
Это так же, как ответ Джека (19 июня) (C && (A || B)) || (A && B)только что изменил имена * переменных`
user85421
4

Буквальный перевод будет работать на всех основных языках:

return (a ? 1:0) + (b ? 1:0) + (c ? 1:0) >= 2;

Но я бы, вероятно, упростил бы чтение для людей и увеличил бы их до трех, что многие программисты забывают:

boolean testBooleans(Array bools)
{
     int minTrue = ceil(bools.length * .5);
     int trueCount = 0;

     for(int i = 0; i < bools.length; i++)
     {
          if(bools[i])
          {
               trueCount++;
          }
     }
     return trueCount >= minTrue;
}
blakecallens
источник
4

В дополнение к отличному сообщению @TofuBeer TofuBeer рассмотрим ответ @pdox pdox:

static boolean five(final boolean a, final boolean b, final boolean c)
{
    return a == b ? a : c;
}

Рассмотрим также его дизассемблированную версию, заданную "javap -c":

static boolean five(boolean, boolean, boolean);
  Code:
    0:    iload_0
    1:    iload_1
    2:    if_icmpne    9
    5:    iload_0
    6:    goto    10
    9:    iload_2
   10:    ireturn

Ответ pdox компилируется в меньший байт-код, чем любой из предыдущих ответов. Как его время выполнения сравнивается с другими?

one                5242 ms
two                6318 ms
three (moonshadow) 3806 ms
four               7192 ms
five  (pdox)       3650 ms

По крайней мере, на моем компьютере ответ pdox немного быстрее, чем ответ @moonshadow moonshadow, что делает pdox самым быстрым в целом (на моем ноутбуке HP / Intel).

Rex Barzee
источник
3

В рубине:

[a, b, c].count { |x| x } >= 2

Который может быть запущен в JRuby на JavaVM. ;-)

user373826
источник
3

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

Самый прямой и естественный способ решить эту проблему - с помощью такого выражения:

a ? (b || c): (b && c)

Поместите это в функцию, если хотите, но это не очень сложно. Решение логически лаконично и эффективно.

оборота вонючий472
источник
3

В С:

return !!a + !!b + !!c >= 2;
Мэтт Столяр
источник
На самом деле, этот ответ неправильный ... он должен быть> = 2, так как вам нужно как минимум два истинных логических значения, а не два.
Пол Уогланд
@Paul Wagland: Спасибо за улов.
Мэтт Джойнер
@ergosys: что я ответил дважды?
Мэтт Столяр