Existe uma maneira mais elegante de expressar ((x == a e y == b) ou (x == b e y == a))?

108

Estou tentando avaliar ((x == a and y == b) or (x == b and y == a))em Python, mas parece um pouco detalhado. Existe uma maneira mais elegante?

LetEpsilonBeLessThanZero
fonte
8
Depende do tipo de objetos x,y, a,b: são ints / floats / strings, objetos arbitrários ou o quê? Se eles fossem do tipo interno e fosse possível manter ambos x,ye a,bna ordem classificada, você poderia evitar o segundo ramo. Observe que a criação de um conjunto fará com que cada um dos quatro elementos x,y, a,bseja hash, que pode ou não ser trivial ou ter uma implicação no desempenho, dependendo inteiramente do tipo de objeto que eles são.
SMCI
18
Lembre-se das pessoas com quem trabalha e do tipo de projeto que está desenvolvendo. Eu uso um pouco de Python aqui e ali. Se alguém codificasse uma das respostas aqui, eu teria que pesquisar no google o que está acontecendo. Seu condicional é legível praticamente, não importa o quê.
Areks
3
Eu não me incomodaria com nenhuma das substituições. Eles são mais concisos, mas não tão claros (IMHO) e acho que serão todos mais lentos.
Barmar
22
Este é um problema clássico do XYAB.
Tashus
5
E, em geral, se você estiver lidando com coleções de objetos independentes da ordem, use sets / dicts / etc. Este é realmente um problema XY, precisamos ver a base de código maior da qual ele foi extraído. Concordo que ((x == a and y == b) or (x == b and y == a))pode parecer estranho, mas 1) sua intenção é clara e inteligível para todos os programadores não-Python, não enigmáticos 2) os intérpretes / compiladores sempre lidam bem com isso e, essencialmente, nunca podem resultar em código de baixo desempenho, ao contrário do alternativas. Portanto, 'mais elegante' também pode ter desvantagens sérias.
SMCI

Respostas:

151

Se os elementos forem laváveis, você poderá usar conjuntos:

{a, b} == {y, x}
Dani Mesejo
fonte
18
@ Graham, não, porque existem exatamente dois itens no conjunto da mão direita. Se ambos são a, não há b, e se ambos são b, não há a, e em ambos os casos os conjuntos não podem ser iguais.
Hbbs #
12
Por outro lado, se tivéssemos três elementos de cada lado e precisássemos testar se eles poderiam corresponder um a um, os conjuntos não funcionariam. {1, 1, 2} == {1, 2, 2}. Nesse ponto, você precisa sortedou Counter.
User2357112 suporta Monica
11
Acho isso difícil de ler (não leia o "{}" como "()"). O suficiente para comentar - e então o objetivo é perdido.
Édouard
9
Eu sei que é python, mas criar dois novos conjuntos apenas para comparar os valores me parece um exagero ... Basta definir uma função que faça isso e chamar quando necessário.
marxin
5
@marxin Uma função seria um exagero ainda maior que duas construções simples de conjunto. E menos legível com 4 argumentos.
Gloweye
60

Eu acho que o melhor que você pode obter é empacotá-los em tuplas:

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

Ou talvez envolva isso em uma pesquisa definida

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

Desde que foi mencionado por alguns comentários, fiz alguns intervalos e tuplas e conjuntos parecem ter um desempenho idêntico aqui quando a pesquisa falha:

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

Embora as tuplas sejam realmente mais rápidas quando a pesquisa for bem-sucedida:

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

Eu escolhi usar um conjunto porque estou fazendo uma pesquisa de associação e, conceitualmente, um conjunto é mais adequado para esse caso de uso do que uma tupla. Se você mediu uma diferença significativa entre as duas estruturas em um caso de uso específico, siga a mais rápida. Não acho que o desempenho seja um fator aqui.

Carcinigenicado
fonte
12
O método da tupla parece muito limpo. Eu ficaria preocupado com o impacto no desempenho do uso de um conjunto. Você poderia fazer if (a, b) in ((x, y), (y, x))?
Brilliand
18
@ Brilliand Se você está preocupado com o impacto no desempenho, o Python não é o idioma para você. :-D
Sneftel 18/10/19
Eu realmente gosto do primeiro método, duas comparações de tuplas. É fácil analisar (uma cláusula de cada vez) e cada cláusula é muito simples. E ainda por cima, deve ser relativamente eficiente.
Matthieu M.
Existe alguma razão para preferir a setsolução na resposta à solução de tupla do @Brilliand?
user1717828
Um conjunto exige que os objetos sejam hasháveis; portanto, uma tupla seria uma solução mais geral com menos dependências. O desempenho, embora provavelmente não seja importante em geral, também pode parecer muito diferente ao lidar com objetos grandes com verificações caras de hash e igualdade.
Dukeling 19/10/19
31

As tuplas o tornam um pouco mais legível:

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

Isso dá uma pista: estamos verificando se a sequência x, yé igual à sequência, a, bmas ignorando a ordem. Isso é apenas definir a igualdade!

{x, y} == {a, b}
Thomas
fonte
,cria uma tupla, não uma lista. so (x, y)e (a, b)são tuplas, iguais a x, ye a, b.
kissgyorgy 8/01
Eu quis dizer "lista" no sentido de "sequência ordenada de elementos", não no sentido do listtipo Python . Editado porque de fato isso era confuso.
Thomas
26

Se os itens não forem editáveis, mas suportarem comparações de pedidos, você pode tentar:

sorted((x, y)) == sorted((a, b))
jasonharper
fonte
Como isso também funciona com itens laváveis ​​(certo?), É uma solução mais global.
Carl Witthoft 18/10/19
5
@CarlWitthoft: Não. Existem tipos que podem ser laváveis, mas não classificáveis: complexpor exemplo.
dan04
26

