web-dev-qa-db-ja.com

「または」を論理的に定義する方法

最近、論理的な「OR」演算子をプログラムで定義する必要がある問題に遭遇しましたが、演算子自体は使用していません。

私が思いついたのはこれです:

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

この論理は正しいですか、それとも私は何かを見逃しましたか?

36
logicNoob

それは正しいと思いますが、このようなものに凝縮できませんか?

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

    return false

Or比較を行っているので、本当に組み合わせを確認する必要はないと思います。それらのうちの1つがtrueを返すかどうかが問題になります。それ以外の場合は、falseを返します。

冗長ではない短いバージョンを探している場合、これも機能します。

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

以下は、比較およびブールリテラルを使用しない、または使用しないソリューションです。

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

それはおそらくそれよりはるかに根本的なものにはならないでしょう;)

148
fredoverflow

1行のコード:

return not (not arg1 and not arg2)

分岐なし、ORなし。

Cベースの言語では、次のようになります。

return !(!arg1 && !arg2);

これは単に De Morganの法則 の適用です:(A || B) == !(!A && !B)

107
user22815

andnotしかない場合は、DeMorganの法則を使用してandを反転できます。

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

...または(さらに簡単に)

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

...

とにかく、機械命令としてほとんど常に利用できる何かを最適化することに固執しているように見えるので、結局は次のようになります。

return not(not arg1 and not arg2)

return arg1 ? true : arg2

などなどなど.

ほとんどの言語は条件付きANDを提供するので、オッズは「AND」演算子はとにかく分岐を意味します。

...

あなたが持っているものがnandである場合( wikipedia を参照):

nand(nand(arg1、arg1)、nand(arg2、arg2))を返す

13
Rob

関数(ECMAScript)

必要なのは、関数定義と関数呼び出しだけです。分岐、条件、演算子、組み込み関数は必要ありません。 ECMAScriptを使用した実装を示します。

まず、truefalseという2つの関数を定義しましょう。それらは好きなように定義でき、完全に任意ですが、後で説明するように、いくつかの利点を持つ非常に特殊な方法で定義します。

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

truは2つのパラメーターを持つ関数で、2番目の引数を無視して最初の引数を返します。 flsも、最初の引数を無視して2番目の引数を返す2つのパラメーターを持つ関数です。

なぜtruflsをこのようにエンコードしたのですか?さて、このように、2つの関数は、truefalseの2つの概念を表すだけでなく、同時に、 "選択"の概念も表します。つまり、これらはif/then/else式でもあります。 ! if条件を評価し、それにthenブロックとelseブロックを引数として渡します。条件がtruと評価された場合、thenブロックを返します。flsと評価された場合、elseブロックを返します。次に例を示します。

_tru(23, 42);
// => 23
_

これは_23_を返し、これは:

_fls(23, 42);
// => 42
_

期待どおりに_42_を返します。

ただし、しわがあります。

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

これはboth _then branch_および_else branch_を出力します!どうして?

まあ、それはreturns最初の引数の戻り値ですが、ECMAScriptは厳密であり、常に関数のすべての引数を評価してから関数を呼び出すため、evaluates両方の引数です。 IOW:console.log("then branch")である最初の引数を評価します。これは単にundefinedを返し、コンソールに_then branch_を出力するという副作用がありますand 2番目の引数を評価します引数。これもundefinedを返し、副作用としてコンソールに出力します。次に、最初のundefinedを返します。

このエンコーディングが発明されたλ-calculusでは、それは問題ではありません:λ-calculusはpureであり、これは副作用がないことを意味します。したがって、2番目の引数も評価されることに気付くことはありません。さらに、λ-calculusはlazy(または、少なくとも通常は通常の順序で評価される)です。つまり、不要な引数は実際には評価されません。したがって、IOW:λ計算では2番目の引数が評価されることはなく、評価されても気付かないでしょう。

