it-swarm.com.de

Wie man "oder" logisch definiert

Kürzlich bin ich auf ein Problem gestoßen, bei dem ich den logischen "ODER" -Operator programmgesteuert definieren musste, ohne jedoch den Operator selbst zu verwenden.

Was ich mir ausgedacht habe, ist Folgendes:

OR(arg1, arg2)
  if arg1 = True and arg2 = True
     return True

  else if arg1 = True and arg2 = False
     return True

  else if arg1 = False and arg2 = True
     return True

  else:
     return False

Ist diese Logik richtig oder habe ich etwas verpasst?

36
logicNoob

Ich würde sagen, das ist richtig, aber könnten Sie es nicht auf so etwas reduzieren?

or(arg1, arg2)
    if arg1 == true
        return true
    if arg2 == true
        return true

    return false

Da Sie einen oder einen Vergleich durchführen, denke ich nicht, dass Sie die Kombination wirklich überprüfen müssen. Es ist nur wichtig, ob einer von ihnen wahr ist, um wahr zurückzukehren. Andernfalls möchten wir false zurückgeben.

Wenn Sie nach einer kürzeren Version suchen, die weniger ausführlich ist, funktioniert dies auch:

or(arg1, arg2)
    if arg1
        return arg1
    return arg2
102

Hier ist eine Lösung ohne oder und nicht Vergleiche und Boolesche Literale:

or(arg1, arg2)
  if arg1
    return arg1
  else
    return arg2

Grundlegender geht es wahrscheinlich nicht;)

148
fredoverflow

Eine Codezeile:

return not (not arg1 and not arg2)

Keine Verzweigung, kein ODER.

In einer C-basierten Sprache wäre es:

return !(!arg1 && !arg2);

Dies ist einfach eine Anwendung von De Morgans Gesetzen : (A || B) == !(!A && !B)

107
user22815

Wenn Sie nur and und not haben, können Sie das DeMorgan-Gesetz verwenden, um and umzudrehen:

if not (arg1 = False and arg2 = False)
  return True
else
  return False

... oder (noch einfacher)

if arg1 = False and arg2 = False
  return false
else
  return true

...

Und da wir uns anscheinend alle darauf fixiert haben, etwas zu optimieren, das sowieso fast immer als Maschinenanweisung verfügbar ist, läuft das auf Folgendes hinaus:

return not(not arg1 and not arg2)

return arg1 ? true : arg2

usw. usw. usw. usw.

Da die meisten Sprachen ein Bedingungs-und bereitstellen, impliziert die Wahrscheinlichkeit, dass der Operator "und" ohnehin einen Zweig impliziert.

...

Wenn Sie nur nand haben (siehe Wikipedia ):

return nand (nand (arg1, arg1), nand (arg2, arg2))

13
Rob

Funktionen (ECMAScript)

Sie benötigen lediglich Funktionsdefinitionen und Funktionsaufrufe. Sie benötigen keine Verzweigungen, Bedingungen, Operatoren oder integrierten Funktionen. Ich werde eine Implementierung mit ECMAScript demonstrieren.

Definieren wir zunächst zwei Funktionen mit den Namen true und false. Wir könnten sie so definieren, wie wir wollen, sie sind völlig willkürlich, aber wir werden sie auf eine ganz besondere Weise definieren, die einige Vorteile hat, wie wir später sehen werden:

const tru = (thn, _  ) => thn,
      fls = (_  , els) => els;

tru ist eine Funktion mit zwei Parametern, die das zweite Argument einfach ignoriert und das erste zurückgibt. fls ist auch eine Funktion mit zwei Parametern, die das erste Argument einfach ignoriert und das zweite zurückgibt.

Warum haben wir tru und fls auf diese Weise codiert? Nun, auf diese Weise repräsentieren die beiden Funktionen nicht nur die beiden Konzepte von true und false, nein, gleichzeitig repräsentieren sie auch das Konzept der "Wahl", mit anderen Worten, Sie sind auch ein if/then/else Ausdruck! Wir werten die Bedingung if aus und übergeben ihr den Block then und den Block else als Argumente. Wenn die Bedingung tru ergibt, wird der Block then zurückgegeben. Wenn sie fls ergibt, wird der Block else zurückgegeben. Hier ist ein Beispiel:

tru(23, 42);
// => 23

Dies gibt 23 Zurück und dies:

fls(23, 42);
// => 42

gibt 42 zurück, genau wie Sie es erwarten würden.

Es gibt jedoch eine Falte:

tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch

Dies druckt beidethen branch Und else branch! Warum?

Nun, es gibt zurück der Rückgabewert des ersten Arguments, aber es wertet aus beide Argumente, da ECMAScript streng ist und immer alle Argumente für eine Funktion auswertet, bevor die Funktion aufgerufen wird. IOW: Es wertet das erste Argument aus, nämlich console.log("then branch"), das einfach undefined zurückgibt und den Nebeneffekt hat, dass then branch Auf die Konsole gedruckt wird, nd Es wertet das zweite Argument aus, das ebenfalls undefined zurückgibt und als Nebeneffekt auf der Konsole gedruckt wird. Dann wird das erste undefined zurückgegeben.

Im λ-Kalkül, wo diese Codierung erfunden wurde, ist das kein Problem: λ-Kalkül ist rein, was bedeutet, dass es keine Nebenwirkungen hat; Daher würden Sie nie bemerken, dass das zweite Argument ebenfalls ausgewertet wird. Außerdem ist λ-Kalkül faul (oder zumindest wird es oft in normaler Reihenfolge ausgewertet), was bedeutet, dass Argumente, die nicht benötigt werden, nicht ausgewertet werden. Also, IOW: In der λ-Rechnung würde das zweite Argument niemals bewertet werden, und wenn es so wäre, würden wir es nicht bemerken.

ECMAScript ist jedoch streng, d. H. Es wertet immer alle Argumente aus. Nun, eigentlich nicht immer: Der if/then/else wertet beispielsweise den Zweig then nur aus, wenn die Bedingung true und wertet den Zweig else nur aus, wenn die Bedingung false ist. Und wir möchten dieses Verhalten mit unserem iff replizieren. Zum Glück kann ECMAScript, obwohl es nicht faul ist, die Auswertung eines Codeteils verzögern, so wie es fast jede andere Sprache tut: Wickeln Sie es in eine Funktion, und wenn Sie diese Funktion nie aufrufen, wird der Code dies tun niemals hingerichtet werden.

Also wickeln wir beide Blöcke in eine Funktion ein und rufen am Ende die zurückgegebene Funktion auf:

tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch

druckt then branch und

fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch

druckt else branch.

Wir könnten das traditionelle if/then/else folgendermaßen implementieren:

const iff = (cnd, thn, els) => cnd(thn, els);

iff(tru, 23, 42);
// => 23

iff(fls, 23, 42);
// => 42

Auch hier benötigen wir einen zusätzlichen Funktionsumbruch, wenn wir die Funktion iff und die Klammern für den zusätzlichen Funktionsaufruf in der Definition von iff aus demselben Grund wie oben aufrufen:

const iff = (cnd, thn, els) => cnd(thn, els)();

iff(tru, () => console.log("then branch"), () => console.log("else branch"));
// then branch

iff(fls, () => console.log("then branch"), () => console.log("else branch"));
// else branch

Nachdem wir diese beiden Definitionen haben, können wir or implementieren. Zuerst betrachten wir die Wahrheitstabelle für or: Wenn der erste Operand wahr ist, ist das Ergebnis des Ausdrucks dasselbe wie der erste Operand. Andernfalls ist das Ergebnis des Ausdrucks das Ergebnis des zweiten Operanden. Kurz gesagt: Wenn der erste Operand true ist, geben wir den ersten Operanden zurück, andernfalls geben wir den zweiten Operanden zurück:

const orr = (a, b) => iff(a, () => a, () => b);

Lassen Sie uns überprüfen, ob es funktioniert:

orr(tru,tru);
// => tru(thn, _) {}

orr(tru,fls);
// => tru(thn, _) {}

orr(fls,tru);
// => tru(thn, _) {}

orr(fls,fls);
// => fls(_, els) {}

Großartig! Diese Definition sieht jedoch etwas hässlich aus. Denken Sie daran, dass tru und fls bereits für sich genommen wie eine Bedingung wirken, sodass iff wirklich nicht erforderlich ist und daher die gesamte Funktion überhaupt umbrochen wird:

const orr = (a, b) => a(a, b);

Dort haben Sie es: or (plus andere boolesche Operatoren) definiert mit nichts als Funktionsdefinitionen und Funktionsaufrufen in nur wenigen Zeilen:

const tru = (thn, _  ) => thn,
      fls = (_  , els) => els,
      orr = (a  , b  ) => a(a, b),
      nnd = (a  , b  ) => a(b, a),
      ntt = a          => a(fls, tru),
      xor = (a  , b  ) => a(ntt(b), b),
      iff = (cnd, thn, els) => cnd(thn, els)();

Leider ist diese Implementierung ziemlich nutzlos: Es gibt keine Funktionen oder Operatoren in ECMAScript, die tru oder fls zurückgeben, sie alle geben also true oder false zurück Wir können sie nicht mit unseren Funktionen verwenden. Aber wir können noch viel tun. Dies ist beispielsweise eine Implementierung einer einfach verknüpften Liste:

const cons = (hd, tl) => which => which(hd, tl),
      car  = l => l(tru),
      cdr  = l => l(fls);

Objekte (Scala)

Möglicherweise haben Sie etwas Besonderes bemerkt: tru und fls spielen eine doppelte Rolle, sie fungieren beide als Datenwerte true und false, aber gleichzeitig Zeit wirken sie auch als bedingter Ausdruck. Sie sind Daten und Verhalten, gebündelt in einem ... ähm ... "Ding" ... oder (wage ich zu sagen) Objekt!

In der Tat sind tru und fls Objekte. Und wenn Sie jemals Smalltalk, Self, Newspeak oder andere objektorientierte Sprachen verwendet haben, werden Sie feststellen, dass sie Boolesche Werte genauso implementieren. Ich werde eine solche Implementierung hier in Scala demonstrieren:

sealed abstract trait Buul {
  def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): T
  def &&&(other: ⇒ Buul): Buul
  def |||(other: ⇒ Buul): Buul
  def ntt: Buul
}

case object Tru extends Buul {
  override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): U = thn
  override def &&&(other: ⇒ Buul) = other
  override def |||(other: ⇒ Buul): this.type = this
  override def ntt = Fls
}

case object Fls extends Buul {
  override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): V = els
  override def &&&(other: ⇒ Buul): this.type = this
  override def |||(other: ⇒ Buul) = other
  override def ntt = Tru
}

object BuulExtension {
  import scala.language.implicitConversions
  implicit def boolean2Buul(b: ⇒ Boolean) = if (b) Tru else Fls
}

import BuulExtension._

