Vérifiez si au moins deux booléens sur trois sont vrais


579

Un intervieweur m'a récemment posé cette question: étant donné trois variables booléennes, a, b et c, retourne vrai si au moins deux des trois sont vraies.

Ma solution suit:

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

Il a dit que cela pouvait encore être amélioré, mais comment?


170
Insérez la déclaration de retour.
Finglas

45
Cela ressemble à une interview "qui a le QI le plus élevé". J'échouerais.
Chris Dutrow

79
atLeastTwo(iWantYou, iNeedYou, imEverGonnaLoveYou)
Andrew Grimm

92
Pourquoi les gens votent-ils les questions les plus triviales?
BlueRaja - Danny Pflughoeft

46
Les questions générales et faciles à comprendre suscitent de nombreux votes positifs. Les questions très spécifiques et techniques ne le sont pas.
Jay

Réponses:


820

Plutôt que d'écrire:

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

Écrire:

return someExpression;

Quant à l'expression elle-même, quelque chose comme ceci:

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

ou ceci (selon ce que vous trouvez plus facile à saisir):

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

Il teste aet bexactement une fois, et cau plus une fois.

Références


144
+1: belle solution au puzzle, mais j'espère que nous ne voyons rien de tel dans le monde réel :)
Juliet

124
@Juliet: Je ne sais pas, je pense que si c'était une exigence du monde réel (avec de vrais noms de variables), elle se lirait plutôt bien. Considérez return hasGoodAttendance ? (passedCoursework || passed Exam) : (passedCoursework && passedExam), cela me va bien.
Andrzej Doyle

18
Je ne pense pas que cela semble mauvais , mais si l'exigence dans le domaine est comprise comme "au moins deux", je pense que ce serait plus facile à lire atLeastTwo(hasgoodAttendance, passedCoursework, passedExam). L'idée de "au moins 2 bools sont vrais" est suffisamment générique pour mériter sa propre fonction.
Ken

17
@Lese: Demander le code le plus micro-optimisé dans les entretiens en face à face n'est pas pratique, et j'ose dire, inutile. Les micro-optimisations, lorsqu'elles sont dictées par le besoin, sont guidées par des résultats de profilage d'exécution, et non par des instincts humains (qui sont connus pour être terribles). Vous pouvez certainement demander aux personnes interrogées le processus par lequel vous souhaitez optimiser cela davantage; c'est plus important que le résultat lui-même.
polygenelubricants

17
L'opérateur ternaire est un idiome courant que vous devriez pouvoir lire. Si vous ne pouvez pas le lire, vous devez l'étudier jusqu'à ce que vous le puissiez. L'utilisation de l'opérateur ternaire n'est pas quelque chose que je considère "intelligent" au sens péjoratif. Mais oui, je mettrais cela comme le corps d'un appel de méthode si vous utilisez couramment la logique "au moins deux".
Stephen P

494

Juste pour le plaisir d'utiliser XOR pour répondre à un problème relativement simple ...

return a ^ b ? c : a

160
Wow, solution cool. Mais pour moi sa version inversée est plus facile à comprendre: a == b? a: c
Rotsor

5
a ^ b? c: a ^ b? c: a ^ b? c: a
alexanderpas

4
Oui, .. XOR obtient une si mauvaise presse et vous avez rarement la chance de l'utiliser.
EightyOne Unite

19
@ Stimul8d peut-être parce que, pour les booléens, c'est la même chose que! = Mais moins lisible? Comprendre cela a été un moment eureka pour moi ...
Tikhon Jelvis

2
Je préfère la forme purement binaire: return ((a ^ b) & c) | (un B). Il est sans branche (plus rapide) et facile à lire: (a ou b est vrai et c est vrai) ou (a et b sont tous les deux vrais). Notez que (a | b) et (a ^ b) fonctionnent tous les deux avec cette formule.
flanglet

217

Pourquoi ne pas l'implémenter littéralement? :)

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

