Existe-t-il une manière plus élégante d'exprimer ((x == a et y == b) ou (x == b et y == a))?

108

J'essaie d'évaluer ((x == a and y == b) or (x == b and y == a))en Python, mais cela semble un peu bavard. Existe-t-il une manière plus élégante?

LetEpsilonBeLessThanZero
la source
8
Cela dépend du type d'objets x,y, a,b: s'agit-il d'entiers / flottants / chaînes, d'objets arbitraires ou quoi? S'il s'agissait de types intégrés et qu'il était possible de les conserver tous les deux x,yet a,bdans l'ordre trié, vous pourriez éviter la deuxième branche. Notez que la création d'un ensemble entraînera le hachage de chacun des quatre éléments x,y, a,b, ce qui pourrait être trivial ou non ou avoir une implication de performance dépendant entièrement du type d'objets qu'ils sont.
smci
18
Gardez à l'esprit les personnes avec lesquelles vous travaillez et le type de projet que vous développez. J'utilise un peu de Python ici et là. Si quelqu'un a codé l'une des réponses ici, je devrais totalement google ce qui se passe. Votre conditionnel est lisible à peu près quoi qu'il arrive.
Areks
3
Je ne m'embêterais avec aucun des remplaçants. Ils sont plus concis, mais pas aussi clairs (à mon humble avis) et je pense que tout sera plus lent.
Barmar
22
Il s'agit d'un problème XYAB classique.
Tashus
5
Et en général, si vous traitez des collections d'objets indépendants de l'ordre, utilisez sets / dicts / etc. C'est vraiment un problème XY, nous aurions besoin de voir la plus grande base de code dont il est tiré. Je suis d'accord que cela ((x == a and y == b) or (x == b and y == a))peut sembler yukky, mais 1) son intention est limpide et intelligible pour tous les programmeurs non-Python, pas cryptique 2) les interprètes / compilateurs le géreront toujours bien et il ne peut en aucun cas résulter en un code non performant, contrairement au alternatives. Ainsi, «plus élégant» peut aussi avoir de sérieux inconvénients.
smci

Réponses:

151

Si les éléments sont hachables, vous pouvez utiliser des ensembles:

{a, b} == {y, x}
Dani Mesejo
la source
18
@Graham non, car il y a exactement deux éléments dans le jeu de droite. Si les deux sont a, il n'y a pas de b, et si les deux sont b, il n'y a pas de a, et dans les deux cas, les ensembles ne peuvent pas être égaux.
Hobbs
12
D'un autre côté, si nous avions trois éléments de chaque côté et que nous devions tester s'ils pouvaient être jumelés un à un, les ensembles ne fonctionneraient pas. {1, 1, 2} == {1, 2, 2}. À ce stade, vous avez besoin de sortedou Counter.
user2357112 prend en charge Monica
11
Je trouve cela difficile à lire (ne lisez pas le "{}" comme "()"). Assez pour le commenter - et puis le but est perdu.
Édouard
9
Je sais que c'est du python, mais créer deux nouveaux ensembles juste pour comparer les valeurs me semble une exagération ... Définissez simplement une fonction qui le fait et appelez en cas de besoin.
marxin
5
@marxin Une fonction serait encore plus exagérée que 2 constructions d'ensembles simples. Et moins lisible avec 4 arguments.
Gloweye
60

Je pense que le mieux que vous puissiez obtenir est de les regrouper en tuples:

if (a, b) == (x, y) or (a, b) == (y, x)

Ou, peut-être envelopper cela dans une recherche d'ensemble

if (a, b) in {(x, y), (y, x)}

Tout comme cela a été mentionné par quelques commentaires, j'ai fait quelques synchronisations, et les tuples et les ensembles semblent fonctionner de manière identique ici lorsque la recherche échoue:

from timeit import timeit

x = 1
y = 2
a = 3
b = 4

>>> timeit(lambda: (a, b) in {(x, y), (y, x)}, number=int(5e7))
32.8357742

>>> timeit(lambda: (a, b) in ((x, y), (y, x)), number=int(5e7))
31.6169182

Bien que les tuples soient en fait plus rapides lorsque la recherche réussit:

x = 1
y = 2
a = 1
b = 2

>>> timeit(lambda: (a, b) in {(x, y), (y, x)}, number=int(5e7))
35.6219458

>>> timeit(lambda: (a, b) in ((x, y), (y, x)), number=int(5e7))
27.753138700000008

