Перевірте, чи принаймні два з трьох булевих значень справжні


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

Він сказав, що це можна вдосконалити і далі, але як?


170
Вбудована заява про повернення.
Фінглас

45
Звучить інтерв'ю "хто-хто-найвищий IQ". Я б провалився.
Кріс Дутроу

79
atLeastTwo(iWantYou, iNeedYou, imEverGonnaLoveYou)
Ендрю Грімм

92
Чому люди підтримують найтривіальніші запитання?
BlueRaja - Danny Pflughoeft

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 буліни справжні" досить загальна, щоб заслужити власну функцію.
Кен

17
@Lese: Запитувати найбільш мікрооптимізований код в інтерв'ю віч-на-віч - недоцільно, і, смію сказати, марно. Мікрооптимізація, керована потребою, керується результатами профілювання часу виконання, а не людськими інстинктами (які, як відомо, страшні). Ви, звичайно, можете запитати респондентів у процесі, за допомогою якого Ви б далі оптимізували це; це важливіше самого результату.
полігенмастильні матеріали

17
Потрійний оператор - це загальна ідіома, яку ви повинні вміти читати. Якщо ви не можете його прочитати, слід вивчити, поки не зможете. Використання потрійного оператора - це не те, що я вважаю "розумним" у зневажливому розумінні. Але так, я ставлю це як основу виклику методу, якщо ви зазвичай використовуєте логіку "щонайменше двох".
Стівен П

493

Просто заради використання XOR для відповіді на відносно пряму проблему ...

return a ^ b ? c : a

159
Нічого собі, круте рішення. Але для мене його перевернуту версію простіше зрозуміти: a == b? a: c
Ротсор

5
a ^ b? c: a ^ b? c: a ^ b? c: a
александрпас

4
Так, .. XOR отримує таку погану пресу, і ви рідко отримуєте шанс скористатися нею.
EightyOne Unite

18
@ Stimul8d, може, тому, що для булів це те саме, що! =, Але менш читабельний? Зрозумівши, що це був для мене момент
еврики

2
Я віддаю перевагу суто бінарній формі: return ((a ^ b) & c) | (a & b). Це без гілок (швидше) і легко читати: (a або b є правдою і c є правдою) або (a і b є істинними). Зауважте, що (a | b) та (a ^ b) обидва працюють з цією формулою.
фланглет

217

Чому б не реалізувати це буквально? :)

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

На мові ви могли просто написати a+b+c >= 2(або !!a+!!b+!!c >= 2бути дуже безпечним).

У відповідь на порівняння TofuBeer байт-коду Java, ось простий тест на продуктивність:

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 Server VM (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 з -clientVM-перемикачем:

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

Результати від Paul Wagland з 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
<s> -1. Ви ніколи не повинні робити цього для C. Ви не знаєте, що таке значення true (воно так само легко може бути -1). </s> Насправді я думаю, що C99 включає в свій стандарт, що true визначається як 1. Але Я все одно не зробив би цього.
Марк Петерс

1
Це можливо, якщо ваш внесок є результатом булевих операцій? І чи можливо це для типу "bool" в С ++?
Ротсор

2
@Rotsor: Ніхто не сказав, що введення має бути результатом булевих операцій. Навіть без негативів ви граєте з вогнем, як якщо б ви визначили це як 2, ваш стан матиме помилкові позитиви. Але мене це не хвилює настільки, як мені не подобається ідея переплутати булеві в арифметичні. Ваше рішення Java зрозуміло, що воно не покладається на нюансові перетворення від булевого до цілого типу.
Марк Петерс

7
Будьте обережні з мікро-орієнтирами: java.sun.com/docs/hotspot/HotSpotFAQ.html#benchmarking_simple
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
@ Джустін, карта Карно зменшила кількість логічних операцій з 3 AND та 2 OR, до 2 AND та 2 OR. @Jack, Дякую, що нагадав мені про існування карти Карна.
Тачі

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) || (&& c) набагато читає, ніж ваше рішення IMHO.
Адріан Григоре

62
Хм, зараз мені потрібна версія "з Чотирьох булевих" версій ... Версія Danatel зараз набагато простіша.
Арафангіон

6
Або в Scala:Seq(true, true, false).map(if (_) 1 else 0).sum >= 2
ретронім

5
@retronym: Хм, ні. Спосіб Java працює чудово у Scala та є більш читабельним та ефективнішим.
Seun Osewa

134
return (a==b) ? a : c;

Пояснення:

Якщо a==b, то обидва вірні або обидва - хибні. Якщо обидва вірні, ми знайшли два справжні булеви, і можемо повернути істину (повернувшись a). Якщо обидва помилкові, не може бути двох справжніх булів, навіть якщо cце правда, тому ми повертаємо хибність (шляхом повернення a). Ось та (a==b) ? aчастина. Про що : c? Що ж, якщо a==bце неправда, то саме одна aчи bповинна бути істинною, тому ми знайшли перший справжній логічний результат, і єдине, що залишається важливим, - cце також правда, тому ми повертаємось cяк відповідь.


8
c ніколи навіть не перевіряється ... геніально!
CurtainDog

Використовує перехідне відношення рівності та той факт, що булевий або правдивий, або хибний +1
Крістоф Руссі

3
Так елегантно! Мені довелося перевірити ручкою та папером, щоб у це повірити :) Кудо вам, пане!
Адріан

3
Я думаю про це як "якщо aі bзгоден, вони мають більшість голосів, тому йдіть з тим, що це є, інакше вони не згодні, так cі вирішальне голосування"
Бен Мілвуд

34

Не потрібно використовувати форми операторів короткого замикання.

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

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