En C, vous pouvez simplement écrire a+b+c >= 2(ou !!a+!!b+!!c >= 2pour être très sûr).

En réponse à la comparaison de TofuBeer du bytecode java, voici un test de performance simple:

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

Cela affiche les éléments suivants sur ma machine (exécutant Ubuntu sur Intel Core 2 + sun java 1.6.0_15-b03 avec HotSpot Server VM (14.1-b02, mode mixte)):

Première et deuxième itérations:

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

Itérations ultérieures:

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

Je me demande ce que pourrait faire une machine virtuelle Java qui dégrade les performances au fil du temps pour (a + b + c> = 2).

Et voici ce qui se passe si j'exécute java avec un -clientcommutateur 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

Mystère...

Et si je l'exécute dans GNU Java Interpreter , cela devient presque 100 fois plus lent, mais la a&&b || b&&c || a&&cversion gagne alors.

Résultats de Tofubeer avec le dernier code fonctionnant sous 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

Résultats de Paul Wagland avec un 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: cela ne fonctionne pas avec les négatifs, non? Vous devrez peut-être faire la !!achose, je ne suis pas sûr.
polygenelubricants

8
<s> -1. Vous ne devriez jamais faire ça pour C. Vous ne savez pas quelle est la valeur de true (cela pourrait tout aussi bien être -1). </s> En fait, je suppose que C99 inclut dans sa norme que true est défini comme 1. Mais Je ne ferais toujours pas ça.
Mark Peters

1
Est-ce possible si votre entrée est le résultat d'opérations booléennes? Et est-ce possible pour le type "bool" en C ++?
Rotsor

2
@Rotsor: Personne n'a dit que l'entrée devait être le résultat d'opérations booléennes. Même sans négatifs, vous jouez avec le feu, comme si vous le définissiez comme 2 votre condition aurait des faux positifs. Mais je m'en fiche autant que je n'aime pas l'idée de mêler les booléens à l'arithmétique. Votre solution Java est claire en ce qu'elle ne repose pas sur des conversions nuancées du type booléen en type entier.
Mark Peters

7
Soyez prudent avec les microbenchmarks
BalusC

143

Ce type de questions peut être résolu avec une carte de Karnaugh :

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

à partir de laquelle vous déduisez que vous avez besoin d'un groupe pour la première ligne et de deux groupes pour la première colonne, obtenant la solution optimale de lubrifiants polygéniques:

(C && (A || B)) || (A && B)  <---- first row
       ^
       |
   first column without third case

10
@Justin, la carte de Karnaugh a réduit le nombre d'opérations logiques de 3 AND et 2 OR à 2 AND et 2 OR. @ Jack, merci de me rappeler l'existence de la carte de Karnaugh.
Tachy

14
+1 pour quelque chose de nouveau. Ma prochaine spécification fonctionnelle comprendra une K-map, qu'elle en ait besoin ou non.
Justin R.

2
Peut-être que la mauvaise lisibilité peut être compensée par (1) le tableau approprié en commentaire et (2) le test unitaire approprié ... +1 pour quelque chose d'utile appris à l'école.
moala

140

La lisibilité devrait être l'objectif. Une personne qui lit le code doit comprendre immédiatement votre intention. Voici donc ma solution.

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

return howManyBooleansAreTrue >= 2;

21
Je suis d'accord avec la prémisse, mais (a && b) || (b && c) || (a && c) est beaucoup plus lisible que votre solution à mon humble avis.
Adrian Grigore

62
Hmm, maintenant j'ai besoin d'une version "deux sur QUATRE booléens" ... la version de danatel est beaucoup plus facile maintenant.
Arafangion

6
Ou à Scala:Seq(true, true, false).map(if (_) 1 else 0).sum >= 2
retronym

5
@retronym: Hmm, non. La méthode Java fonctionne très bien dans Scala et est à la fois plus lisible et plus efficace.
Seun Osewa

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

Explication:

