Как вы получаете логический xor двух переменных в Python?


648

Как вы получаете логический xor двух переменных в 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'

3
Как вы определяете "xor" для пары строк? Что, по вашему мнению, "abc" ^ "" должно вернуть, а что нет?
Мердад Афшари

18
Он должен возвращать True, а не вызывать исключение, поскольку только одна из строк True, как определено типом bool обычного Python.
Зак Хирш

38
Я поражен тем, что в Python нет инфиксного оператора под названием «xor», который был бы наиболее интуитивной, Pythonic реализацией. Использование «^» совместимо с другими языками, но не так явно читаемо, как большинство языков Python.
Марк Э. Хааз

13
@MehrdadAfshari Очевидный ответ на ваш вопрос заключается в том, что a xor aон определен как (a and not b) or (not a and b), и поэтому a xor b, когда aи bявляются символьными строками, или любыми другими типами, они должны давать любые результаты (a and not b) or (not a and b).
Каз

1
Проблема в том, что документация плохая. ^ - «поразрядное исключение или», что буквально означает «по крупицам», а не «bool by bool». поэтому x'FFFF00 '^ x'FFFF00' должно быть x'000000 '. Или это должно происходить только на символе за символом? лить как числа? Нам нужно перебрать более короткие строковые символы, чтобы соответствовать длине более длинной строки. Все это должно быть встроено.
mckenzm

Ответы:


1188

Если вы уже нормализуете входные данные для логических значений, тогда! = Is xor.

bool(a) != bool(b)

148
Хотя это умно и кратко, я не уверен, что это чисто. Когда кто-то читает эту конструкцию в коде, для них сразу становится очевидным, что это операция xor? Я чувствовал себя обязанным добавить комментарий - признак того, что я пишу неясный код и пытаюсь извиниться с комментарием.

47
Возможно, "ясно, что это XOR?" это неправильный вопрос. Мы просто пытались понять, одинаковы ли ответы на два вопроса, и думали, что мы будем использовать XOR для их реализации. Например, если мы хотим убедиться, что мы не сравниваем яблоки с апельсинами, действительно ли «если xor (isApple (x), isApple (y))» действительно яснее, чем «if isApple (x)! = IsApple (y)»? Не мне!
AmigoNico

106
Существует проблема с использованием "! =" В качестве xor. Вы, вероятно, ожидаете, что bool (a)! = Bool (b)! = Bool (c) будет таким же, как bool (a) ^ bool (b) ^ bool (c). Так что делай приведения к булу, но я бы порекомендовал ^. Чтобы узнать, что происходит в первом примере, посмотрите «операторская цепочка».
Элмо

19
@elmo: +1 за указание на разницу и +1 за обучение, что такое операторная цепочка! Я в лагере, который говорит, что! = Не так читабельно, как ^.
Марк Э. Хааз

13
должно быть bool(a) is not bool(b)вместо этого?
РНК,

485

Вы всегда можете использовать определение xor для вычисления его из других логических операций:

(a and not b) or (not a and b)

Но для меня это слишком многословно и на первый взгляд не совсем понятно. Еще один способ сделать это:

bool(a) ^ bool(b)

Оператор xor на двух логических значениях является логическим xor (в отличие от целых, где он побитовый). Что имеет смысл, так как boolэто просто подклассint , но реализован так, чтобы иметь только значения 0и 1. И логический xor эквивалентен побитовому xor, когда домен ограничен 0и 1.

Таким образом, logical_xorфункция будет реализована так:

def logical_xor(str1, str2):
    return bool(str1) ^ bool(str2)

Благодарим Ника Коглана из списка рассылки Python-3000 .


7
отличный пост, но из всех способов назвать ваши параметры, почему 'str1' и 'str2'?
SingleNegationElimination

1
@ Так почему бы и нет. Вы имеете в виду, потому что они не очень Pythonic?
орокусаки

1
@ Zach Hirsch Могли бы вы использовать (не a и b) вместо (b и не a) для удобства чтения, или определение будет несовместимо с xor.
Орокусаки

10
Сначала вы должны поместить nots, например, (not b and a) or (not a and b)так, чтобы он возвращал строку, если она была, что похоже на питонный способ работы функции.
Rjmunro

2
@TokenMacGuy: Что вы предлагаете ему назвать вместо этого?
user541686

180

Побитовое исключение или уже встроено в Python в operatorмодуле (который идентичен ^оператору):

from operator import xor
xor(bool(a), bool(b))  # Note: converting to bools is essential

3
Это то, что мне было нужно. При реверс-инжиниринге вредоносных программ много раз строки искажаются до операции XOR. Используя этот chr (xor (ord ("n"), 0x1A)) = 't'
ril3y

