最近、論理的な「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
この論理は正しいですか、それとも私は何かを見逃しましたか?
それは正しいと思いますが、このようなものに凝縮できませんか?
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
以下は、比較およびブールリテラルを使用しない、または使用しないソリューションです。
or(arg1, arg2)
if arg1
return arg1
else
return arg2
それはおそらくそれよりはるかに根本的なものにはならないでしょう;)
1行のコード:
return not (not arg1 and not arg2)
分岐なし、ORなし。
Cベースの言語では、次のようになります。
return !(!arg1 && !arg2);
これは単に De Morganの法則 の適用です:(A || B) == !(!A && !B)
and
とnot
しかない場合は、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))を返す
必要なのは、関数定義と関数呼び出しだけです。分岐、条件、演算子、組み込み関数は必要ありません。 ECMAScriptを使用した実装を示します。
まず、true
とfalse
という2つの関数を定義しましょう。それらは好きなように定義でき、完全に任意ですが、後で説明するように、いくつかの利点を持つ非常に特殊な方法で定義します。
_const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
_
tru
は2つのパラメーターを持つ関数で、2番目の引数を無視して最初の引数を返します。 fls
も、最初の引数を無視して2番目の引数を返す2つのパラメーターを持つ関数です。
なぜtru
とfls
をこのようにエンコードしたのですか?さて、このように、2つの関数は、true
とfalse
の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) {}
_
すごい!ただし、その定義は少し見苦しく見えます。 tru
とfls
は、それ自体がすでに条件式のように機能するため、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);
_
お気づきかもしれませんが、tru
とfls
は2つの役割を果たし、データ値true
とfalse
の両方として機能しますが、同時に条件式としても機能します。それらはdataとbehaviorで、1つにまとめられます…uhm… "thing"…または(あえて言ってください)object!
実際、tru
とfls
はオブジェクトです。また、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の部分的な関数定義のようなものを使用してor
を定義することもできます。
_True ||| _ = True
_ ||| b = b
_
もちろん、パターンマッチングは条件付き実行の形式ですが、オブジェクト指向のメッセージディスパッチもそうです。
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 ] ]) { /* ... */ }
論理演算子を整数の算術式として表現する別の方法(可能な場合)。この方法により、多くの述語のより大きな表現のために多くの分岐を回避できます。
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;
}
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))
のように、まったく分岐しないという利点があります。
すべての良い答えはすでに与えられています。しかし、私はそれを止めさせません。
// 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
がそれぞれ*
および&&
のように扱われる場合、1
が0
に類似しているという事実を悪用します。 (待って、あなたはこれをコードゴルフではないと言っていますか?)
これはまともな答えです:
or(arg1, arg2)
return arg1 ? arg1 : arg2
しかし、それはすでに与えられた他の答えと本質的に同じです。
If構文を使用してプログラムされたすべてのソリューションに加えて、3つのNANDゲートを組み合わせてORゲートを構築することができます。ウィキペディアでどのように行われるかを確認したい場合は、 ここをクリック してください。
これから、表現、
NOT [NOT(A AND A)AND NOT(B AND B)]
nOTとANDを使用すると、ORと同じ答えが得られます。 NOTとANDの両方を使用することは、NANDを表現するあいまいな方法にすぎないことに注意してください。
これは私に特徴的な機能を覚えています:
_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)
Swift標準ライブラリを見て、ショートカットの実装を確認してくださいORおよびしない評価されるショートカットAND演算)不要/許可されていない場合の第2オペランド。
or
を定義する1つの方法は、ルックアップテーブルを使用することです。これを明示的にすることができます:
bool Or( bool a, bool b } {
bool retval[] = {b,true}; // or {b,a};
return retval[a];
}
a
が何であるかに応じて戻り値が持つべき値を持つ配列を作成します。次に、ルックアップを行います。 C++のような言語では、bool
は配列インデックスとして使用できる値に昇格し、true
は1
であり、false
は0
です。
次に、これを他の論理演算に拡張できます。
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つの和集合を返します。