ただし、ECMAScriptはstrictです。つまり、常にすべての引数を評価します。ええと、実際には、常にというわけではありません。たとえば、if/then/elseは、条件がthenの場合はtrueブランチのみを評価し、条件がelseの場合はfalseブランチのみを評価します。そして、この動作をiffで再現したいと思います。ありがたいことに、ECMAScriptは怠惰ではありませんが、他のほとんどすべての言語と同じように、コードの評価を遅延させることができます。それを関数にラップし、その関数を呼び出さない場合、コードは決して実行されません。

したがって、両方のブロックを関数でラップし、最後に返される関数を呼び出します。

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

_then branch_および

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

_else branch_を出力します。

従来のif/then/elseを次のように実装できます。

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

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

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

ここでも、上記と同じ理由で、iff関数を呼び出すときに追加の関数ラッピングとiffの定義で追加の関数呼び出し括弧を必要とします。

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

これで2つの定義ができたので、orを実装できます。まず、orの真理値表を確認します。最初のオペランドが真である場合、式の結果は最初のオペランドと同じです。それ以外の場合、式の結果は第2オペランドの結果です。つまり、最初のオペランドがtrueの場合、最初のオペランドを返し、それ以外の場合は2番目のオペランドを返します。

_const orr = (a, b) => iff(a, () => a, () => b);
_

それが機能することを確認してみましょう:

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

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

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

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

すごい!ただし、その定義は少し見苦しく見えます。 truflsは、それ自体がすでに条件式のように機能するため、iffの必要性はまったくなく、そのため、すべての関数がラップされます。

_const orr = (a, b) => a(a, b);
_

あなたはそれを持っています:or(および他のブール演算子)はほんの一握りの行で関数定義と関数呼び出しだけで定義されています:

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

残念ながら、この実装はあまり役に立ちません。ECMAScriptには、truまたはflsを返す関数や演算子はありません。これらはすべてtrueまたはfalseを返すため、関数で使用することはできません。しかし、私たちにできることはまだたくさんあります。たとえば、これは単一リンクリストの実装です。

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

オブジェクト(Scala)

お気づきかもしれませんが、truflsは2つの役割を果たし、データ値truefalseの両方として機能しますが、同時に条件式としても機能します。それらはdatabehaviorで、1つにまとめられます…uhm… "thing"…または(あえて言ってください)object

実際、truflsはオブジェクトです。また、Smalltalk、Self、Newspeak、またはその他のオブジェクト指向言語を使用したことがある場合は、まったく同じ方法でブール値を実装していることに気づくでしょう。ここではScalaでそのような実装を示します。

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

このBTWが、条件付き多態性リファクタリングの置換が常に機能する理由です。ここで示したように、多態性メッセージディスパッチは条件を単純に実装するだけで置き換えることができるため、プログラム内のすべての条件を常に多態性メッセージディスパッチに置き換えることができます。 Smalltalk、Self、Newspeakのような言語は、それらの存在を証明するものです。これらの言語には条件さえもないからです。 (また、ループ、BTW、または実際にはanyポリモーフィックメッセージディスパッチまたは仮想メソッドコールを除く、言語に組み込まれた制御構造の種類もありません。)


パターンマッチング(Haskell)

また、パターンマッチングまたはHaskellの部分的な関数定義のようなものを使用してorを定義することもできます。

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

もちろん、パターンマッチングは条件付き実行の形式ですが、オブジェクト指向のメッセージディスパッチもそうです。

13
Jörg W Mittag

ORを定義する別の方法、または実際に論理演算子を定義する最も伝統的な方法を使用する方法があります。真理値表を使用します。

もちろん、これはJavascriptやPerlなどの高水準言語で行うのは簡単ですが、この例をCで書いて、この手法が高水準言語の機能に依存していないことを示しています。

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

AND、NOR、NAND、NOT、XORでも同じことができます。コードは、次のようなことができるように、構文のように見えるほどきれいです。

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

論理演算子を整数の算術式として表現する別の方法(可能な場合)。この方法により、多くの述語のより大きな表現のために多くの分岐を回避できます。

Trueを1にし、Falseを0にします

両方の合計が1より大きい場合、trueまたはfalseが返されます。

boolean isOR(boolean arg1, boolean arg2){

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

   return (L+R) > 0;

}
3