75
Будьте осторожны, это тоже побитово: xor(1, 2)возвращает 3. Из строки документации: xor(a, b) -- Same as a ^ b. Помните, что все, что импортируется из, operatorявляется просто функциональной формой существующего встроенного инфиксного оператора.
Askewchan

5
@askewchan: boolтип перегружается __xor__для возврата логических значений. Это будет работать просто отлично, но это излишне, когда bool(a) ^ bool(b)делает то же самое.
Мартин Питерс

@MartijnPieters ^Оператор вызывает __xor__внутри страны.
Quantum7

5
@ Quantum7: да, я не уверен, почему вы говорите мне это, хотя. Я только что сказал, что boolтип реализует __xor__метод именно потому, что ^вызывает его . Дело в том, что это bool(a) ^ bool(b)работает нормально, здесь нет необходимости использовать operator.xor()функцию.
Мартин Питерс

43

Как объяснил Зак , вы можете использовать:

xor = bool(a) ^ bool(b)

Лично я предпочитаю немного другой диалект

xor = bool(a) + bool(b) == 1

Этот диалект навеян языком логических диаграмм, который я выучил в школе, где «ИЛИ» обозначалось полем, содержащим ≥1(больше или равным 1), а «XOR» обозначалось полем, содержащим =1.

Это имеет преимущество правильной реализации исключительного или нескольких операндов.

  • «1 = a ^ b ^ c ...» означает, что число истинных операндов нечетно. Этот оператор "паритет".
  • «1 = a + b + c ...» означает, что истинно только один операнд. Это «исключительный или», что означает «один за исключением других».

12
Итак, True + True + False + True == 3 и 3! = 1, но True XOR True XOR False XOR True == True. Можете ли вы рассказать о «правильной реализации XOR для нескольких операндов»?
tzot

3
@tzot Ваш пример терпит неудачу, потому что согласно решению ddaa вы применяете сложение только к двум переменным одновременно. Таким образом, правильный способ записать все это должно быть (((((True + True)==1)+False)==1)+True)==1. Ответ, данный здесь, полностью обобщает несколько операндов.
Ely

6
Кроме того, существует разница между трехсторонним XOR и множеством сгруппированных по порядку операций двух XOR. Таким образом, 3-WAY-XOR (A, B, C) - это не то же самое, что XOR (XOR (A, B), C). И пример ddaa - первый, а ваш - второй.
Ely

3
@ Mr.F Ваше объяснение не оправдывает этот ответ. В Python, если вы просто делаете True + True + False + True, вы действительно получите 3, и True + True + False + True == 3возвращает в Trueто время True + True + False + True == 1отдает False. Другими словами, ответ здесь не обобщается правильно; для этого вам нужно проделать дополнительную работу. Между тем простая True ^ True ^ False ^ Trueработа, как и ожидалось.
jpmc26

3
@ jpmc26 Я не понимаю ваш комментарий. Подход сложения предназначен для обобщения операции, в которой вы хотите проверить, является ли ровно один операнд Trueмногофакторным XOR. Это другая операция, чем, например A XOR B XOR ... XOR Z,. Другими словами, если вы планируете использовать версию, основанную на сложении, то после отправки операндов в True + True + False + Trueвас следует ожидать, что результат будет Falseболее одного True, который работает, если условие проверяется == 1.
Ely

26
  • Python логический or: A or Bвозвращает, Aесли bool(A)есть True, иначе возвращаетB
  • Python логический and: A and Bвозвращает, Aесли bool(A)есть False, иначе возвращаетB

Чтобы сохранить большую часть этого мышления, мое логическое определение xor будет:

def logical_xor(a, b):
    if bool(a) == bool(b):
        return False
    else:
        return a or b

Таким образом , он может вернуться a, bили False:

>>> logical_xor('this', 'that')
False
>>> logical_xor('', '')
False
>>> logical_xor('this', '')
'this'
>>> logical_xor('', 'that')
'that'

5
Это кажется плохим или, по крайней мере, странным, для меня. Ни один из других встроенных логических операторов не возвращает одно из трех возможных значений.
Зак Хирш

2
@ Зак Хирш: Вот почему я сказал «сохранять большую часть такого мышления» - так как нет хорошего результата, когда оба являются истинными или ложными
nosklo

Логическая операция должна возвращать логическое значение, поэтому второе «return a или b» выглядит странно, поэтому второе return должно возвращать True.
Денис Барменков

9
@Denis Барменков: Ну, обратите внимание , что питон логические операторы andи orне будет возвращать логическое значение. 'foo' and 'bar'возвращается 'bar'...
nosklo

6
На первый взгляд, два предыдущих ответа кажутся лучшими, но, если подумать, этот ответ действительно единственный правильный, т. Е. Единственный, который дает пример xorреализации, согласующейся со встроенным andи or. Однако, конечно, в практических ситуациях, bool(a) ^ bool(b)или даже a ^ b(если aи bкак известно, будут bool), конечно, более кратким.
Эрик Каплун

