在R语言中,布尔运算符&&和&以及||和|之间的区别是什么?

329
根据R语言定义&&&(相应的|||)之间的区别在于前者是向量化的,而后者不是。
根据帮助文本,我认为这两者之间的区别类似于"And"和"AndAlso"(相应地是"Or"和"OrElse")之间的区别......意思是:如果不必要的话,不需要对所有的评估进行评估(例如,如果A为真,则A或B或C总是为真,因此如果A为真,则停止评估)。
请问有人能够解释一下吗? 另外,在R中是否有AndAlso和OrElse?

还可以在 https://dev59.com/bWw05IYBdhLWcg3w72Q0 和 https://dev59.com/FFzUa4cB1Zd3GeqP6MBs(现已关闭为重复)中查看类似的问题。 - Aaron left Stack Overflow
3
我认为在R中实现的&&和||有些问题。在其他语言中,它们是条件AND和OR运算符,可以执行逻辑AND或OR布尔运算,但仅在必要时才评估第二个操作数。但在R中,它们并没有什么实用价值。 - skan
2
@skan:我认为你的说法是错误的,即 &&|| 的行为与未指定“其他语言”不评估第二个参数当第一个参数确定时不同。 - IRTFM
你的断言是错误和粗鲁的。例如,在Julia中: https://docs.julialang.org/en/v1/manual/control-flow/#Short-Circuit-Evaluation“它们不一定会评估它们的第二个参数” “只有必要确定整个链的最终布尔值的最小表达式数量才被评估” “在表达式a && b中,只有当a评估为true时,子表达式b才会被评估。” - skan
3
如果第一个参数为FALSE,则不会对&&的第二个参数进行求值。在这方面,与Julia没有区别。 - IRTFM
@skan 你可能想要删除那个旧评论,因为它是错误的(正如我希望你现在同意的那样),并有可能误导草率的读者。 - Konrad Rudolph
5个回答

442
较短的函数是向量化的,意味着它们可以返回一个向量,就像这样:
((-2:2) >= 0) & ((-2:2) <= 0)
# [1] FALSE FALSE  TRUE FALSE FALSE

较长的形式不是如此(从4.3.0开始),必须给出长度为1的输入。(好耶!减少了检查的必要性,请参见下面。)
在R 4.3.0之前,给出长度大于1的&&输入不会引发错误,而是从左到右进行求值,仅检查每个向量的第一个元素,所以上述情况变为:
((-2:2) >= 0) && ((-2:2) <= 0)
# [1] FALSE

根据帮助页面所说,这使得较长的形式“适用于编程控制流,并且在if子句中通常更受青睐”。
因此,只有当您确定向量长度为1时,才应使用长形式,在4.3.0版本之后,R会强制执行此规定。
如果您使用的是旧版本,则应确保您的向量长度仅为1,例如在它们是返回仅长度为1的布尔值的函数的情况下。如果向量长度可能大于1,则应使用短形式。因此,如果您不确定,您应该先进行检查,或者使用短形式,然后使用allany将其缩减为长度为1,以便在控制流语句(如if)中使用。
函数allany经常用于向量化比较的结果,以查看所有或任何比较是否为真。这些函数的结果肯定是长度为1的,因此它们适用于在if子句中使用,而向量化比较的结果则不适用。(尽管这些结果适用于ifelse的使用)。

最后一个区别是: &&|| 只会计算它们需要的项(这通常被称为短路)。例如,下面是一个使用未定义的值 a 进行比较的例子;如果它没有进行短路操作,就像 &| 一样,它会产生一个错误。

a
# Error: object 'a' not found
TRUE || a
# [1] TRUE
FALSE && a
# [1] FALSE
TRUE | a
# Error: object 'a' not found
FALSE & a
# Error: object 'a' not found

最后,请参见《The R Inferno》第8.2.17节,标题为"and and andand"。


我正在比较长度为1的逻辑值。文档没有明确说明为什么它更适合控制流。这是因为它使用了@Theo答案中的“短路”,从而具有更好的性能吗? - Suraj
不行。只使用短形式'&'--短路答案是不正确的。 - M. Tibbits
2
不行,因为它保证只有一个TRUE/FALSE答案。较短的形式可能会导致c(TRUE, FALSE),并且if语句就不清楚了。如果您确定所有内容都是长度为1的,则可以使用任何一种形式,并且您正确地选择了“短路”的原因。但请注意,确保您百分之百确定它们只能是长度为1。否则,您可能会遇到非常奇怪的错误。 - Aaron left Stack Overflow
11
是的,短路计算是它成为流程控制首选的原因。除了更好的性能外,您可能不想评估所有参数。在?is.R中给出了典型的例子,用于检查您是否正在运行R或S-Plus。if(exists(“is.R”)&& is.function(is.R)&& is.R())。如果is.R不存在,则不要评估is.function(is.R),因为它会抛出错误。同样,如果is.R不是函数,则不要像调用函数一样调用它。 - Richie Cotton
3
在当前版本的《R地狱》中,相关章节现在是8.2.17“and and andand”。 - Silverfish
显示剩余3条评论

49
关于短路求值的答案可能具有误导性,但也有一些真相(见下文)。在R / S语言中,&&||仅评估第一个参数中的第一个元素。无论第一个元素的值如何,向量或列表中的所有其他元素都将被忽略。这些运算符旨在与if(cond){} else {} 构造一起工作,并引导程序控制,而不是构造新的向量。 &|运算符旨在处理向量,因此它们将“并行”地应用于最长参数的长度。在进行比较之前,需要评估两个向量。如果向量的长度不同,则对较短参数进行循环使用。
当评估&&||的参数时,存在“短路求值”的情况,即如果从左到右连续的任何值是决定性的,则评估停止,并返回最终值。
> if( print(1) ) {print(2)} else {print(3)}
[1] 1
[1] 2
> if(FALSE && print(1) ) {print(2)} else {print(3)} # `print(1)` not evaluated
[1] 3
> if(TRUE && print(1) ) {print(2)} else {print(3)}
[1] 1
[1] 2
> if(TRUE && !print(1) ) {print(2)} else {print(3)}
[1] 1
[1] 3
> if(FALSE && !print(1) ) {print(2)} else {print(3)}
[1] 3

