如何在不进行类型转换的情况下确定一个布尔值是否为真且只有一个?

41

给定一个任意的布尔值列表,最优雅的方法是什么,以确定其中恰好有一个为true?

最明显的方法是类型转换:将它们转换为false0true1,然后将它们相加,并返回sum==1

我想知道是否有一种方法可以在不将它们转换为整数的情况下,实际使用布尔逻辑来完成这个任务。

(这似乎应该是微不足道的,我不知道,这是漫长的一周)

编辑:如果这不是很明显,这更像一个代码高尔夫或理论问题。我并不关心在PROD代码中使用类型转换/整数加法,我只是想知道是否有一种方法可以在不进行这样的操作的情况下完成。

编辑2:抱歉朋友们,这是一个漫长的一周,我表达得不好。让我试试这个:

在布尔逻辑中,ANDing一组布尔值是真的,如果所有布尔值都为真,则ORing集合是真的,如果至少有一个为真,则XOR是这个集合中恰好一个布尔值为真的逻辑构造。如果有两个布尔值的集合,但超过两个就不行了。


2
转换是最优雅的方法。迄今为止。 - RBarryYoung
我想知道是否还有其他方法。我已经写了带类型转换的代码。如果答案是“你不能用布尔逻辑做到这一点”,那么也可以这样回答。 - SCdF
1
为什么XOR不适合您?它只有在一个为真的情况下才会评估为真。 - Shiva Kumar
2
好的,我意识到使用异或运算符时,“true and true and true”将评估为“true”,这不符合您的要求。 - Shiva Kumar
@Shiva - 我在想指出你刚刚意识到的 true ^ true ^ true 的事实时,不小心点了一下你第一个 xor 评论的赞同。无论如何,请忽略这个赞!=) - c.fogelklou
显示剩余4条评论
18个回答

14

实际上,您可以只使用布尔逻辑来完成这项任务,尽管在您的示例中可能没有实际价值。布尔版本比仅计算真值数量更复杂。

无论如何,为了满足知识上的好奇心,我们来看一下。首先,使用一系列异或的想法很好,但它只能让我们走了一半路。对于任意两个变量xy

xy

当且仅当其中一个为真时,该式子成立。然而,如果你增加第三个变量z,这种情况就不再成立,

xyz

第一部分xy仍然成立,当且仅当xy中恰有一个为真。如果xy中任何一个为真,则整个表达式需要z为假以使其为真,这正是我们想要的。但是请考虑一下如果xy都为真会发生什么。那么xy为假,但是如果z也为真,则整个表达式可以变为真。因此,一个变量或全部三个变量必须为真。通常,如果您有一个由XOR链接的语句,则当奇数个变量为真时它将为真。

由于1是奇数,因此这可能证明有用。当然,仅检查真实数量是不够的。我们还需要确保最多只有一个变量为真。可以通过成对方式进行,将所有两个变量配对并检查它们是否都为真。这两个条件共同确保仅有一个变量为真。

下面是一个小的Python脚本来说明这种方法。

from itertools import product

print("x|y|z|only_one_is_true")
print("======================")
for x, y, z in product([True, False], repeat=3):
    uneven_number_is_true = x ^ y ^ z
    max_one_is_true = (not (x and y)) and (not (x and z)) and (not (y and z))
    only_one_is_true = uneven_number_is_true and max_one_is_true
    print(int(x), int(y), int(z), only_one_is_true)

这里是输出结果。

x|y|z|只有一个为真
======================
1 1 1 False
1 1 0 False
1 0 1 False
1 0 0 True
0 1 1 False
0 1 0 True
0 0 1 True
0 0 0 False

1
这似乎无法很好地扩展到4,5,...个输入。看起来你需要# inputs choose 2个操作数来计算max_one_is_true - TWiStErRob
这个解决方案可以更容易理解,不使用 x ^ y ^ z 来测试真值的数量是否为奇数,而是只需使用 x or y or z 确保至少有一个为真。 - R. Schreurs
@TWiStErRob,成对数量随着n(n-1)/2呈二次增长。在我看来,这并不算太糟糕。 - R. Schreurs

5
没有人提到我们正在寻找的“操作”可以像大多数语言中的布尔AND和OR一样进行快捷操作。 这是Java中的一个实现:
public static boolean exactlyOneOf(boolean... inputs) {
    boolean foundAtLeastOne = false;
    for (boolean bool : inputs) {
        if (bool) {
            if (foundAtLeastOne) {
                // found a second one that's also true, shortcut like && and ||
                return false;
            }
            foundAtLeastOne = true;
        }
    }
    // we're happy if we found one, but if none found that's less than one
    return foundAtLeastOne;
}

5
当然,你可以像这样做(伪代码,因为你没有提到语言):
found = false;
alreadyFound = false;
for (boolean in booleans):
    if (boolean):
        found = true;
        if (alreadyFound):
            found = false;
            break;
        else:
            alreadyFound = true;
return found;

5