2つの形式:

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

OR

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

コードと同様に、これまでの他の提案よりも少し小さく、ブランチが1つ少ないというゴルフのような利点があります。したがって、非常に頻繁に使用されるプリミティブの作成を検討している場合は、ブランチの数を減らすというばかげたマイクロオプトではありません。

JavaScriptの_||_の定義はこれに似ており、緩い型付けと組み合わせると、式_false || "abc"_の値は_"abc"_になり、_42 || "abc"_の値は_42_になります。 。

ただし、すでに他の論理演算子がある場合は、nand(not(arg1), not(arg2))のように、まったく分岐しないという利点があります。

1
Jon Hanna

すべての良い答えはすでに与えられています。しかし、私はそれを止めさせません。

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

または:

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

このようなアプローチを実際に使用する人がいないことを願っています。彼らは選択肢の認識を促進するためだけにここにあります。

更新:

負の数は上記のアプローチの両方を壊す可能性があるため、ここに別のひどい提案があります:

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

これは単に DeMorganの法則 を使用し、trueおよびfalseがそれぞれ*および&&のように扱われる場合、10に類似しているという事実を悪用します。 (待って、あなたはこれをコードゴルフではないと言っていますか?)

これはまともな答えです:

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

しかし、それはすでに与えられた他の答えと本質的に同じです。

1
Keen

If構文を使用してプログラムされたすべてのソリューションに加えて、3つのNANDゲートを組み合わせてORゲートを構築することができます。ウィキペディアでどのように行われるかを確認したい場合は、 ここをクリック してください。

これから、表現、

NOT [NOT(A AND A)AND NOT(B AND B)]

nOTとANDを使用すると、ORと同じ答えが得られます。 NOTとANDの両方を使用することは、NANDを表現するあいまいな方法にすぎないことに注意してください。

1
Walter Mitty

これは私に特徴的な機能を覚えています:

_or(a, b)
    return a + b - a*b
_

これは、ブール値を(1、0)として処理できる言語にのみ適用されます。 SmalltalkまたはPythonには適用されません。Smalltalkではさらに進んでいます(これは一種の疑似コードで記述されます):

_False::or(a)
    return a

True::or(a)
    return self
_

また、次の2つの方法があります。

_False::and(a)
    return self

True::and(a)
    return a
_

したがって、「ロジック」はOPステートメントでは完全に有効ですが、冗長です。注意してください、それは悪くありません。たとえば、ある種の行列に基づく数学演​​算子のように機能する関数が必要な場合に最適です。他の人は実際のキューブを実装します(Quine-McCluskeyステートメントのように):

_or = array[2][2] {
    {0, 1},
    {1, 1}
}
_

そして、あなたはor [a] [b]を評価します

したがって、はい、ここにあるすべてのロジックは有効です(言語内OR演算子xDDDDDDDDを使用して投稿されたものを除く)。

しかし、私のお気に入りはDeMorganの法則です:!(!a && !b)

0
Luis Masuelli

Swift標準ライブラリを見て、ショートカットの実装を確認してくださいORおよびしない評価されるショートカットAND演算)不要/許可されていない場合の第2オペランド。

0
gnasher729

orを定義する1つの方法は、ルックアップテーブルを使用することです。これを明示的にすることができます:

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

aが何であるかに応じて戻り値が持つべき値を持つ配列を作成します。次に、ルックアップを行います。 C++のような言語では、boolは配列インデックスとして使用できる値に昇格し、true1であり、false0です。

次に、これを他の論理演算に拡張できます。

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

これらすべての欠点は、プレフィックス表記が必要になることです。

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

そして今、あなたはtrue *OR* falseとタイプすることができ、それはうまくいきます。

上記の手法には、引数に依存するルックアップとテンプレートをサポートする言語が必要です。おそらく、ジェネリックとADLを備えた言語でそれを行うことができます。

余談ですが、上記の*OR*を拡張して、セットを操作することができます。 or_tagと同じ名前空間に無料の関数invokeを作成するだけです。

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

そしてset *OR* setは2つの和集合を返します。

0
Yakk