Si a==b, alors les deux sont vrais ou les deux sont faux. Si les deux sont vrais, nous avons trouvé nos deux vrais booléens et pouvons retourner vrai (en revenant a). Si les deux sont faux, il ne peut pas y avoir deux vrais booléens même si cc'est vrai, donc nous retournons faux (en revenant a). Voilà la (a==b) ? apartie. Et alors : c? Eh bien, si a==best faux, alors exactement l'un de aou bdoit être vrai, nous avons donc trouvé le premier vrai booléen, et la seule chose qui reste est si cest également vrai, alors nous revenons ccomme réponse.


8
c n'est même jamais testé ... génial!
CurtainDog

Utilise la relation transitive d'égalité et le fait qu'un booléen est soit vrai soit faux +1
Christophe Roussy

3
Si élégant! J'ai dû vérifier avec un stylo et du papier pour le croire :) Bravo à vous monsieur!
Adrian

3
Je pense à cela comme "si aet d' baccord, ils ont le vote majoritaire, alors allez avec quoi que ce soit, sinon, ils ne sont pas d'accord, tout ccomme le vote décisif"
Ben Millwood

34

Vous n'avez pas besoin d'utiliser les formes de court-circuit des opérateurs.

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

Cela effectue le même nombre d'opérations logiques que votre version, mais est complètement sans branche.


11
Pourquoi voudriez-vous forcer 5 évaluations alors que 1 pourrait le faire? Il n'effectue vraiment pas le même nombre d'opérations logiques en vérité. En fait, il serait toujours plus performant.
Mark Peters

2
Je pense que mélanger l'arithmétique binaire et l'arithmétique booléenne est une mauvaise idée. C'est comme enfoncer des vis dans le mur avec une clé. Le pire est qu'ils ont une sémantique différente.
Peter Tillemans

12
@Mark - cela pourrait être plus rapide ... en fonction de l'effet d'une prédiction de branche incorrecte sur le pipeline CPU. Cependant, il est préférable de laisser ces micro-optimisations au compilateur JIT.
Stephen C

4
C'est bien de faire quelque chose comme ça en Java (ou tout autre langage) ... avec quelques mises en garde: 1) cela doit être plus rapide (dans ce cas, je pense que oui, voir ma deuxième réponse) 2) préférable significativement plus rapide (je ne sais pas si c'est le cas), 3) surtout documenté car il est "étrange". Tant que cela sert un but et qu'il est documenté, il est bien de "briser les règles" quand cela a du sens.
TofuBeer

11
@Peter Tillemans Il n'y a pas de mélange avec les opérateurs binaires, en Java ce sont des opérateurs booléens.
starblue

27

Voici une approche générale pilotée par les tests. Pas aussi "efficace" que la plupart des solutions proposées jusqu'à présent, mais claire, testée, fonctionnelle et généralisée.

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
Wow, je n'ai jamais vu une méthode entièrement testée avant de voir celle-ci.
Rotsor

51
Personnellement, je trouve ce code affreux, pour tant de raisons. Je ne vais pas voter contre, mais si jamais je voyais cela dans le code de production, je maudirais. Une opération booléenne extrêmement simple n'a pas besoin d'être compliquée comme celle-ci.
CaptainCasey

10
Je serais très intéressé de connaître vos raisons, @CaptainCasey. Je pense que c'est un assez bon code. Il y a une belle fonction généralisée qui est facile à comprendre, facile à vérifier et une fonction spécifique qui en profite, également facile à comprendre et à vérifier. Dans le monde réel, je les rendrais publics et les mettais dans une autre classe; à part ça - je serais heureux de mettre ce code en production. Oh - oui - je renommerais countBooleans () en countTrue ().
Carl Manaster

5
s'il ne s'agit pas de performances, cette solution me semble presque parfaite: très facile à lire et extensible. C'est exactement pour cela que les var-args sont faits.
atamanroman

7
Que diable, les gens? C'est du code clair et bien testé, et la seule raison pour laquelle il ressemble beaucoup, c'est qu'il inclut les tests. A +++, voterait à nouveau.
Christoffer Hammarström

