¿Hay alguna forma más elegante de expresar ((x == a y y == b) o (x == b y y == a))?

108

Estoy tratando de evaluar ((x == a and y == b) or (x == b and y == a))en Python, pero parece un poco detallado. ¿Hay alguna forma más elegante?

LetEpsilonBeLessThanZero
fuente
8
Depende de qué tipo de objetos x,y, a,bsean: ¿son ints / floats / strings, objetos arbitrarios o qué? Si fueran incorporada tipos y fue posible mantener tanto x,yy a,ben forma ordenada, entonces usted podría evitar la segunda rama. Tenga en cuenta que la creación de un conjunto hará que cada uno de los cuatro elementos x,y, a,bse mezcle, lo que podría o no ser trivial o tener una implicación de rendimiento dependiendo completamente de qué tipo de objetos sean.
smci
18
Tenga en cuenta las personas con las que trabaja y el tipo de proyecto que está desarrollando. Yo uso un pequeño Python aquí y allá. Si alguien codificara una de las respuestas aquí, tendría que buscar en Google lo que está sucediendo. Su condicional es legible prácticamente sin importar qué.
Areks
3
No me molestaría con ninguno de los reemplazos. Son más concisos, pero no tan claros (en mi humilde opinión) y creo que todos serán más lentos.
Barmar
22
Este es un problema clásico de XYAB.
Tashus
55
Y, en general, si se trata de colecciones de objetos independientes del orden, use sets / dicts / etc. Esto realmente es un problema XY, tendríamos que ver la base de código más grande de la que se extrae. Estoy de acuerdo en que ((x == a and y == b) or (x == b and y == a))puede parecer desagradable, pero 1) su intención es clara e inteligible para todos los programadores que no son de Python, no crípticos 2) los intérpretes / compiladores siempre lo manejarán bien y, esencialmente, nunca puede dar como resultado un código no funcional. alternativas. Entonces, 'más elegante' también puede tener serias desventajas.
smci

Respuestas:

151

Si los elementos son hashable, puede usar conjuntos:

{a, b} == {y, x}
Dani Mesejo
fuente
18
@Graham no, no lo hace, porque hay exactamente dos elementos en el conjunto de la derecha. Si ambos son a, no hay b, y si ambos son b, no hay a, y en cualquier caso los conjuntos no pueden ser iguales.
hobbs
12
Por otro lado, si tuviéramos tres elementos en cada lado y necesitáramos probar si se podrían combinar uno a uno, entonces los conjuntos no funcionarían. {1, 1, 2} == {1, 2, 2}. En ese punto, necesitas sortedo Counter.
user2357112 es compatible con Monica el
11
Encuentro esto difícil de leer (no lea el "{}" como "()"). Lo suficiente como para comentarlo, y luego se pierde el propósito.
Édouard el
99
Sé que es Python, pero crear dos nuevos conjuntos solo para comparar los valores me parece una exageración ... Simplemente defina una función que lo haga y llame cuando sea necesario.
marxin
55
@marxin Una función sería incluso más grande que 2 construcciones de conjuntos simples. Y menos legible con 4 argumentos.
Gloweye
60

Creo que lo mejor que puedes obtener es empaquetarlos en tuplas:

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

O, tal vez envuelva eso en una búsqueda establecida

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

Solo desde que fue mencionado por un par de comentarios, hice algunos tiempos, y las tuplas y los conjuntos parecen funcionar de manera idéntica aquí cuando falla la búsqueda:

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

Aunque las tuplas son realmente más rápidas cuando la búsqueda tiene éxito:

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

Elegí usar un conjunto porque estoy haciendo una búsqueda de membresía, y conceptualmente un conjunto es mejor para ese caso de uso que una tupla. Si midió una diferencia significativa entre las dos estructuras en un caso de uso particular, vaya con la más rápida. Sin embargo, no creo que el rendimiento sea un factor aquí.

