如何在Python中获取两个变量的逻辑异或?

967

在Python中,如何获得两个变量的逻辑异或

例如,我有两个变量,我希望它们都是字符串。我想测试只有其中一个包含True值(不是None或空字符串):

str1 = raw_input("Enter string one:")
str2 = raw_input("Enter string two:")
if logical_xor(str1, str2):
    print "ok"
else:
    print "bad"

^ 运算符是按位运算符,不适用于所有对象:
>>> 1 ^ 1
0
>>> 2 ^ 1
3
>>> "abc" ^ ""
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for ^: 'str' and 'str'

6
你如何定义一对字符串的“异或(xor)”操作?你认为对于“abc” ^ ""这个操作,它应该返回什么而实际上它却不返回这个值? - Mehrdad Afshari
24
由于根据Python的bool类型定义,只有其中一个字符串是True,因此应该返回True而不是引发异常。 - Zach Hirsch
71
我惊讶于Python没有一个称为“异或”的中缀运算符,这将是最直观、最符合Python风格的实现。使用“^”与其他语言保持一致,但不如Python的大部分代码那样易读。 - Mark E. Haase
20
你的问题的明显答案是,a xor a 被定义为 (a and not b) or (not a and b),因此当 ab 为字符串或任何其他类型时,a xor b 应该产生 (a and not b) or (not a and b) 的结果。 - Kaz
2
问题在于文档不够详细。"^"是按位异或,字面意思是逐位进行,而不是逐个布尔值进行。因此,x'FFFF00' ^ x'FFFF00'应该是x'000000'。或者这只是针对char的逐个比较?需要将其转换为数字?我们需要迭代较短字符串的字符以匹配较长字符串的长度。所有这些都应该内置。 - mckenzm
如果这有帮助的话,int("0xFFFF00", 16) ^ int("0xFFFF00", 16) == int(0),所以你必须将其转换为数字并在结果上调用 hex,然后在左侧填充足够多的 0 以达到最长长度。 - Guimoute
29个回答

12

我没有看到使用可变参数并仅对真值True或False进行操作的异或操作的简单变体,因此我将在此提供它以供任何人使用。 正如其他人指出的那样,它非常(甚至可以说非常)直截了当。

def xor(*vars):
    result = False
    for v in vars:
        result = result ^ bool(v)
    return result

使用也很简单:

if xor(False, False, True, False):
    print "Hello World!"

由于这是广义的n元逻辑异或,只有当True操作数的数量为奇数时(不仅仅是当只有一个为True时),它的真值才为True。

因此,如果你正在寻找一个只有在它的操作数中恰好有一个为True时才为True的n元谓词,你可能想要使用:

def isOne(*vars):
    result = False
    for v in vars:
        if result and v:
            return False
        else:
            result = result or v
    return result

为了改进这个答案:(bool(False) is False) == True。你可以在这些行上直接使用 False - pathunstrom

10

有奖问答:

另一个想法......你可以尝试使用“不是”(可能是)这个Pythonic表达式来获得逻辑“异或”的行为

真值表如下:

>>> True is not True
False
>>> True is not False
True
>>> False is not True
True
>>> False is not False
False
>>>

而对于您的示例字符串:

>>> "abc" is not  ""
True
>>> 'abc' is not 'abc' 
False
>>> 'abc' is not '' 
True
>>> '' is not 'abc' 
True
>>> '' is not '' 
False
>>> 

然而,正如他们上面所指出的,这取决于您想从任何一对字符串中提取出的实际行为,因为字符串不是布尔值...更重要的是:如果您“Dive Into Python”,您将发现“and”和“or”的奇特性质。

http://www.diveintopython.net/power_of_introspection/and_or.html

抱歉我的英语写作,这不是我的母语。

敬礼。


我曾经也把它读作“严格不同”。这是因为一些语言使用按位实现二进制运算并取结果的布尔值。我猜你的答案更“类型防弹”,因为它超越了布尔空间。 - yucer
我的意思是你的答案涵盖了将None、False和''进行比较的情况,这是独特的。例如:bool(False) != bool(''),尽管False不是'',但更符合“严格不同”的语义。 - yucer

9
您使用与C语言相同的XOR运算符,即^
我不知道为什么,但最受欢迎的解决方案建议使用bool(A) != bool(B),而我会说 - 与C的^运算符相一致,最明显的解决方案是:
bool(A) ^ bool(B)

对于那些使用C或类似C语言的人来说,更易读且立即理解...

在进行代码高尔夫时,可能会使用。

not A ^ (not B)

使用 not 作为布尔值的转换器,将会成为胜者(比 bool() 少一个字母)。在某些情况下,可以省略第一个表达式中的括号。当需要执行 not(A) ^ (not(B)) 时,则需要使用 bool() 的相同字母数...


1
“not not A” 是一种不需要函数调用就能得到与“bool(A)”相同结果的方法。 - Kiran Jonnalagadda

8

我知道这有点晚了,但我有一个想法,也许值得记录一下。或许这个方法可以行:np.abs(x-y)。这个想法是:

  1. 如果x=True=1而y=False=0,则结果为|1-0|=1=True
  2. 如果x=False=0而y=False=0,则结果为|0-0|=0=False
  3. 如果x=True=1而y=True=1,则结果为|1-1|=0=False
  4. 如果x=False=0而y=True=1,则结果为|0-1|=1=True

你甚至可以省略 abs,因为 Python 将负数解释为真值,尽管在我看来这非常晦涩(if (x > 1) - (y > 3) 是什么意思?)。 - Nearoo