24

Résumer. Cela s'appelle l'algèbre booléenne pour une raison:

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

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

Si vous regardez les tables de vérité là-bas, vous pouvez voir que la multiplication est booléenne et, et simplement l'addition est xor.

Pour répondre à ta question:

return (a + b + c) >= 2

2
C'est la solution la plus élégante, à mon avis.
Torbjørn Kristoffersen

9
Erreur débutant cependant, une valeur booléenne n'est PAS 0, cela ne signifie pas que c'est toujours 1.
tomdemuyt

13
Sauf que la balise sur le post dit "Java", et vous ne pouvez pas écrire "a + b + c" lorsqu'ils sont définis comme booléens en Java.
Jay

Pour travailler en Java, cela devrait être le cas return ((a?1:0) + (b?1:0) + (c?1:0)) >= 2.
David R Tribble,

Duh, j'ai voté contre parce que je pensais que c'était une question C ++ ... pourquoi lis-je des questions java? : /
Carlo Wood

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

15

Cela dépend vraiment de ce que vous entendez par «amélioré»:

Plus clair?

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

Plus général?

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

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

        if(count > x) return true;
    }

    return false;
}

Plus évolutif?

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

Plus rapide?

// Only profiling can answer this.

Laquelle est "améliorée" dépend fortement de la situation.


14

Voici une autre implémentation utilisant map / Reduce. Cela évolue bien à des milliards de booléens © dans un environnement distribué. Utilisation de MongoDB:

Création d'une base valuesde données de booléens:

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

Création de la carte, réduction des fonctions:

Edit : J'aime la réponse de CurtainDog à propos de l'application de map / réduire aux listes génériques, alors voici une fonction de carte qui prend un rappel qui détermine si une valeur doit être comptée ou non.

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

Exécution de la carte / réduction:

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: autant que j'aime M / R et l'exposition que Google lui a récemment donnée (même si ce n'est pas le seul vrai M / R de FP), j'aurais tendance à appeler bullsh! T sur votre réponse. Il y a des milliards et des milliards de lignes de code exécutant des "trucs" Real-World [TM] où il n'y a pas une seule ligne de carte / réduction utilisée. Quelqu'un répondant à une telle question avec ceci est définitivement signalé dans mon livre comme: "essayer de jouer au smartie" . Sans oublier que la plupart des enquêteurs ne seraient pas en mesure de dire si vous essayez de les dénigrer ou non, car ils n'ont en fait jamais écrit un seul programme en utilisant M / R dans leur carrière.
SyntaxT3rr0r

2
@Syntax - Chacun a droit à son opinion. Ma réponse n'est qu'une approche de plus pour examiner le problème. Bien sûr, cela semble exagéré pour 3 valeurs booléennes, mais cela ne signifie pas que j'essaie d'être le pantalon intelligent ici. Il s'agit d'une approche courante de résolution de problèmes que tout le monde utilise - décomposez le problème en petits morceaux. C'est ainsi que fonctionne l'induction mathématique, c'est ainsi que fonctionnent la plupart des algorithmes récursifs, et c'est ainsi que les gens résolvent les problèmes en général.
Anurag

13

Prendre les réponses (jusqu'à présent) ici:

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

et les exécuter via le décompilateur (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
}

Vous pouvez voir que les?: Ceux sont légèrement meilleurs que la version corrigée de votre original. Celui qui est le meilleur est celui qui évite complètement les branchements. C'est bon du point de vue de moins d'instructions (dans la plupart des cas) et mieux pour les parties de prédiction de branche du CPU, car une mauvaise estimation dans la prédiction de branche peut entraîner le blocage du CPU.

Je dirais que le plus efficace est celui de moonshadow dans l'ensemble. Il utilise le moins d'instructions en moyenne et réduit les risques de blocage du pipeline dans le CPU.

Pour être sûr à 100%, vous devrez connaître le coût (en cycles de processeur) de chaque instruction, qui, malheureusement, n'est pas facilement disponible (vous devrez consulter la source du point d'accès puis les spécifications des fournisseurs de CPU pour le moment). prises pour chaque instruction générée).