短路的优点只有在参数需要长时间计算时才会出现。这通常发生在处理较大对象或具有更复杂数学运算的函数作为参数时。更新:最新版本的news(“R”)指出,向&&||提供长度大于1的向量已不推荐使用,并且RCore的意图是在R的后续版本中将其变为错误。

“短路计算”对我来说是一个新术语,但我认为描述它的答案与您关于 &&|| 的说法相符。” - Aaron left Stack Overflow
它们对于长度大于1的向量不相等 - M. Tibbits
2
如果&&的参数是函数且第一个参数为false,则不会评估第二个参数。但对于&ifelse,这种情况并不成立,它们将评估两个参数。 - IRTFM
这不就是Theo关于短路的答案所说的吗? - Aaron left Stack Overflow
1
@dez93 当“&&”之前是TRUE时,并不意味着它是决定性的。而且,这些并不是真正返回1,而是在分别返回2和3之前,1作为print函数评估的副作用被打印出来。1被打印出来是为了表明在那一点上TRUE并不是决定性的。第5个实例中没有将1打印到控制台,因为FALSE对于“&&”表达式是决定性的,因为第二个参数的值并不重要。如果我将if子句的结果分配给一个命名变量,我们可以更清楚地看到情况。 - IRTFM
显示剩余7条评论

32

&&|| 被称为“短路”。这意味着,如果第一个操作数足以确定表达式的值,则它们不会计算第二个操作数。

例如,如果 && 的第一个操作数为 false,则没有必要计算第二个操作数,因为它无法更改表达式的值(false && truefalse && false 都是 false)。当第一个操作数为 true 时,|| 同样适用。

您可以在此处阅读更多信息:http://en.wikipedia.org/wiki/Short-circuit_evaluation。从该页面的表格中,您可以看到 && 相当于 VB.NET 中的 AndAlso,我假设您正在引用它。


3
这应该足以证明它正在发生短路:f <- function() { print('hello'); TRUE }; FALSE && f()。将其改为 & 并注意到函数被评估。QED. (翻译者注:QED 是拉丁文 "quod erat demonstrandum" 的缩写,意思是“证毕”。) - Theo
2
Theo,是的,你说的&&||会短路是正确的。但在短格式和长格式之间的比较中,这只是一个相对较小的点;更重要的是要理解每个输入向量时它们各自的作用。 - Aaron left Stack Overflow
2
@MTibbits 实际上这不是一个完整的答案,但是关于短路的说法是正确的。尝试使用 F & {message("Boo!");T}F && {message("Boo!");T} - mbq

4

运算符&&/||&/|之间有三个相关的差异,这些差异在官方文档中有解释。以下是摘要:

1. &|向量化的

这意味着如果您想对向量执行逐元素逻辑操作,则应使用&|

a = c(TRUE, TRUE, FALSE, FALSE)
b = c(TRUE, FALSE, TRUE, FALSE)

a | b
# [1]  TRUE  TRUE  TRUE FALSE

a || b
# [1] TRUE

&&|| 逻辑运算符在使用时会丢弃第一个元素后面的所有元素。最近版本的 R 在对长度大于1的向量使用这两个运算符时会生成一个有用的警告信息:

In a || b : 'length(x) = 4 > 1' in coercion to 'logical(1)'

2. &&|| 是被短路的

被短路指的是只有在左侧无法决定表达式结果时,才会对右侧进行求值。几乎所有编程语言都在条件操作中这样做,因为它可以在写if条件语句时使用便捷的惯用语,例如:

if (length(x) > 0L && x[1L] == 42) …

这段代码依赖于短路计算:如果没有短路计算,那么如果x为空,代码将失败,因为右侧尝试访问不存在的元素。如果没有短路计算,我们将不得不使用嵌套的if块,导致更冗长的代码:

if (length(x) > 0L) {
    if (x[1L] == 42) …
}

作为一般规则,在条件表达式(ifwhile)中,即使不需要短路,您也应该始终使用&&||。这更符合惯用法,并导致更统一的代码。
3. &|可以执行位运算
在许多(大多数?)编程语言中,&|实际上执行位运算而不是布尔运算。也就是说,对于两个整数aba & b计算按位与a | b计算按位或。对于布尔值,按位和逻辑操作之间没有区别;但对于任意整数,结果不同。例如,在大多数编程语言中,1 | 2 == 3
然而,在R中,这是不正确的:R将&|的数字参数强制转换为逻辑值并执行布尔运算。
...除非两个参数都是raw类型:
c(1, 3) | c(2, 4)
# [1] TRUE TRUE

as.raw(c(1, 3)) | as.raw(c(2, 4))
# [1] 03 07

值得注意的是,当使用 raw 参数调用操作符 !(逻辑非)和 xor 时,它们也会执行位运算。

1
changelog以来,自 R 4.2.0 版本开始,使用长度大于一的参数调用&&||将产生警告:
调用 && 或 || 时,如果任一参数的长度大于一,现在会产生警告(预计将变为错误)。
并且自4.3.0版本以来,这将是一个错误:
调用 && 或 || 时,如果左侧或(如果被评估)右侧的长度大于一,现在总是会报错,报告形式如下: 'length = 4' in coercion to 'logical(1)' 环境变量 R_CHECK_LENGTH_1_LOGIC2 不再起作用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接