Деякі із запропонованих тут реалізацій спричинять неодноразову оцінку операндів у деяких випадках, що може призвести до непередбачуваних побічних ефектів, і тому їх слід уникати.
Однак, xor
реалізація, яка повертає True
або False
є досить простою; той, який повертає один із операндів, якщо це можливо, набагато складніше, оскільки не існує єдиної думки щодо того, який операнд повинен бути обраним, особливо, коли є більше двох операндів. Наприклад, повинен 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()