11
Чому ви хочете змусити 5 оцінок, коли 1 міг це зробити? Він дійсно не виконує однакову кількість логічних операцій в правді. Насправді це завжди було б більше.
Марк Петерс

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

12
@Mark - це може бути швидше ... залежно від впливу неправильного прогнозування гілок на конвеєр процесора. Однак найкраще залишити такі мікрооптимізації компілятору JIT.
Стівен С

4
Добре робити щось подібне на Java (або будь-якій іншій мові) ... з парою застережень: 1) це має бути швидшим (у цьому випадку, я вважаю, це є, дивіться мою другу відповідь) 2) краще значно швидше (не впевнений, чи є), 3) найважливіше задокументовано, оскільки це "непарно". Поки це служить цілі, і це задокументовано, добре "порушувати правила", коли це має сенс.
TofuBeer

11
@ Peter Tillemans Немає змішування з бінарними операторами, на Java це булі оператори.
starblue

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
Нічого собі, я ніколи не бачив повністю перевіреного методу, перш ніж бачити цей.
Ротсор

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

2
Це, на мою думку, найелегантніше рішення.
Torbjørn Kristoffersen

9
Помилка
новичка,

13
За винятком того, що тег у публікації пише "Java", і ви не можете написати "a + b + c", коли вони визначені як булеві на Java.
Джей

Щоб працювати на Яві, це мало б бути return ((a?1:0) + (b?1:0) + (c?1:0)) >= 2.
David R Tribble

Дух, я проголосував за це, бо думав, що це питання С ++ ... чому я читаю питання 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);
}

Терсер?

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.

Який із них "покращений" сильно залежить від ситуації.


14

Ось ще одна реалізація, що використовує карту / зменшення. Це добре масштабує мільярди булевих © у розподіленому середовищі. Використання MongoDB:

Створення бази даних valuesбулевих:

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

Створюючи карту, зменшіть функції:

Edit : я як CurtainDog в відповідь про те , Map / Reduce застосовуються до спільними списками, так що тут йде функцію карти , яка приймає функцію зворотного виклику , яка визначає , є чи значення має враховуватися чи ні.

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), я схильний би дзвонити! Існують мільярди та мільярди рядків коду, які виконують "речі" Real-World [TM], де немає жодного рядка карти / зменшення. Хто - то відповісти на таке питання з цим , безумовно , потрапляє в моїй книзі , як: «намагається грати Smartie» . Не кажучи вже про більшість інтерв'юерів, не змогли б сказати, намагаєтесь ви їх робити чи ні, тому що вони ніколи не писали жодної програми, використовуючи M / R у своїй кар'єрі.
SyntaxT3rr0r

2
@Syntax - кожен має право на свою думку. Моя відповідь - це ще один підхід розгляду проблеми. Звичайно, це звучить перебільшено для 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%, вам потрібно буде дізнатись вартість (в циклах процесора) на кожну інструкцію, яка, на жаль, не є доступною (вам доведеться шукати джерело для точки доступу, а потім і специфікації постачальників процесорів на час береться для кожної створеної інструкції).

Дивіться оновлену відповідь Ротсора щодо аналізу часу виконання коду.


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вище) будь-який гідний компілятор буде компілювати кожну ++операцію в одну інструкцію процесора, будь то до або після публікації.
David R Tribble

12

Ще один спосіб зробити це, але не дуже добре:

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

Значення Booleanхеш-коду фіксуються на рівні 1231 для істинного та 1237 для хибного, тому вони могли б однаково використовуватись<= 3699


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

Але ці покращення незначні.


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)

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 з цих 1000 000 значень справжніх», де перші два фактично істинні, то це повинно йти швидше, ніж деякі більш «нормальні» рішення.


Це, мабуть, має бути так: якщо (++ лічильник == howMany) замість збільшення і потім перевірити окремо.
Джо Енос

2
Або ще коротше: якщо (b&& (++ counter == howMany))
Джо Енос

1
Я б зробив boolean ... boolValuesтак, що простіше зателефонувати, але все-таки бере масив
Стівен

Я не в курсі моєї Java - не знав, що існує. Вигляд дивного синтаксису, але це корисно - раз у раз я це робитиму в C # (ключове слово парам), і це робить речі приємнішими для виклику. Або я не знаю про Java, але в .NET, масиви та всі колекції реалізують IEnumerable <T>, тому я, мабуть, використовував би будь-який еквівалент Java.
Джо Енос

Як порівняння продуктивності цього порівняно з прикладом 2of3? повернути a? (b || c): (b&& c);
Iain Sproat

6

Ми можемо перетворити булі в цілі числа і виконати цю просту перевірку:

(int(a) + int(b) + int(c)) >= 2

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

Занадто багато способів зробити це ...


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

Функціонально це те саме. Синтаксично це полегшує читання тим, хто не звик використовувати умовний оператор знака питання. Я готовий зробити ставку більше людей, які знають, як використовувати оператори AND і OR, ніж кількість людей, які знають, як використовувати умовні оператори знака питання. Оригінальне запитання вимагає "вдосконаленої відповіді". Прийнята відповідь спрощує відповідь, але викликає дуже цікаве питання про те, що можна вважати поліпшенням. Ви програмуєте на універсальність для читання чи простоту? Для мене це поліпшення порівняно з прийнятою відповіддю :)
abelito

Особисті переваги. Для мене набагато простіше зрозуміти чистішого потрійного оператора, ніж це рішення.
nico

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

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



3

Він, мабуть, не шукає нічого згорнутого, як, наприклад, побітові оператори порівняння (як правило, не згорнуті, але з булевими, надзвичайно дивно використовувати побітові оператори) або щось дуже кругле, як перетворення на int та підсумовування їх.

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

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

Покладіть його на функцію, якщо хочете, але це не дуже складно. Рішення є логічно стислим та ефективним.


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