Voir la réponse mise à jour par Rotsor pour une analyse d'exécution du code.


5
Vous ne regardez que le bytecode. Pour tout ce que vous savez, le JIT prendra une version avec des branches dans le bytecode et la transformera en une version sans branches en code natif. Mais on aurait tendance à penser que moins de branches dans le bytecode serait mieux.
David Conrad

13

Un autre exemple de code direct:

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

Ce n'est évidemment pas le code le plus succinct.

Addenda

Une autre version (légèrement optimisée) de ceci:

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

Cela peut s'exécuter légèrement plus rapidement, en supposant que la comparaison avec 0 utilisera du code plus rapide (ou peut-être moins) que la comparaison avec 2.


+1 @Loadmaster, je suis désolé mais vous vous trompez! C'est la réponse la plus succincte ici. (c'est-à-dire brièvement ET clairement exprimé);)
Ash


@ M.Mimpen: uniquement pour les objets de classe. Pour les types primitifs (comme nci-dessus), tout compilateur décent compilera chaque ++opération dans une seule instruction CPU, qu'elle soit pré ou post.
David R Tribble du

12

Encore une autre façon de le faire, mais pas très bonne:

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

Les Booleanvaleurs du code de hachage sont fixées à 1231 pour true et 1237 pour false, donc auraient également pu utiliser<= 3699


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

12

Les améliorations les plus évidentes sont les suivantes:

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

et alors

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

Mais ces améliorations sont mineures.


10

Je n'aime pas le ternaire (d' return a ? (b || c) : (b && c);après la première réponse), et je ne pense pas avoir vu quelqu'un le mentionner. Il est écrit comme ceci:

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)

Usage:

(at-least 2 true false true)

2
+1 La grande version générique montre la puissance des Lisps. Merci,
dsmith

6

Je ne pense pas avoir encore vu cette solution:

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

Son avantage est qu'une fois qu'il atteint le nombre que vous recherchez, il casse. Donc, si c'était "au moins 2 de ces 1 000 000 valeurs sont vraies" alors que les deux premières sont réellement vraies, alors cela devrait aller plus vite que certaines des solutions les plus "normales".


Cela devrait probablement être: if (++ counter == howMany) au lieu d'incrémenter puis de vérifier séparément.
Joe Enos

2
Ou encore plus court: if (b && (++ counter == howMany))
Joe Enos

1
Je le ferais boolean ... boolValues, il est plus facile d'appeler, mais prend toujours un tableau
Stephen

Je ne suis pas à jour sur mon Java - je ne savais pas que cela existait. Une sorte de syntaxe étrange, mais c'est utile - de temps en temps je le ferai en C # (mot-clé params), et cela rend les choses plus agréables à appeler. Ou, je ne connais pas Java, mais dans .NET, les tableaux et toutes les collections implémentent IEnumerable <T>, donc j'utiliserais probablement l'équivalent de Java.
Joe Enos

Comment les performances de cette comparaison par rapport à l'exemple 2of3? retourner un? (b || c): (b && c);
Iain Sproat

6

Nous pouvons convertir les bools en entiers et effectuer cette vérification facile:

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

6

Comme il n'a pas été précisé comment le code doit être amélioré, je m'efforcerai d'améliorer le code en le rendant plus amusant. Voici ma solution:

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

Si quelqu'un se demande si ce code fonctionne, voici une simplification utilisant la même logique:

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; 

}

Cela peut se résumer comme suit:

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

Mais maintenant ce n'est plus drôle.


5
Function ReturnTrueIfTwoIsTrue(bool val1, val2, val3))
{
     return (System.Convert.ToInt16(val1) +
             System.Convert.ToInt16(val2) +
             System.Convert.ToInt16(val3)) > 1;
}

Trop de façons de le faire ...


