如何逻辑地定义“或”


36

最近,我遇到了一个问题,该问题要求我以编程方式定义逻辑“ 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

这种逻辑是正确的,还是我错过了什么?


10
@gnat:公平地说,真值表列出了输入的每种组合的输出,而Wikipedia文章给出了对该功能的描述。我认为OP真正要问的是如何在不使用运算符本身的情况下以编程方式定义逻辑或。
Blrfl 2014年

6
@ user3687688您能否阐明允许使用的原语?
fredoverflow 2014年

4
这个问题已经引发了微观优化的集体痉挛;)
Rob

8
您可以使用三元运算return arg1 ? arg1 : arg2;
马太福音

4
我已经知道为什么您需要重新定义or运算符。
凯尔·斯特兰德

Answers:


102

我会说这是正确的,但是您不能将其浓缩为诸如此类的东西吗?

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

    return false

由于您要进行“或”比较,因此我认为您确实不需要检查组合。如果其中之一为true,则返回true就很重要。否则我们要返回false。

如果您正在寻找较不冗长的较短版本,这也将起作用:

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

6
您还可以删除第4行上的“ else”(仅保留if arg2 == true)。
道森·托斯

1
@DawsonToth旋转它的方式有很多,具体取决于您是否想变得冗长或凝缩。如果我对其他情况感到满意,但听起来这是一个伪代码问题,因此为了清楚起见,我可能会这样保留。虽然很真实!
艾略特·布莱克本2014年

@BlueHat如果使用else似乎有点不一致,但最后不要使用else。
SBoss

1
@Mehrdad谢谢!我之所以将旧答案包括在内,只是因为我觉得它有点冗长,并且将解决方案解释得更清楚了。但是您的解决方案要小得多,并且可以完成相同的工作。
艾略特·布莱克本2014年

1
甚至更好(更糟糕):or(a, b): a ? a : b
萨拉

149

这是不包含或不包含比较和布尔文字的解决方案:

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

它可能没有比这更基本的东西了;)


32
+1比我的答案短一点。但是,我也很想放弃“ else”,只是为了优雅。
艾略特·布莱克本2014年

10
@BlueHat但是这两个返回将以不同的方式缩进;)
fredoverflow 2014年

5
每当有人将某项与true或进行比较时,我都希望获得EUR false
JensG 2014年

1
@JensG好吧,您认为比尔·盖茨的收入来自何处?
Kroltan

1
||简而言之的JavaScript 运算符(以动态类型的语言实现时)。
犀牛

108

一行代码:

return not (not arg1 and not arg2)

没有分支,没有OR。

在基于C的语言中,它将是:

return !(!arg1 && !arg2);

这只是De Morgan定律的一种应用:(A || B) == !(!A && !B)


6
我认为这种方法是最好的解决方案,因为(在我看来)if/else构造与使用OR相同,只是名称不同。
尼克

2
@Nick使用if等效于相等。通常,在机器代码中,an if被实现为算术,然后通过跳转将其与零进行比较。


1
我喜欢这种方法,因为它and会使IFF 短路,从而使运营商之间保持一致。
凯尔·斯特兰德

1
@Snowman是的。我的意思是,这在if (a) return true; else if (b) return true;道德上或多或少都等同于if (a OR b) return true;,但是这种观点很可能会引起争议。
尼克

13

如果只有andand 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

等等等等等等

由于大多数语言都提供条件和,因此无论如何,“和”运算符都意味着分支。

...

如果您只有nand(请参阅Wikipedia):

返回nand(nand(arg1,arg1),nand(arg2,arg2))


7
简化:return not (not arg1 and not arg2)

@Snowman,您应该真正做出回答,这样我可以投票赞成。您(当前)是这里唯一不进行分支的人。
Lawtonfogle 2014年

4
要添加NAND解决方案,但您击败了我。一切都应以NAND实现。
安迪

2
@Andy:实际上,所有内容都应以NOR定义。;-)
Pieter Geerkens 2014年

1
用纯nand解决方案可以很好地工作。
AAT 2014年

13

函数(ECMAScript)

您只需要函数定义和函数调用。您不需要任何分支,条件,运算符或内置函数。我将演示使用ECMAScript的实现。

首先,让我们定义两个名为true和的函数false。我们可以根据需要定义它们,它们是完全任意的,但是我们将以非常特殊的方式定义它们,这具有一些优势,我们将在后面看到:

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

tru是具有两个参数的函数,它仅忽略其第二个参数并返回第一个参数。fls也是一个具有两个参数的函数,它仅忽略其第一个参数并返回第二个参数。