在您的澄清之后,这里没有整数。

 bool IsExactlyOneBooleanTrue( bool *boolAry, int size )
    {
      bool areAnyTrue = false;
      bool areTwoTrue = false;
      for(int i = 0; (!areTwoTrue) && (i < size); i++) {
        areTwoTrue = (areAnyTrue && boolAry[i]);
        areAnyTrue |= boolAry[i];
      }
      return ((areAnyTrue) && (!areTwoTrue));
    }

有趣的实现break关键字的方式。你是想避免分支吗? - TWiStErRob
@TWiStErRob,你的意思是因为没有break语句吗?主要原因是为了可读性。这样做可以使所有退出条件在循环开始时显而易见;它让读者知道哪些条件会导致循环退出(从而知道循环的目的)。 - c.fogelklou
大多数areTwoTrue的用法都是为了停止循环。我猜这是我们习惯的/语言约定的方式(C++ v Java)。我认为我的方法也很易读(忽略如何迭代数组,那是特定于语言的):它清楚地显示我们只关心数组中的“true”值,并且我们将在第二个值处停止。我猜圆形复杂度是相似的,只是使用if|== &&更普遍。好奇你的想法。 - TWiStErRob
两种方式都可以,只是个人偏好而已。我更喜欢从while/for语句中读取循环退出的原因,而不是去查看循环体内部。当然,有时在循环内使用break或return可以使代码更易读。各有所好。(你说得对,“我的”版本可能会导致较少的分支,但如果编译器聪明的话,你的和我的版本最终生成的硬件代码可能是相同的。) - c.fogelklou

4

booleanList.Where(y => y).Count() == 1;


2
booleanList.Where(y => y).Take(2).Count() == 1; 布尔列表.Where(y => y).Take(2).Count() == 1; - Morten Gorm Madsen

4

仅凭布尔逻辑,可能无法实现您想要的功能。因为您所要求的是一种真实性评估,不仅基于真值,还基于其他信息(在这种情况下是计数)。但是布尔运算是二进制逻辑,它不能依赖于除操作数本身之外的任何东西。而且没有办法通过真值来逆向工程找到操作数,因为操作数有四种可能的组合,但只有两个结果。在您的情况下,给定一个假值,您能否确定它是由 F ^ F 还是 T ^ T 导致的?以便下一次评估可以根据此进行确定?


1
不是真的。c.fogelklou的答案确实可以被解释为普通的布尔逻辑。理论上,SCdF正在寻求一个具有多个参数的布尔函数,我们知道任何布尔函数都可以仅使用合取和否定来实现。 - took
通过循环,总是可以找出是否有多个布尔值为真。我相信提问者已经知道这一点。但据我所知,当提问者最初提出问题时,他想要一个优雅的答案,而不是通过循环或直接使用布尔逻辑(如XOR或类似的东西)来返回仅在一个元素为真时直接返回true。 - Shiva Kumar

3

由于现在的阅读量很大,因此这里需要快速清理并提供额外信息。

选项1:

询问是否只有第一个变量为真,或只有第二个变量为真,...或只有第n个变量为真。

x1 & !x2 & ... & !xn |
!x1 & x2 & ... & !xn |
...
!x1 & !x2 & ... & xn

这种方法的复杂度为O(n^2),在发现第一个正匹配后就会停止评估。因此,如果可能存在正匹配,则更可取。

选项2:

询问是否总共至少有一个变量为真。此外,检查每对中最多包含一个真变量(Anders Johannsen的答案)。

(x1 | x2 | ... | xn) &
(!x1 | !x2) &
...
(!x1 | !xn) &
(!x2 | !x3) &
...
(!x2 | !xn) &
...

由于可能出现的对数对数量,该选项的复杂度为O(n^2)。通过惰性计算,在第一个反例后停止公式。因此,如果有可能存在负匹配,则更喜欢使用它。

(选项3):

该选项涉及减法,因此不适用于受限设置的有效答案。尽管如此,它表明在无限制的情况下,循环值可能不是最有益的解决方案。

将x1 ... xn视为二进制数x。减去1,然后AND结果。输出为零当且仅当x1 ... xn最多包含一个true值。(旧的“检查2的幂”算法)

x    00010000
x-1  00001111
AND  00000000

如果位已经存储在位板中,使用这种方法可能比循环更有益。但请记住,这会降低可读性,并受可用板长度的限制。
最后提醒一下:现在已经存在一个名为计算机科学的堆栈交换平台,专门用于此类算法问题。

2

可以使用递归来完成,例如在Haskell中

-- there isn't exactly one true element in the empty list
oneTrue [] = False 
-- if the list starts with False, discard it
oneTrue (False : xs) = oneTrue xs
-- if the list starts with True, all other elements must be False
oneTrue (True : xs) = not (or xs)

2

Python:

boolean_list.count(True) == 1

2

// Javascript 在数组上使用.filter()方法并检查新数组的长度。

// Example using array
isExactly1BooleanTrue(boolean:boolean[]) {
  return booleans.filter(value => value === true).length === 1;
}

// Example using ...booleans
isExactly1BooleanTrue(...booleans) {
  return booleans.filter(value => value === true).length === 1;
}

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