J'ai choisi d'utiliser un ensemble parce que je fais une recherche d'adhésion, et conceptuellement, un ensemble est mieux adapté à ce cas d'utilisation qu'un tuple. Si vous avez mesuré une différence significative entre les deux structures dans un cas d'utilisation particulier, optez pour la plus rapide. Je ne pense pas que la performance soit un facteur ici.

Carcigenicate
la source
12
La méthode du tuple semble très propre. Je serais préoccupé par l'impact sur les performances de l'utilisation d'un ensemble. if (a, b) in ((x, y), (y, x))Mais tu pourrais le faire ?
Brilliand
18
@Brilliand Si vous êtes préoccupé par l'impact sur les performances, Python n'est pas le langage pour vous. :-D
Sneftel
J'aime vraiment la première méthode, deux comparaisons de tuple. Il est facile d'analyser (une clause à la fois) et chaque clause est très simple. Et en plus, il devrait être relativement efficace.
Matthieu M.
Y a-t-il une raison de préférer la setsolution dans la réponse à la solution de tuple de @Brilliand?
user1717828
Un ensemble requiert que les objets soient hachables, donc un tuple serait une solution plus générale avec moins de dépendances. Les performances, même si elles ne sont probablement pas généralement importantes, peuvent également sembler très différentes lorsque vous traitez des objets volumineux avec des contrôles de hachage et d'égalité coûteux.
Dukeling
31

Les tuples le rendent légèrement plus lisible:

(x, y) == (a, b) or (x, y) == (b, a)

Cela donne un indice: nous vérifions si la séquence x, yest égale à la séquence a, bmais ignorons l'ordre. C'est juste définir l'égalité!

{x, y} == {a, b}
Thomas
la source
,crée un tuple, pas une liste. donc (x, y)et (a, b)sont des tuples, les mêmes que x, yet a, b.
kissgyorgy
Je voulais dire "liste" dans le sens de "séquence ordonnée d'éléments", pas dans le sens du listtype Python . Modifié car en effet c'était déroutant.
Thomas
26

Si les articles ne sont pas hachables, mais prennent en charge les comparaisons de commande, vous pouvez essayer:

sorted((x, y)) == sorted((a, b))
jasonharper
la source
Étant donné que cela fonctionne également avec les articles lavables (non?), C'est une solution plus globale.
Carl Witthoft
5
@CarlWitthoft: Non. Il existe des types qui sont hachables mais non triables: complexpar exemple.
dan04
26

La façon la plus élégante, à mon avis, serait

(x, y) in ((a, b), (b, a))

C'est une meilleure façon que d'utiliser des ensembles, c'est-à-dire {a, b} == {y, x}, comme indiqué dans d'autres réponses, car nous n'avons pas besoin de penser si les variables sont hachables.

Wagner Macedo
la source
En quoi cela diffère-t-il de cette réponse précédente ?
scohe001
5
@ scohe001 Il utilise un tuple où la réponse précédente utilise un ensemble. Cette réponse antérieure envisageait cette solution, mais a refusé de l'énumérer comme recommandation.
Brilliand
25

S'il s'agit de chiffres, vous pouvez les utiliser (x+y)==(a+b) and (x*y)==(a*b).

S'il s'agit d'articles comparables, vous pouvez les utiliser min(x,y)==min(a,b) and max(x,y)==max(a,b).

Mais ((x == a and y == b) or (x == b and y == a))est clair, sûr et plus général.

lhf
la source
2
Haha, polynômes symétriques ftw!
Carsten S
2
Je pense que cela crée un risque d'erreurs de débordement.
Razvan Socol
3
C'est la bonne réponse, en particulier la dernière phrase. Restez simple, cela ne devrait pas nécessiter plusieurs nouveaux itérables ou quelque chose comme ça. Pour ceux qui veulent utiliser des ensembles, jetez un oeil à l'implémentation des objets ensemble et imaginez ensuite essayer de l'exécuter en boucle serrée ...
Z4-tier
3
@RazvanSocol OP n'a pas dit quels sont les types de données, et cette réponse qualifie les solutions qui dépendent du type.
Z4-tier
21

Comme généralisation à plus de deux variables, nous pouvons utiliser itertools.permutations. C'est au lieu de

(x == a and y == b and z == c) or (x == a and y == c and z == b) or ...

nous pouvons écrire

(x, y, z) in itertools.permutations([a, b, c])

Et bien sûr la version à deux variables:

(x, y) in itertools.permutations([a, b])
un invité
la source
5
Très bonne réponse. Il convient de souligner ici (pour ceux qui n'ont pas fait grand-chose avec les générateurs auparavant) que cela est très efficace en mémoire, car une seule permutation est créée à la fois, et la vérification "in" s'arrêterait et retournerait True immédiatement après un une correspondance est trouvée.
robertlayton
2
Il convient également de souligner que la complexité de cette méthode est O(N*N!); Pour 11 variables, cela peut prendre plus d'une seconde pour terminer. (J'ai posté une méthode plus rapide, mais elle prend toujours O(N^2)et commence à prendre plus d'une seconde sur les variables 10k; Il semble donc que cela puisse être fait rapidement ou généralement (par rapport à l'habilité / ordre), mais pas les deux: P)
Aleksi Torhamo
15

Vous pouvez utiliser des tuples pour représenter vos données, puis vérifier l'inclusion de l'ensemble, comme:

def test_fun(x, y):
    test_set = {(a, b), (b, a)}

    return (x, y) in test_set
Nils Müller
la source
3
Écrit en une ligne et avec une liste (pour les articles non lavables), je considère que c'est la meilleure réponse. (bien que je sois un débutant en Python).
Édouard
1
@ Édouard Maintenant, il y a une autre réponse qui est essentiellement cela (juste avec un tuple au lieu d'une liste, qui est de toute façon plus efficace).
Brilliand
10

Vous avez déjà la solution la plus lisible . Il existe d'autres façons d'exprimer cela, peut-être avec moins de caractères, mais elles sont moins simples à lire.

Selon ce que les valeurs représentent réellement, votre meilleur pari est d' encapsuler le chèque dans une fonction avec un nom parlant . Alternativement ou en plus, vous pouvez modéliser les objets x, y et a, b chacun dans des objets de classe supérieure dédiés que vous pouvez ensuite comparer avec la logique de la comparaison dans une méthode de contrôle d'égalité de classe ou une fonction personnalisée dédiée.

Frank Hopkins
la source
3

Il semble que l'OP ne concernait que le cas de deux variables, mais comme StackOverflow s'adresse également à ceux qui recherchent la même question plus tard, je vais essayer d'aborder le cas générique ici en détail; Une réponse précédente contient déjà une réponse générique utilisant itertools.permutations(), mais cette méthode conduit à des O(N*N!)comparaisons, car il existe des N!permutations avec des Néléments chacun. (Ce fut la principale motivation de cette réponse)

Tout d'abord, résumons comment certaines des méthodes des réponses précédentes s'appliquent au cas générique, comme motivation pour la méthode présentée ici. Je vais utiliser Apour faire référence à (x, y)et Bpour faire référence à (a, b), qui peuvent être des tuples de longueur arbitraire (mais égale).

set(A) == set(B)est rapide, mais ne fonctionne que si les valeurs sont hachables et vous pouvez garantir que l'un des tuples ne contient aucune valeur en double. (Par exemple {1, 1, 2} == {1, 2, 2}, comme l'a souligné @ user2357112 dans la réponse de @Daniel Mesejo)

La méthode précédente peut être étendue pour fonctionner avec des valeurs en double en utilisant des dictionnaires avec des nombres, au lieu d'ensembles: (Cela a toujours la limitation que toutes les valeurs doivent être lavables, donc par exemple. Les valeurs mutables comme listne fonctionneront pas)

def counts(items):
    d = {}
    for item in items:
        d[item] = d.get(item, 0) + 1
    return d

counts(A) == counts(B)

sorted(A) == sorted(B)ne nécessite pas de valeurs lavables, mais est légèrement plus lent et requiert des valeurs ordonnables à la place. (Donc, par exemple complex, ne fonctionnera pas)

A in itertools.permutations(B)ne nécessite pas de valeurs lavables ou ordonnables, mais comme déjà mentionné, il a de la O(N*N!)complexité, donc même avec seulement 11 articles, il peut prendre plus d'une seconde pour terminer.

Alors, y a-t-il un moyen d'être aussi général, mais le faire beaucoup plus rapidement? Pourquoi oui, en vérifiant "manuellement" qu'il y a la même quantité de chaque élément: (La complexité de celui-ci est O(N^2), donc ce n'est pas bon non plus pour les entrées de grande taille; Sur ma machine, les éléments 10k peuvent prendre plus d'une seconde - mais avec entrées plus petites, comme 10 articles, c'est aussi rapide que les autres)

def unordered_eq(A, B):
    for a in A:
        if A.count(a) != B.count(a):
            return False
    return True

Pour obtenir les meilleures performances, on peut vouloir essayer d'abord la dictméthode basée sur, se replier sur la sortedméthode basée sur si cela échoue en raison de valeurs incontrôlables, et enfin retomber sur la countméthode basée sur si cela échoue aussi en raison de valeurs non ordonnancables.

Aleksi Torhamo
la source