python - ¿Hay alguna forma más elegante de expresar((x== a y y== b) o(x== by e== a))?
boolean-logic (10)
Como generalización a más de dos variables, 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])
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?
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í.
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.
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 lista
x, y
es igual a la lista
a, b
pero ignorando el orden.
Eso es solo establecer la igualdad!
{x, y} == {a, b}
Parece que el OP solo estaba preocupado por el caso de dos variables, pero dado que 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 conduce a comparaciones
O(N*N!)
, Ya que hay
N!
permutaciones con
N
elementos 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é
A
para referirme a
(x, y)
y
B
para referirme a
(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 hashable y puede garantizar que una de las tuplas no contenga ningún valor duplicado.
(Por ejemplo,
{1, 1, 2} == {1, 2, 2}
, como lo señaló @ user2357112 en la respuesta de @Daniel Mesejo)
El método anterior puede extenderse 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 utilizables, por ejemplo, los valores mutables como la
list
no 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 una complejidad
O(N*N!)
, 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 demás)
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 método basado en
dict
, recurrir al método basado en orden si eso falla debido a valores no compartibles, y finalmente recurrir al método basado en
count
si eso también falla debido a valores no ordenables.
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
Si los elementos no se pueden compartir, pero admiten comparaciones de pedidos, puede intentar:
sorted((x, y)) == sorted((a, b))
Si los elementos son hashable, puede usar conjuntos:
{a, b} == {y, x}
Si se trata de números, puede usar
(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.
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.