为什么我们编码trufls这样?那么,这样一来,这两个功能不仅代表两个概念true,并false没有在同一时间,他们也代表了“选择”的概念,换句话说,他们也是一个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

这将打印两个 then branchelse branch!为什么?

好吧,它返回第一个参数的返回值,但是它会评估两个参数,因为ECMAScript是严格的,并且始终在调用函数之前评估函数的所有参数。IOW:它评估第一个参数console.log("then branch"),它只是返回undefined并具有打印then branch到控制台的副作用,并且它评估第二个参数,它也返回undefined并打印到控制台作为副作用。然后,它返回first undefined

在发明了这种编码的λ微积分中,这不是问题:λ微积分是pure,这意味着它没有任何副作用。因此,您永远不会注意到第二个参数也会被求值。另外,λ演算是惰性的(或者至少经常是按正常顺序求值的),这意味着,它实际上并不求值不需要的参数。因此,IOW:在λ微积分中,永远不会评估第二个参数,如果是,我们将不会注意到。

但是,ECMAScript是strict,即,它始终对所有参数求值。好吧,实际上,并非总是如此:例如,if/ then/ elsethen在条件为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

现在我们有了这两个定义,我们可以实现了or。首先,我们看一下真值表or:如果第一个操作数是真实的,则表达式的结果与第一个操作数相同。否则,表达式的结果就是第二个操作数的结果。简而言之:如果第一个操作数为true,则返回第一个操作数,否则返回第二个操作数:

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,它们都返回truefalse,因此我们不能在函数中使用它们。但是,我们还有很多事情可以做。例如,这是单链接列表的实现:

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

对象(标量)

你可能已经注意到一些奇怪:trufls扮演着双重角色,他们既充当数据值truefalse,但与此同时,他们也作为一个条件表达式。它们是数据行为,捆绑成一个……呃……“事物”……或(我敢说)对象

事实上,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

这就是为什么使用多态重构替换条件始终有效的原因:您总是可以用多态消息分发来替换程序中的任何条件,因为正如我们刚刚所示,多态消息分发可以通过简单地实现而代替条件。Smalltalk,Self和Newspeak之类的语言就是存在的证明,因为这些语言甚至没有条件。(除了多态消息分发又称为虚拟方法调用,它们也没有循环,BTW或实际上没有任何类型的语言内置控制结构。)


模式匹配(Haskell)

您还可以or使用模式匹配或类似Haskell的部分函数定义的方式进行定义:

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

当然,模式匹配是条件执行的一种形式,但是面向对象的消息分发也是如此。


2
如何False ||| False = False_ ||| _ = True呢?:)
fredoverflow 2014年

3
@FredOverflow:这将需要始终评估正确的操作数。通常,布尔运算符在其正确的参数(也称为“短路”)中应是非严格的。
约尔格W¯¯米塔格

嗯当然了 我知道必须有更深层次的原因:)
fredoverflow

第一部分立即让我想起了埃里克·利珀特(Eric Lippert)关于延续传球风格的精彩系列。纯粹是巧合,但仍然很有趣:)
Voo

1
@JörgWMittagFredOverflow的定义是适当的短路。尝试True ||| undefined在ghci中自己看看!
丹尼尔·瓦格纳

3

这是使用最传统的定义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 ] ]) { /* ... */ }

我认为这在某些数学意义上是“最纯正的”方法。或运算符毕竟是一个函数,并且真值表实际上是该函数作为关系和集合的本质。当然,这也可以以一种有趣的面向对象的方式来编写:BinaryOperator or = new TruthTableBasedBinaryOperator(new TruthTable(false, true, true, true));

3

将逻辑运算符表示为整数算术表达式的另一种方式(在可行的情况下)。这种方法可以避免许多谓词的较大表达式的大量分支。

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

}

6
booleanExpression ? true : false等于booleanExpression
敏锐2014年

我喜欢您的方法,但是一个简单的错误是,两个参数的总和必须大于零才是正确的,而不大于ONE。
授予2014年

1
return (arga+argb)>0
授予2014年

1
我只是在纠正你的文字。您的代码是完美的,但可以放在一行:return (((arg1 ? 1 : 0)+(arg2 ? 1 : 0)) > 0); :)
授予2014年

1
@SenthuSivasambu我不反对您使用arg1 ? 1 : 0;。这些是将布尔值转换为数字的可靠表达式。只有return语句可以被简单地重构。
Keen 2014年

1

两种形式:

OR(arg1, arg2)
  if arg1
     return True
  else:
     return arg2

要么

OR(arg1, arg2)
  if arg1
     return arg1
  else:
     return arg2

与到目前为止的其他建议相比,它具有类似代码高尔夫球的优势,少了一个分支。如果我们正在考虑创建一个将被大量使用的原语,那么减少分支数量的傻瓜选择甚至还不是很愚蠢。