8

许多人,包括我自己,需要一个行为类似于n输入异或电路的xor函数,其中n是可变的。(请参见https://en.wikipedia.org/wiki/XOR_gate)。下面这个简单的函数实现了这个功能。

def xor(*args):
   """
   This function accepts an arbitrary number of input arguments, returning True
   if and only if bool() evaluates to True for an odd number of the input arguments.
   """

   return bool(sum(map(bool,args)) % 2)

以下是样例输入输出:

In [1]: xor(False, True)
Out[1]: True

In [2]: xor(True, True)
Out[2]: False

In [3]: xor(True, True, True)
Out[3]: True

我通常更喜欢使用生成器推导式而不是使用map,因为它们通常更快,并且(对于我个人来说)通常更明确,所以我会使用bool(sum(bool(a) for a in args) % 2)。另一方面,我认为map在使用内置函数(如bool)时略微更快。总之,这是一个好的解决方案+1。 - Alex Potapenko

7

这里建议的一些实现在某些情况下会导致操作数重复评估,可能会导致意外的副作用,因此必须避免。

话虽如此,一个返回TrueFalsexor实现相当简单;如果可能的话,返回其中一个操作数则要困难得多,因为没有共识可以选择哪个操作数,特别是当有两个以上的操作数时。例如,xor(None, -1, [], True)应该返回None[]还是False?我敢打赌每个答案都会被一些人认为是最直观的。

对于真结果或假结果,有多达五种可能的选择:返回第一个操作数(如果它与最终结果匹配,则返回布尔值),返回第一个匹配项(如果至少存在一个,则返回布尔值),返回最后一个操作数(如果...否则...),返回最后一个匹配项(如果...否则...),或始终返回布尔值。总共有 5 ** 2 = 25 种xor的口味。

def xor(*operands, falsechoice = -2, truechoice = -2):
  """A single-evaluation, multi-operand, full-choice xor implementation
  falsechoice, truechoice: 0 = always bool, +/-1 = first/last operand, +/-2 = first/last match"""
  if not operands:
    raise TypeError('at least one operand expected')
  choices = [falsechoice, truechoice]
  matches = {}
  result = False
  first = True
  value = choice = None
  # avoid using index or slice since operands may be an infinite iterator
  for operand in operands:
    # evaluate each operand once only so as to avoid unintended side effects
    value = bool(operand)
    # the actual xor operation
    result ^= value
    # choice for the current operand, which may or may not match end result
    choice = choices[value]
    # if choice is last match;
    # or last operand and the current operand, in case it is last, matches result;
    # or first operand and the current operand is indeed first;
    # or first match and there hasn't been a match so far
    if choice < -1 or (choice == -1 and value == result) or (choice == 1 and first) or (choice > 1 and value not in matches):
      # store the current operand
      matches[value] = operand
    # next operand will no longer be first
    first = False
  # if choice for result is last operand, but they mismatch
  if (choices[result] == -1) and (result != value):
    return result
  else:
    # return the stored matching operand, if existing, else result as bool
    return matches.get(result, result)

testcases = [
  (-1, None, True, {None: None}, [], 'a'),
  (None, -1, {None: None}, 'a', []),
  (None, -1, True, {None: None}, 'a', []),
  (-1, None, {None: None}, [], 'a')]
choices = {-2: 'last match', -1: 'last operand', 0: 'always bool', 1: 'first operand', 2: 'first match'}
for c in testcases:
  print(c)
  for f in sorted(choices.keys()):
    for t in sorted(choices.keys()):
      x = xor(*c, falsechoice = f, truechoice = t)
      print('f: %d (%s)\tt: %d (%s)\tx: %s' % (f, choices[f], t, choices[t], x))
  print()

7

异或(Exclusive Or)的定义如下:

def xor( a, b ):
    return (a or b) and not (a and b)

2
对于 xor('this', ''),应该返回 True,并且遵循 Python 的方式,应该返回 'this'。 - nosklo
请向BDFL提出此问题,而不是向我提问。既然Python返回True,那么这一定是Python的方式。 - S.Lott
2
我的意思是为了与其他Python逻辑运算符保持一致 - 当我执行('this' or '')时,Python不会返回True,而是返回'this'。但是在你的函数中,xor('this', '')返回True。它应该像Python内置的"or"一样返回'this'。 - nosklo
11
Python中的andor运算符支持短路求值。任何xor实现都不能使用短路求值,因此已经存在差异;因此,没有理由让xor的行为类似于and+or - tzot

7
有时我会使用1和0而不是布尔值True和False。在这种情况下,异或可以定义为:
z = (x + y) % 2

这个真值表如下:

     x
   |0|1|
  -+-+-+
  0|0|1|
y -+-+-+
  1|1|0|
  -+-+-+

6

以下是我编写任何真值表的代码方式。对于异或运算,我们有:

| a | b  | xor   |             |
|---|----|-------|-------------|
| T | T  | F     |             |
| T | F  | T     | a and not b |
| F | T  | T     | not a and b |
| F | F  | F     |             |

只需查看答案列中的 T 值,并使用逻辑或将所有为真的情况串起来。因此,该真值表可能在第 2 或第 3 种情况下产生。因此,

xor = lambda a, b: (a and not b) or (not a and b)

6
这个怎么样?
(not b and a) or (not a and b)

如果b为假,则返回a
如果a为假,则返回b
否则,返回False

或者使用Python 2.5+的三元表达式:

(False if a else b) if b else a

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