Certaines des implémentations suggérées ici entraîneront une évaluation répétée des opérandes dans certains cas, ce qui peut entraîner des effets secondaires involontaires et doit donc être évité.
Cela dit, une xor
implémentation qui renvoie soit True
ou False
est assez simple; celui qui renvoie l'un des opérandes, si possible, est beaucoup plus délicat, car il n'y a pas de consensus quant à l'opérande qui doit être choisi, surtout lorsqu'il y a plus de deux opérandes. Par exemple, devrait xor(None, -1, [], True)
revenir None
, []
ou False
? Je parie que chaque réponse apparaît à certaines personnes comme la plus intuitive.
Pour le résultat True ou False, il y a jusqu'à cinq choix possibles: retourner le premier opérande (s'il correspond au résultat final en valeur, sinon booléen), retourner la première correspondance (s'il en existe au moins un, sinon booléen), retourne le dernier opérande (si ... sinon ...), retourne la dernière correspondance (si ... sinon ...), ou retourne toujours booléen. Au total, cela fait 5 ** 2 = 25 saveurs de 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()