Javascript的定义||与此类似,再加上其宽松的类型,意味着表达式既false || "abc"具有值"abc"42 || "abc"具有value 42

尽管如果您已经拥有其他逻辑运算符,则类似之类nand(not(arg1), not(arg2))可能具有根本没有分支的优势。


重复先前的答案(如您所承认的)有什么意义?
gnat 2014年

@gnat距离我很近,以至于我看到那个答案就不会打扰,但是在任何一个地方它仍然找不到它,所以我就离开了。
乔恩·汉娜

@gnat,实际上正在考虑“我们正在寻找能够提供一些解释和上下文的长答案”。我现在对这个答案更满意。
乔恩·汉娜

1

除了使用if构造的所有编程解决方案之外,还可以通过组合三个NAND门来构造OR门。如果要在Wikipedia中查看其操作方式,请单击此处

从这个表达,

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

使用NOT和AND的答案与OR相同。请注意,同时使用NOT和AND只是表达NAND的一种晦涩方式。


NOT(A AND A)== NOT(A)吗?
查理

对,就是这样。在同一维基百科文章中,您可以看到它们如何将“非”门减少为“与非”门。与门的操作相同。我选择不编辑它们为“或”门显示的公式。
Walter Mitty 2014年

1

所有好的答案都已经给出。但是我不会阻止我。

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

这只是简单地使用了德摩根定律,并滥用了*&&何时truefalse分别被视为1和类似的事实0。(等等,您是说这不是代码高尔夫球吗?)

这是一个不错的答案:

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

但这与已经给出的其他答案基本相同。


3
这些方法从根本上来说是有缺陷的。考虑-1 + 1 arg1+arg2,-1和0 max(arg1,arg2)
蓬松的2014年

@fluffy这种方法采用布尔参数,然后碰巧可以正确处理大多数类型的垃圾输入。你们很高兴指出,仍有一些垃圾会产生问题。正是这样的原因,为什么我们在实践中应尽可能直接地对实际问题域建模(并避免被我们自己的聪明行为所迷惑)。
敏锐2014年

如果您正在执行纯1位布尔值,那么加法仍然无效,因为1 + 1 =0。:)
蓬松的2014年

@fluffy就是在其中进行显式转换的。是否需要它们取决于实现的细节(这就是为什么这是一个愚蠢的想法)。
Keen 2014年

0

定义的一种方法or是通过查找表。我们可以明确指出:

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

我们根据返回值创建一个具有返回值应具有的值的数组a。然后我们进行查找。在C ++之类的语言中bool,使用truebeing 1falsebeing 提升为可用作数组索引的值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*上述内容以使用集合。只需invoke在与以下名称空间相同的名称空间中创建一个自由函数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;
}

现在set *OR* set返回两者的并集。


0

这使我想起了具有以下特征的功能:

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

这仅适用于可以将布尔值视为(1,0)的语言。由于布尔值是一个类,因此不适用于Smalltalk或Python。在smalltalk中,它们甚至走得更远(这将以伪代码的形式编写):

False::or(a)
    return a

True::or(a)
    return self

并且存在以下两种双重方法:

False::and(a)
    return self

True::and(a)
    return a

因此,“逻辑”在OP语句中是完全有效的,尽管它很冗长。当心,这还不错。如果您需要一个功能,例如基于某种矩阵的数学运算符,那么它是完美的。其他人将实现一个实际的多维数据集(例如Quine-McCluskey语句):

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

然后您将评估或[a] [b]

因此,是的,这里的所有逻辑都是有效的(除了使用语言或运算符xDDDDDDDD发布的逻辑)。

但是我最喜欢的是迪摩根定律: !(!a && !b)


0

查看Swift标准库,并检查它们对快捷方式OR和快捷方式AND操作的实现,如果不需要/不允许,则不评估第二个操作数。


-2

逻辑完全正确,但可以简化:

or(arg1, arg2)
  if arg1 = True
     return True
  else if arg2 = True
     return True
  else
     return False

大概您的语言有一个OR运算符,所以-除非这与问题的实质背道而驰-为什么不

or(arg1, arg2)
  if arg1 = True or arg2 = True
     return True
  else
     return False

if arg1 = True or arg2 = True { return true } else { return false }更好的是return arg1 = True or arg2 = Trueif condition then true else false是多余的。
Doval 2014年

4
询问者特别指出,他们的要求是“不使用操作员本身”
gnat 2014年

2
嗯,我什么也没说。这是我的意思,但直到编辑后问题才这么说,她就这样回答,这是我对此的错。
logicNoob 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.