(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3

Übrigens funktioniert das Refactoring "Bedingt durch Polymorphismus ersetzen" immer: Sie können jede Bedingung in Ihrem Programm durch einen polymorphen Nachrichtenversand ersetzen, da der polymorphe Nachrichtenversand, wie wir gerade gezeigt haben, Bedingungen ersetzen kann, indem Sie sie einfach implementieren. Sprachen wie Smalltalk, Self und Newspeak sind ein Existenzbeweis dafür, weil diese Sprachen nicht einmal Bedingungen haben. (Sie haben auch keine Schleifen, BTW oder wirklich any Art von in die Sprache integrierten Kontrollstrukturen, mit Ausnahme des polymorphen Nachrichtenversands, auch bekannt als virtuelle Methodenaufrufe.)


Mustervergleich (Haskell)

Sie können or auch mithilfe des Mustervergleichs oder ähnlich wie bei den Teilfunktionsdefinitionen von Haskell definieren:

True ||| _ = True
_    ||| b = b

Natürlich ist der Mustervergleich eine Form der bedingten Ausführung, aber auch der objektorientierte Nachrichtenversand.

13
Jörg W Mittag

Hier ist eine andere Möglichkeit, OR oder einen logischen Operator mit der traditionellsten Art der Definition zu definieren: Verwenden Sie eine Wahrheitstabelle.

Dies ist natürlich in höheren Sprachen wie Javascript oder Perl ziemlich trivial, aber ich schreibe dieses Beispiel in C, um zu zeigen, dass die Technik nicht von höheren Sprachfunktionen abhängt:

#include <stdio.h>

int main (void) {
    // Define truth table for OR:
    int OR[2][2] = {
        {0,   // false, false
         1},  // false, true
        {1,   // true, false
         1}   // true, true
    }

    // Let's test the definition
    printf("false || false = %d\n",OR[1==2]['b'=='a']);
    printf("true || false = %d\n",OR[10==10]['b'=='a']);

    // Usage:
    if (OR[ 1==2 ][ 3==4 ]) {
        printf("at least one is true\n");
    }
    else {
        printf("both are false\n");
    }
}

Sie können dasselbe mit AND, NOR, NAND, NOT und XOR tun. Der Code ist sauber genug, um wie eine Syntax auszusehen, sodass Sie Folgendes tun können:

if (OR[ a ][ AND[ b ][ c ] ]) { /* ... */ }
3
slebetman

Eine andere Möglichkeit, die logischen Operatoren als ganzzahlige arithmetische Ausdrücke auszudrücken (sofern möglich). Auf diese Weise können viele Verzweigungen vermieden werden, um einen größeren Ausdruck vieler Prädikate zu erhalten.

Sei True 1 Sei False 0

wenn die Summe von beiden größer als 1 ist, ist es wahr oder falsch, zurückgegeben zu werden.

boolean isOR(boolean arg1, boolean arg2){

   int L = arg1 ? 1 : 0;
   int R = arg2 ? 1 : 0;

   return (L+R) > 0;

}
3

Die zwei Formen:

_OR(arg1, arg2)
  if arg1
     return True
  else:
     return arg2
_

OR

_OR(arg1, arg2)
  if arg1
     return arg1
  else:
     return arg2
_

Haben Sie neben dem Code Golf-ähnlichen Vorteil, ein bisschen kleiner als die anderen Vorschläge bisher zu sein, einen Zweig weniger. Es ist nicht einmal so albern, eine Mikrooption zu verwenden, um die Anzahl der Zweige zu verringern, wenn wir die Schaffung eines Grundelements in Betracht ziehen, das daher sehr häufig verwendet wird.

Die Definition von _||_ in Javascript ist ähnlich, was zusammen mit seiner losen Typisierung bedeutet, dass der Ausdruck _false || "abc"_ den Wert _"abc"_ und _42 || "abc"_ den Wert _42_ hat .

Wenn Sie jedoch bereits andere logische Operatoren haben, haben solche wie nand(not(arg1), not(arg2)) möglicherweise den Vorteil, dass überhaupt keine Verzweigung erfolgt.

1
Jon Hanna

Alle guten Antworten wurden bereits gegeben. Aber das wird mich nicht aufhalten lassen.

// This will break when the arguments are additive inverses.
// It is "cleverness" like this that's behind all the most amazing program errors.
or(arg1, arg2)
    return arg1 + arg2
    // Or if you need explicit conversions:
    // return (bool)((short)arg1 + (short)arg2)

Alternative:

// Since `0 > -1`, negative numbers will cause weirdness.
or(arg1, arg2)
    return max(arg1, arg2)

Ich hoffe, niemand würde jemals solche Ansätze verwenden. Sie sind nur hier, um das Bewusstsein für Alternativen zu fördern.

Update:

Da negative Zahlen beide oben genannten Ansätze brechen können, ist hier ein weiterer schrecklicher Vorschlag:

or(arg1, arg2)
    return !(!arg1 * !arg2)

Dies verwendet einfach DeMorgan's Laws und missbraucht die Tatsache, dass *&& ähnlich ist, wenn true und false behandelt werden wie 1 bzw. 0. (Warten Sie, Sie sagen dies ist nicht Code Golf?)

Hier ist eine anständige Antwort:

or(arg1, arg2)
    return arg1 ? arg1 : arg2

Dies ist jedoch im Wesentlichen identisch mit anderen bereits gegebenen Antworten.

1
Keen

Zusätzlich zu allen programmierten Lösungen, die das if-Konstrukt verwenden, ist es möglich, ein OR - Gate durch Kombinieren von drei NAND-Gates zu erstellen. Wenn Sie sehen möchten, wie es in Wikipedia gemacht wird, klicken Sie auf) hier .

Daraus ergibt sich der Ausdruck

NICHT [NICHT (A UND A) UND NICHT (B UND B)]