23

Я протестировал несколько подходов и not a != (not b)оказался самым быстрым.

Вот несколько тестов

%timeit not a != (not b)
10000000 loops, best of 3: 78.5 ns per loop

%timeit bool(a) != bool(b)
1000000 loops, best of 3: 343 ns per loop

%timeit not a ^ (not b)
10000000 loops, best of 3: 131 ns per loop

Изменить: в примерах 1 и 3 выше отсутствуют круглые скобки, поэтому результат неверен. Новые результаты + truth()функция, как предложил ShadowRanger.

%timeit  (not a) ^  (not b)   # 47 ns
%timeit  (not a) != (not b)   # 44.7 ns
%timeit truth(a) != truth(b)  # 116 ns
%timeit  bool(a) != bool(b)   # 190 ns

6
Это 100 нс моей жизни, я не вернусь ;-)
Арель

4
Для промежуточного времени вы можете выполнить from operator import truthв верхней части модуля и проверить truth(a) != truth(b). boolбудучи конструктор имеет много неизбежных накладных расходов на уровне C (он должен принимать аргументы в качестве эквивалента *args, **kwargsи разбора tupleи dictизвлечь их), где truth(будучи функцией) можно использовать оптимизированный путь , который не требует либо tupleили а dict, и работает в около половины времени boolрешений , основанных (но все еще больше , чем notрешения на основе).
ShadowRanger,

9

Награждение темы:

Идея анодера ... Просто попробуйте (возможно) питонское выражение «нет», чтобы получить логическое поведение «xor»

Таблица истинности будет:

>>> 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», вы найдете «The Pegetarian Nature» и «и» или «» http://www.diveintopython.net/power_of_introspection/and_or.html

Извините за мой английский, это не мой родной язык.

С уважением.


Я также использую, чтобы прочитать это как "строго различный". Это связано с тем, что некоторые языки используют для реализации операции побитовое двоичное представление и принимают значение bool для получающейся побитовой операции. Я предполагаю, что ваш ответ более «пуленепробиваемый», потому что он выходит за пределы логического пространства.
yucer

Я имею в виду тот факт, что ваш ответ охватывает случай сравнения «Нет», «Ложь», «отличия - это отличительная вещь. Например: bool (False)! = Bool (''), тем не менее, False is not '' 'больше согласен с этой семантикой «строго другой»
yucer

8

В Python есть побитовый оператор исключающего ИЛИ, это ^:

>>> True ^ False
True
>>> True ^ True
False
>>> False ^ True
True
>>> False ^ False
False

Вы можете использовать его, преобразовав входные данные в логические значения перед применением xor ( ^):

bool(a) ^ bool(b)

(Отредактировано - спасибо Арель)


Ваш ответ должен прояснить, что ^это битовый xor (не логический xor, как заданный вопрос). bool(2) ^ bool(3)дает другой ответ, чем bool(2 ^ 3).
Арель

1
@ Арель Но это не тот случай. a ^ bэто полиморф. Если aи bесть boolэкземпляры, результат будет boolтакже. Такое поведение вряд ли можно назвать «побитовым» xor.
Альфе

@ Важным моментом является то, что значения должны быть сначала приведены к логическим значениям. Документация Python определяет ^как побитовую, хотя это интересный момент, для которого типы boolи intтипы сохраняются . Примечание: True ^ 2есть 3, демонстрируя, насколько оно действительно побитовое.
Арель

@ Арель Да, bool ^ intдело вроде как все ставит на intпервое место. Тем не менее, Python имеет встроенный в ^операторе для многих бит intи для одного бита представлены в bool, так как это побитовое , но побитовом исключающем для одного бита просто является логическим исключающей для булевыми.
Alfe

Я всегда ненавижу использовать этот оператор, хотя я понимаю, что xor, исходя из инженерного опыта, для меня это инстинктивно ощущается как математическая сила, то есть, 2^3 = pow(2,3)что означает, что я всегда явно комментирую, чтобы избежать путаницы.
Николас Гамильтон

8

Поскольку я не вижу простого варианта xor, использующего переменные аргументы и только операции со значениями True, True или False, я просто брошу его здесь для любого использования. Это, как отмечают другие, довольно (если не сказать очень) просто.

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

И использование также просто:

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

Так как это обобщенный n-арный логический XOR, его значение истины будет True, когда число операндов True нечетно (и не только когда точно один равен True, это всего лишь один случай, когда n-арный XOR равен True).

Таким образом, если вы ищете n-арный предикат, который имеет значение True только тогда, когда есть только один из его операндов, вы можете использовать:

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

Для улучшения этого ответа: (bool(False) is False) == True. Вы можете просто использовать Falseна этих линиях.
pathunstrom

7

Исключительно или определяется следующим образом

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