A maneira mais elegante, na minha opinião, seria

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

Essa é uma maneira melhor do que usar conjuntos, ou seja {a, b} == {y, x}, conforme indicado em outras respostas, porque não precisamos pensar se as variáveis ​​são hashable.

Wagner Macedo
fonte
Como isso difere da resposta anterior ?
scohe001
5
@ scohe001 Utiliza uma tupla em que a resposta anterior utiliza um conjunto. Essa resposta anterior considerou essa solução, mas se recusou a listá-la como uma recomendação.
Brilliand
25

Se estes são números, você pode usar (x+y)==(a+b) and (x*y)==(a*b).

Se esses itens são comparáveis, você pode usar min(x,y)==min(a,b) and max(x,y)==max(a,b).

Mas ((x == a and y == b) or (x == b and y == a))é claro, seguro e mais geral.

lhf
fonte
2
Haha, polinômios simétricos ftw!
Carsten S
2
Eu acho que isso cria um risco de erros de estouro.
Razvan Socol
3
Esta é a resposta correta, em particular a última frase. Mantenha as coisas simples, isso não deve exigir vários novos iterables ou algo assim. Para aqueles que querem usar conjuntos, dê uma olhada a implementação de objetos definidos e, em seguida, imagine tentar executar isso em um loop apertado ...
Z4-tier
3
O @RazvanSocol OP não disse quais são os tipos de dados e esta resposta qualifica as soluções que dependem do tipo.
camada Z4,
21

Como generalização para mais de duas variáveis, podemos usar itertools.permutations. Isso é em vez de

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

nós podemos escrever

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

E, claro, a versão de duas variáveis:

(x, y) in itertools.permutations([a, b])
um convidado
fonte
5
Ótima resposta. Vale ressaltar aqui (para aqueles que não fizeram muito com geradores antes) que isso é muito eficiente em termos de memória, pois apenas uma permutação é criada por vez, e a verificação "in" para e retorna True imediatamente após um correspondência encontrada.
Robertlayton
2
Também vale ressaltar que a complexidade desse método é O(N*N!); Para 11 variáveis, isso pode levar mais de um segundo para terminar. (Eu publiquei um método mais rápido, mas ainda é necessário O(N^2), e começa a demorar mais de um segundo em 10 mil variáveis; portanto, parece que isso pode ser feito rápido ou geralmente (wrt. Hashability / orderability), mas não os dois: P)
Aleksi Torhamo
15

Você pode usar tuplas para representar seus dados e verificar a inclusão de conjuntos, como:

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

    return (x, y) in test_set
Nils Müller
fonte
3
Escrito como one-liner e com uma lista (para itens não-laváveis), consideraria esta a melhor resposta. (embora eu seja um novato em Python completo).
Édouard
11
Agora é outra resposta que é basicamente isso (apenas com uma tupla em vez de uma lista, que é mais eficiente de qualquer maneira).
Brilliand
10

Você já tem a solução mais legível . Existem outras maneiras de expressar isso, talvez com menos caracteres, mas são menos fáceis de ler.

Dependendo do que os valores realmente representam, sua melhor aposta é encerrar o cheque em uma função com um nome falante . Como alternativa ou além disso, é possível modelar os objetos x, ye a, b, cada um em objetos dedicados de classe superior, que você poderá comparar com a lógica da comparação em um método de verificação de igualdade de classe ou em uma função personalizada dedicada.

Frank Hopkins
fonte
3

Parece que o OP estava preocupado apenas com o caso de duas variáveis, mas como o StackOverflow também é para quem procurar a mesma pergunta posteriormente, tentarei abordar o caso genérico aqui com mais detalhes; Uma resposta anterior já contém uma resposta genérica usando itertools.permutations(), mas esse método leva a O(N*N!)comparações, pois existem N!permutações com Nitens cada. (Essa foi a principal motivação para esta resposta)

Primeiro, vamos resumir como alguns dos métodos das respostas anteriores se aplicam ao caso genérico, como motivação para o método apresentado aqui. Vou usar Apara me referir (x, y)e Bme referir (a, b), que podem ser tuplas de comprimento arbitrário (mas igual).

set(A) == set(B)é rápido, mas só funciona se os valores forem hasháveis ​​e você pode garantir que uma das tuplas não contenha valores duplicados. (Por exemplo {1, 1, 2} == {1, 2, 2}, como apontado por @ user2357112 na resposta de @Daniel Mesejo)

O método anterior pode ser estendido para trabalhar com valores duplicados usando dicionários com contagens, em vez de conjuntos: (Isso ainda tem a limitação de que todos os valores precisam ser laváveis, portanto, por exemplo, valores mutáveis ​​como listnão funcionarão)

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)não requer valores laváveis, mas é um pouco mais lento e requer valores ordenáveis. (Então, por exemplo complex, não funcionará)

A in itertools.permutations(B)não requer valores laváveis ​​ou ordenáveis, mas, como já mencionado, possui O(N*N!)complexidade; portanto, mesmo com apenas 11 itens, pode demorar um segundo para terminar.

Então, existe uma maneira de ser tão geral, mas é consideravelmente mais rápido? Por que sim, verificando "manualmente" se existe a mesma quantidade de cada item: (A complexidade deste é O(N^2), portanto, também não é bom para entradas grandes; na minha máquina, 10 mil itens podem demorar um segundo - mas com entradas menores, como 10 itens, isso é tão rápido quanto os outros)

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

Para obter o melhor desempenho, pode-se tentar dictprimeiro o método baseado em, retornar ao sortedmétodo baseado em falha devido a valores laváveis ​​e, finalmente, voltar ao countmétodo baseado em falha também devido a valores desordenados.

Aleksi Torhamo
fonte