die NOT und AND verwendet, gibt die gleiche Antwort wie OR. Beachten Sie, dass die Verwendung von NOT und AND nur eine dunkle Art ist, NAND auszudrücken.

1
Walter Mitty

Dieser erinnert mich an die charasteristischen Funktionen:

or(a, b)
    return a + b - a*b

Dies gilt nur für Sprachen, die Boolesche Werte als (1, 0) behandeln können. Gilt nicht für Smalltalk oder Python, da Boolean eine Klasse ist. In Smalltalk gehen sie noch weiter (dies wird in einer Art Pseudocode geschrieben):

False::or(a)
    return a

True::or(a)
    return self

Und die dualen Methoden existieren für und:

False::and(a)
    return self

True::and(a)
    return a

Die "Logik" ist also in der OP-Anweisung vollkommen gültig, obwohl sie ausführlich ist. Achtung, es ist nicht schlecht. Es ist perfekt, wenn Sie eine Funktion benötigen, die sich wie ein mathematischer Operator verhält, der beispielsweise auf einer Art Matrix basiert. Andere würden einen tatsächlichen Würfel implementieren (wie eine Quine-McCluskey-Anweisung):

or = array[2][2] {
    {0, 1},
    {1, 1}
}

Und Sie werden bewerten oder [a] [b]

Also ja, jede Logik hier ist gültig (außer der, die mit dem in-language OR Operator xDDDDDDDD) gepostet wurde.

Aber mein Favorit ist das Gesetz von DeMorgan: !(!a && !b)

0
Luis Masuelli

Schauen Sie sich die Standardbibliothek Swift) an und überprüfen Sie deren Implementierung der Verknüpfung OR und Verknüpfungs-AND-Operationen, die nicht auswerten die zweiten Operanden, falls nicht benötigt/erlaubt.

0
gnasher729

Eine Möglichkeit, or zu definieren, besteht in einer Nachschlagetabelle. Wir können dies explizit machen:

bool Or( bool a, bool b } {
  bool retval[] = {b,true}; // or {b,a};
  return retval[a];
}

wir erstellen ein Array mit den Werten, die der Rückgabewert haben sollte, abhängig davon, was a ist. Dann machen wir eine Suche. In C++ - ähnlichen Sprachen wird bool auf einen Wert hochgestuft, der als Array-Index verwendet werden kann, wobei true1 Und false0.

Wir können dies dann auf andere logische Operationen ausweiten:

bool And( bool a, bool b } {
  bool retval[] = {false,b}; // or {a,b};
  return retval[a];
}
bool Xor( bool a, bool b } {
  bool retval[] = {b,!b};
  return retval[a];
}

Ein Nachteil von all diesen ist, dass eine Präfixnotation erforderlich ist.

namespace operators {
  namespace details {
    template<class T> struct is_operator {};
    template<class Lhs, Op> struct half_expression { Lhs&& lhs; };
    template<class Lhs, class Op>
    half_expression< Lhs, Op > operator*( Lhs&&lhs, is_operator<Op> ) {
      return {std::forward<Lhs>(lhs)};
    }
    template<class Lhs, class Op, class Rhs>
    auto operator*( half_expression<Lhs, Op>&& lhs, Rhs&& rhs ) {
    return invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
    }
  }
  using details::is_operator;
}

struct or_tag {};
static const operators::is_operator<or_tag> OR;

bool invoke( bool a, or_tag, bool b ) {
  bool retval[] = {b,true};
  return retval[a];
}

und jetzt können Sie true *OR* false eingeben und es funktioniert.

Die obige Technik erfordert eine Sprache, die argumentabhängige Suche und Vorlagen unterstützt. Sie könnten es wahrscheinlich in einer Sprache mit Generika und ADL tun.

Nebenbei können Sie den obigen *OR* Erweitern, um mit Mengen zu arbeiten. Erstellen Sie einfach eine kostenlose Funktion invoke im selben Namespace wie or_tag:

template<class...Ts>
std::set<Ts...> invoke( std::set<Ts...> lhs, or_tag, std::set<Ts...> const& rhs ) {
  lhs.insert( rhs.begin(), rhs.end() );
  return lhs;
}

und jetzt gibt set *OR* set die Vereinigung der beiden zurück.

0
Yakk