2
который вернул бы True для xor ('this', '') и, следуя пути python, должен вернуть 'this'.
Носкло

@nosklo: Возьмите это с BDFL, пожалуйста, не я. Поскольку Python возвращает True, то это должно быть способом Python.
S.Lott

2
Я имею в виду согласованность с другими логическими операторами python - Python не возвращает True, когда я делаю («this» или «»), он возвращает «this». Но в вашей функции xor ('this', '') возвращает True. Он должен возвращать 'this', как это делает встроенный python.
Носкло

10
Питон andи orсделать короткое замыкание. Любая xorреализация не может замкнуть накоротко, поэтому уже есть несоответствие; следовательно, нет причины, которая xorдолжна работать как and+ ordo.
tzot

7

Иногда я работаю с 1 и 0 вместо логических значений True и False. В этом случае xor может быть определен как

z = (x + y) % 2

который имеет следующую таблицу истинности:

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

7

Я знаю, что уже поздно, но у меня была мысль, и это может стоить, просто для документации. Возможно, это сработает: 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

7

Просто, легко понять:

sum( (bool(a), bool(b) ) == 1

Если вам нужен эксклюзивный выбор, его можно расширить до нескольких аргументов:

sum( bool(x) for x in y ) % 2 == 1

1
sum(map(bool, y)) % 2 == 1
варварюк

6

Как насчет этого?

(not b and a) or (not a and b)

даст aесли bложь
даст bесли aложь
даст Falseиначе

Или с троичным выражением Python 2.5+:

(False if a else b) if b else a

6

Некоторые из предложенных здесь реализаций будут вызывать повторную оценку операндов в некоторых случаях, что может привести к непреднамеренным побочным эффектам и, следовательно, их следует избегать.

Это сказано, xorреализация , которая возвращает либо Trueили Falseдостаточно просто; тот, который возвращает один из операндов, если это возможно, гораздо хитрее, потому что не существует единого мнения относительно того, какой операнд должен быть выбранным, особенно когда имеется более двух операндов. Например, должен xor(None, -1, [], True)вернуться None, []илиFalse ? Могу поспорить, что каждый ответ кажется некоторым людям наиболее интуитивным.

Для True- или 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()

5

Многим людям, включая меня, нужна xorфункция, которая ведет себя как схема x-n-входа, где 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

5

Чтобы получить логический xor двух или более переменных в Python:

  1. Преобразование входных данных в логические значения
  2. Используйте побитовый оператор xor ( ^или operator.xor)

Например,

bool(a) ^ bool(b)

Когда вы конвертируете входные данные в логические значения, битовый xor становится логическим xor.

Обратите внимание, что принятый ответ неверен: != это не то же самое, что xor в Python из-за тонкости объединения операторов .

Например, xor из трех значений ниже является неправильным при использовании !=:

True ^  False ^  False  # True, as expected of XOR
True != False != False  # False! Equivalent to `(True != False) and (False != False)`

(PS Я попытался отредактировать принятый ответ, чтобы включить это предупреждение, но мое изменение было отклонено.)


4

Это легко, когда вы знаете, что делает XOR:

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

test_data = [
  [False, False],
  [False, True],
  [True, False],
  [True, True],
]

for a, b in test_data:
    print '%r xor %s = %r' % (a, b, logical_xor(a, b))

4

Это получает логический эксклюзивный XOR для двух (или более) переменных

str1 = raw_input("Enter string one:")
str2 = raw_input("Enter string two:")

any([str1, str2]) and not all([str1, str2])

Первая проблема с этой настройкой заключается в том, что она, скорее всего, дважды пересекает весь список и, как минимум, дважды проверит хотя бы один из элементов. Таким образом, это может улучшить понимание кода, но не увеличивает скорость (которая может незначительно отличаться в зависимости от вашего варианта использования).

Вторая проблема с этой настройкой заключается в том, что она проверяет исключительность независимо от количества переменных. Это может поначалу рассматриваться как особенность, но первая проблема становится намного более значимой по мере увеличения числа переменных (если они когда-либо будут).


4

Xor ^в Python. Возвращает:

  • Побитовый xor для целых
  • Логический xor для bools
  • Эксклюзивный союз для наборов
  • Пользовательские результаты для классов, которые реализуют __xor__ .
  • Ошибка типа для неопределенных типов, таких как строки или словари.

Если вы все равно намереваетесь использовать их для строк, приведение к ним boolделает вашу работу однозначной (вы также можете это иметь в виду set(str1) ^ set(str2)).



3

Вот как я бы кодировал любую таблицу истинности. В частности, для xor имеем:

| 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

Мы можем легко найти xor двух переменных, используя:

def xor(a,b):
    return a !=b

Пример:

xor (верно, неверно) >>> верно


1
или xor("hey", "there")>>> Правда, но это не то, что мы хотим
Mayou36
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.