Carcigenicate
fuente
12
El método de la tupla se ve muy limpio. Me preocuparía el impacto en el rendimiento del uso de un conjunto. ¿Pero podrías hacer if (a, b) in ((x, y), (y, x))?
Brilliand
18
@Brilliand Si te preocupa el impacto en el rendimiento, Python no es el lenguaje para ti. :-D
Sneftel
Realmente me gusta el primer método, dos comparaciones de tuplas. Es fácil de analizar (una cláusula a la vez) y cada cláusula es muy simple. Y además, debería ser relativamente eficiente.
Matthieu M.
¿Hay alguna razón para preferir la setsolución en la respuesta a la solución de tupla de @Brilliand?
user1717828
Un conjunto requiere que los objetos sean hashable, por lo que una tupla sería una solución más general con menos dependencias. El rendimiento, aunque probablemente no sea generalmente importante, también puede verse muy diferente cuando se trata de objetos grandes con costosas comprobaciones de hashing e igualdad.
Dukeling
31

Las tuplas lo hacen un poco más legible:

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

Esto da una pista: estamos verificando si la secuencia x, yes igual a la secuencia a, bpero ignorando el orden. Eso es solo establecer la igualdad!

{x, y} == {a, b}
Thomas
fuente
,crea una tupla, no una lista. so (x, y)y (a, b)son tuplas, igual que x, yy a, b.
kissgyorgy
Quise decir "lista" en el sentido de "secuencia ordenada de elementos", no en el sentido del listtipo Python . Editado porque de hecho esto era confuso.
Thomas
26

Si los elementos no son compartibles, pero admiten comparaciones de pedidos, puede intentar:

sorted((x, y)) == sorted((a, b))
jasonharper
fuente
Dado que esto también funciona con elementos que se pueden compartir (¿verdad?), Es una solución más global.
Carl Witthoft
55
@CarlWitthoft: No. Hay tipos que se pueden compartir pero que no se pueden ordenar: complexpor ejemplo.
dan04
26

La forma más elegante, en mi opinión, sería

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

Esta es una mejor manera que usar conjuntos, es decir {a, b} == {y, x}, como se indica en otras respuestas porque no necesitamos pensar si las variables son hashaable.

Wagner Macedo
fuente
¿Cómo difiere esto de esta respuesta anterior ?
scohe001
55
@ scohe001 Utiliza una tupla donde la respuesta anterior usa un conjunto. Esa respuesta anterior consideró esta solución, pero se negó a enumerarla como una recomendación.
Brilliand
25

Si estos son números, puedes usarlos (x+y)==(a+b) and (x*y)==(a*b).

Si se trata de elementos comparables, puede usar min(x,y)==min(a,b) and max(x,y)==max(a,b).

Pero ((x == a and y == b) or (x == b and y == a))es claro, seguro y más general.

lhf
fuente
2
Jaja, polinomios simétricos ftw!
Carsten S
2
Creo que esto crea un riesgo de errores de desbordamiento.
Razvan Socol
3
Esta es la respuesta correcta, en particular la última oración. Mantenlo simple, esto no debería requerir múltiples iterables nuevos ni nada de eso. Para aquellos que desean usar conjuntos, eche un vistazo a la implementación de objetos de conjunto y luego imagine intentar ejecutar esto en un ciclo cerrado ...
Z4-tier
3
@RazvanSocol OP no dijo cuáles son los tipos de datos, y esta respuesta califica las soluciones que dependen del tipo.
Z4-tier
21

Como generalización a más de dos variables que podemos usar itertools.permutations. Eso es en lugar de

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

podemos escribir

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

Y, por supuesto, las dos versiones variables:

(x, y) in itertools.permutations([a, b])
un invitado
fuente
55
Gran respuesta. Vale la pena señalar aquí (para aquellos que no han hecho mucho con los generadores antes) que esto es muy eficiente en la memoria, ya que solo se crea una permutación a la vez, y la comprobación "in" se detendría y devolvería True inmediatamente después de un se encuentra la coincidencia
robertlayton
2
También vale la pena señalar que la complejidad de este método es O(N*N!); Para 11 variables, esto puede tomar más de un segundo para terminar. (Publiqué un método más rápido, pero aún así toma O(N^2), y comienza a tomar más de un segundo en 10k variables; por lo tanto, parece que esto se puede hacer rápido o en general (wrt. Hashability / orderability), pero no ambos: P)
Aleksi Torhamo
15

Puede usar tuplas para representar sus datos y luego verificar la inclusión de conjuntos, como:

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

    return (x, y) in test_set
Nils Müller
fuente
3
Escrito como una línea y con una lista (para elementos no hashaable), consideraría que esta es la mejor respuesta. (Aunque soy un novato completo de Python).
Édouard el
1
@ Édouard Ahora hay otra respuesta que es esencialmente eso (solo con una tupla en lugar de una lista, que de todos modos es más eficiente).
Brilliand
10

Ya tienes la solución más legible . Hay otras formas de expresar esto, quizás con menos caracteres, pero son menos fáciles de leer.

Dependiendo de lo que los valores representen realmente, su mejor apuesta es envolver el cheque en una función con un nombre de voz . Alternativamente o además, puede modelar los objetos x, y y a, b cada uno en objetos dedicados de clase superior que luego puede comparar con la lógica de la comparación en un método de verificación de igualdad de clase o una función personalizada dedicada.

Frank Hopkins
fuente
3

Parece que el OP solo estaba preocupado por el caso de dos variables, pero dado que StackOverflow también es para aquellos que buscan la misma pregunta más adelante, intentaré abordar el caso genérico aquí con cierto detalle; Una respuesta anterior ya contiene una respuesta genérica usando itertools.permutations(), pero ese método lleva a O(N*N!)comparaciones, ya que hay N!permutaciones con Nelementos cada una. (Esta fue la principal motivación para esta respuesta)

Primero, resumamos cómo algunos de los métodos en respuestas anteriores se aplican al caso genérico, como motivación para el método presentado aquí. Usaré Apara referirme (x, y)y Bpara referirme (a, b), que pueden ser tuplas de longitud arbitraria (pero igual).

set(A) == set(B)es rápido, pero solo funciona si los valores son hashables y puede garantizar que una de las tuplas no contenga ningún valor duplicado. (Por ejemplo {1, 1, 2} == {1, 2, 2}, como señaló @ user2357112 bajo la respuesta de @Daniel Mesejo)

El método anterior se puede ampliar para trabajar con valores duplicados mediante el uso de diccionarios con recuentos, en lugar de conjuntos: (Esto todavía tiene la limitación de que todos los valores deben ser hashables, por ejemplo, los valores mutables como listno funcionarán)

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)no requiere valores hashables, pero es un poco más lento y requiere valores ordenables en su lugar. (Entonces, por ejemplo complex, no funcionará)

A in itertools.permutations(B)no requiere valores hashables u ordenables, pero como ya se mencionó, tiene O(N*N!)complejidad, por lo que incluso con solo 11 elementos, puede llevar más de un segundo terminar.

Entonces, ¿hay alguna manera de ser tan general, pero hacerlo considerablemente más rápido? Por qué sí, al verificar "manualmente" que hay la misma cantidad de cada elemento: (La complejidad de este es O(N^2), por lo que tampoco es bueno para entradas grandes; en mi máquina, 10k elementos pueden tomar más de un segundo, pero con entradas más pequeñas, como 10 elementos, esto es tan rápido como los otros)

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

Para obtener el mejor rendimiento, es posible que primero desee probar el dictmétodo basado, recurrir al sortedmétodo basado si eso falla debido a valores no compartibles y, finalmente, recurrir al countmétodo basado si eso también falla debido a valores no ordenados.

Aleksi Torhamo
fuente