3
Ressemble plus à C #. Cela doit être mentionné comme tel dans la réponse car la question est ciblée Java :)
BalusC

5

Solution AC.

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

ou vous pouvez préférer:

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

4
return 1 << $a << $b << $c >= 1 << 2;

Je n'ai pas vu la réponse de Suvega avant de poser cela, à peu près la même chose.
Kevin

Est-ce que cela fonctionne vraiment? Je suppose que c'est PHP, mais je n'y ai pas accès, mais je vais juste vous demander: que se passe-t-il si $ a vaut 0?
Mark Edgar

@Mark Cela ne fonctionne pas si $ a est 0. C'était une erreur. Merci d'avoir fait remarquer cela. :)
Kevin

4

La manière la plus simple (IMO) qui n'est pas déroutante et facile à lire:

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

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

Fonctionnellement, c'est pareil. Syntaxiquement, il facilite la lecture pour ceux qui ne sont pas habitués à l'utilisation de l'opérateur conditionnel de point d'interrogation. Je suis prêt à parier que plus de personnes savent utiliser les opérateurs ET et OU que le nombre de personnes qui savent utiliser les opérateurs conditionnels de point d'interrogation. La question d'origine demande une "réponse améliorée". La réponse acceptée simplifie la réponse, mais soulève une question très intéressante de ce que l'on considère comme une amélioration. Vous programmez pour une lisibilité universelle ou pour plus de simplicité? Pour moi, c'est une amélioration par rapport à la réponse acceptée :)
abelito

Préférences personnelles. Pour moi, il est beaucoup plus facile de comprendre l'opérateur ternaire plus propre que cette solution.
nico

1
Ah ouais, j'ai vu ce problème et je me demandais pourquoi personne d'autre n'a mentionné cette solution. Si vous écrivez la logique de l'OP sous forme d'algèbre booléenne, vous obtenez A B + A C + B C, qui a cinq opérations. Par la propriété associative, vous pouvez écrire A * (B + C) + B C, qui a quatre opérations.
Vivian River

C'est la même chose que la réponse de Jack (19 juin) qui (C && (A || B)) || (A && B)vient de changer les noms de * variable ...
user85421

4

Une interprétation littérale fonctionnera dans toutes les langues principales:

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

Mais je rendrais probablement la lecture plus facile, et extensible à plus de trois - quelque chose qui semble être oublié par de nombreux programmeurs:

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

En complément de l'excellent message de @TofuBeer TofuBeer, considérez la réponse de @pdox pdox:

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

Considérez également sa version démontée comme donnée par "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

La réponse de pdox se compile en moins de code d'octet que n'importe laquelle des réponses précédentes. Comment son temps d'exécution se compare-t-il aux autres?

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

Au moins sur mon ordinateur, la réponse de pdox est juste un peu plus rapide que la réponse de @moonshadow moonshadow, faisant de pdox la plus rapide dans l'ensemble (sur mon ordinateur portable HP / Intel).


3

En rubis:

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

Qui pourrait être exécuté dans JRuby sur JavaVM. ;-)


3

Il ne cherche probablement rien de compliqué comme les opérateurs de comparaison au niveau du bit (pas normalement compliqué mais avec des booléens, il est extrêmement étrange d'utiliser des opérateurs au niveau du bit) ou quelque chose de très détourné comme la conversion en int et leur résumé.

La façon la plus directe et la plus naturelle de résoudre ce problème est d'utiliser une expression comme celle-ci:

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

Mettez-le dans une fonction si vous préférez, mais ce n'est pas très compliqué. La solution est logiquement concise et efficace.


3

En C:

return !!a + !!b + !!c >= 2;

En fait, cette réponse est fausse… elle devrait être> = 2, car vous avez besoin d' au moins deux vrais booléens, pas exactement deux.
Paul Wagland

@ Paul Wagland: Merci pour la capture.
Matt Joiner

@ergosys: Avec qui ai-je répondu deux fois?
Matt Joiner
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.