Esiste un modo più elegante per esprimere ((x == a e y == b) o (x == b and y == a))?


108

Sto provando a valutare ((x == a and y == b) or (x == b and y == a))in Python, ma sembra un po 'prolisso. C'è un modo più elegante?


8
Dipende dal tipo di oggetti x,y, a,b: sono ints / float / stringhe, oggetti arbitrari o cosa? Se fossero dei tipi predefiniti ed è stato possibile mantenere entrambi x,ye a,bin ordine, è possibile evitare il secondo ramo. Si noti che la creazione di un set causerà l' x,y, a,bhash di ciascuno dei quattro elementi , che potrebbe essere o meno banale o avere un impatto sulle prestazioni a seconda del tipo di oggetti che sono.
smci,

18
Tieni a mente le persone con cui lavori e il tipo di progetto che stai sviluppando. Uso un po 'di Python qua e là. Se qualcuno ha codificato una delle risposte qui, dovrei assolutamente google cosa sta succedendo. Il tuo condizionale è leggibile praticamente, non importa quale.
Areks,

3
Non mi preoccuperei di nessuna delle sostituzioni. Sono più concisi, ma non così chiari (IMHO) e penso che saranno tutti più lenti.
Barmar,

22
Questo è un classico problema XYAB.
Tashus,

5
E in generale se hai a che fare con raccolte di oggetti indipendenti dall'ordine, usa sets / dicts / etc. Questo è davvero un problema XY, avremmo bisogno di vedere la base di codice più grande da cui è tratto. Sono d'accordo che ((x == a and y == b) or (x == b and y == a))possa sembrare yukky, ma 1) il suo intento è cristallino e intelligibile per tutti i programmatori non Python, non criptici 2) gli interpreti / compilatori lo gestiranno sempre bene e sostanzialmente non può mai tradursi in codice non performante, a differenza del alternative. Quindi, "più elegante" può avere anche dei lati negativi.
smci,

Risposte:


151

Se gli elementi sono hash, è possibile utilizzare i set:

{a, b} == {y, x}

18
@Graham no, perché ci sono esattamente due elementi nel set a destra. Se entrambi sono a, non c'è b, e se entrambi sono b, non c'è a, e in entrambi i casi i set non possono essere uguali.
Hobbs,

12
D'altra parte, se avessimo tre elementi per lato e avessimo bisogno di verificare se potevano essere abbinati uno a uno, i set non funzionerebbero. {1, 1, 2} == {1, 2, 2}. A quel punto, hai bisogno sortedo Counter.
user2357112 supporta Monica il

11
Lo trovo difficile da leggere (non leggere "{}" come "()"). Abbastanza per commentarlo - e poi lo scopo è perso.
Édouard,

9
So che è Python, ma la creazione di due nuovi set solo per confrontare i valori mi sembra eccessivo ... Basta definire una funzione che lo fa e chiamare quando necessario.
Marxin,

5
@marxin Una funzione sarebbe ancora più grande di 2 semplici costruzioni in serie. E meno leggibile con 4 argomenti.
Gloweye,

60

Penso che il meglio che puoi ottenere sia impacchettarli in tuple:

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

O forse avvolgerlo in una ricerca set

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

Proprio da quando è stato menzionato da un paio di commenti, ho fatto dei tempi e tuple e set sembrano funzionare identicamente qui quando la ricerca fallisce:

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

Sebbene le tuple siano effettivamente più veloci quando la ricerca ha esito positivo:

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

Ho scelto di utilizzare un set perché sto effettuando una ricerca di appartenenza e concettualmente un set è più adatto a quel caso d'uso rispetto a una tupla. Se hai misurato una differenza significativa tra le due strutture in un caso d'uso particolare, scegli quella più veloce. Non penso che le prestazioni siano un fattore qui.


12
Il metodo tupla sembra molto pulito. Sarei preoccupato per l'impatto sulle prestazioni dell'utilizzo di un set. Potresti fare if (a, b) in ((x, y), (y, x)), però?
Brilliand,

18
@Brilliand Se sei preoccupato per l'impatto sulle prestazioni, Python non è la lingua che fa per te. :-D
Sneftel

Mi piace molto il primo metodo, due confronti di tuple. È facile analizzare (una clausola alla volta) e ogni clausola è molto semplice. E soprattutto, dovrebbe essere relativamente efficiente.
Matthieu M.,

C'è qualche motivo per preferire la setsoluzione nella risposta alla soluzione di tupla di @Brilliand?
user1717828

Un set richiede che gli oggetti siano hash, quindi una tupla sarebbe una soluzione più generale con meno dipendenze. Le prestazioni, sebbene probabilmente non siano generalmente importanti, possono anche apparire molto diverse quando si tratta di oggetti di grandi dimensioni con costosi controlli di hashing e uguaglianza.
Dukeling,

31

Le tuple lo rendono leggermente più leggibile:

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

Questo dà un indizio: stiamo verificando se la sequenza x, yè uguale alla sequenza a, bma ignoriamo l'ordinamento. Questo è solo set uguaglianza!

{x, y} == {a, b}

,crea una tupla, non un elenco. così (x, y)e (a, b)sono tuple, lo stesso di x, ye a, b.
kissgyorgy

Intendevo "elenco" nel senso di "sequenza ordinata di elementi", non nel senso del listtipo Python . Modificato perché in effetti questo era confuso.
Thomas

26

Se gli articoli non sono hash, ma supportano il confronto degli ordini, puoi provare:

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

Dal momento che funziona anche con oggetti lavabili (giusto?), È una soluzione più globale.
Carl Witthoft,

5
@CarlWitthoft: No. Esistono tipi che possono essere cancellati ma non ordinabili: complexad esempio.
dan04,

26

Il modo più elegante, secondo me, sarebbe

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

Questo è un modo migliore rispetto all'utilizzo dei set, ovvero {a, b} == {y, x}come indicato in altre risposte perché non abbiamo bisogno di pensare se le variabili sono hash.


In cosa differisce da questa risposta precedente ?
scohe001,

5
@ scohe001 Utilizza una tupla in cui la risposta precedente utilizza un set. Quella risposta precedente ha preso in considerazione questa soluzione, ma ha rifiutato di elencarla come una raccomandazione.
Brilliand,

25

Se questi sono numeri, puoi usare (x+y)==(a+b) and (x*y)==(a*b).

Se si tratta di articoli comparabili, è possibile utilizzare min(x,y)==min(a,b) and max(x,y)==max(a,b).

Ma ((x == a and y == b) or (x == b and y == a))è chiaro, sicuro e più generale.


2
Haha, polinomi simmetrici ftw!
Carsten S,

2
Penso che ciò crei un rischio di errori di overflow.
Razvan Socol,

3
Questa è la risposta corretta, in particolare l'ultima frase. Per farla semplice, questo non dovrebbe richiedere più nuovi iterabili o cose del genere. Per coloro che vogliono usare i set, dai un'occhiata all'implementazione degli oggetti set e poi immagina di provare a eseguirlo in un ciclo stretto ...
Z4-tier

3
@RazvanSocol OP non ha detto quali sono i tipi di dati e questa risposta qualifica le soluzioni che dipendono dal tipo.
Livello Z4,

21

Come generalizzazione per più di due variabili possiamo usare itertools.permutations. Questo è invece di

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

possiamo scrivere

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

E ovviamente le due versioni variabili:

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

5
Bella risposta. Vale la pena sottolineare qui (per coloro che non hanno mai fatto molto con i generatori prima) che questo è molto efficiente in termini di memoria, poiché viene creata una sola permutazione alla volta e il controllo "in" si interrompe e restituisce True immediatamente dopo un la corrispondenza è stata trovata.
robertlayton,

2
Vale anche la pena sottolineare che la complessità di questo metodo è O(N*N!); Per 11 variabili, questo può richiedere più di un secondo per terminare. (Ho pubblicato un metodo più veloce, ma richiede ancora O(N^2), e inizia a prendere un secondo su 10k variabili; Quindi sembra che questo possa essere fatto velocemente o in generale (wrt. Hashability / ordinabilità), ma non entrambi: P)
Aleksi Torhamo

15

Puoi utilizzare le tuple per rappresentare i tuoi dati e quindi verificare l'inserimento impostato, come:

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

    return (x, y) in test_set

3
Scritto come una riga e con un elenco (per gli articoli non cancellabili), considero questa la risposta migliore. (anche se sono un principiante di Python completo).
Édouard,

1
@ Édouard Ora c'è un'altra risposta che è essenzialmente quella (solo con una tupla invece di un elenco, che è comunque più efficiente).
Brilliand,

10

Hai già la soluzione più leggibile . Esistono altri modi per esprimerlo, forse con meno personaggi, ma sono meno semplici da leggere.

A seconda di ciò che i valori rappresentano effettivamente la tua scommessa migliore è racchiudere il controllo in una funzione con un nome parlato . In alternativa o in aggiunta, è possibile modellare gli oggetti x, y e a, b ciascuno in oggetti di classe superiore dedicati che è quindi possibile confrontare con la logica del confronto in un metodo di controllo dell'uguaglianza di classe o in una funzione personalizzata dedicata.


3

Sembra che l'OP si occupasse solo del caso di due variabili, ma poiché StackOverflow è anche per coloro che cercano la stessa domanda in seguito, proverò ad affrontare il caso generico qui in dettaglio; Una risposta precedente contiene già una risposta generica usando itertools.permutations(), ma quel metodo porta a O(N*N!)confronti, poiché ci sono N!permutazioni con Nelementi ciascuno. (Questa è stata la motivazione principale per questa risposta)

Innanzitutto, riassumiamo come alcuni dei metodi delle risposte precedenti si applicano al caso generico, come motivazione per il metodo presentato qui. Userò Aper fare riferimento (x, y)e Bfare riferimento a (a, b), che può essere tuple di lunghezza arbitraria (ma uguale).

set(A) == set(B)è veloce, ma funziona solo se i valori sono hash e puoi garantire che una delle tuple non contenga valori duplicati. (Ad esempio {1, 1, 2} == {1, 2, 2}, come sottolineato da @ user2357112 nella risposta di @Daniel Mesejo)

Il metodo precedente può essere esteso per funzionare con valori duplicati usando dizionari con conteggi, anziché set: (Questo ha ancora la limitazione che tutti i valori devono essere hash, quindi ad esempio valori mutabili come listnon funzioneranno)

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)non richiede valori hash, ma è leggermente più lento e richiede invece valori ordinabili. (Quindi ad es. complexNon funzionerà)

A in itertools.permutations(B)non richiede valori hash o ordinabili, ma come già accennato, ha O(N*N!)complessità, quindi anche con solo 11 articoli, può richiedere più di un secondo per terminare.

Quindi, c'è un modo per essere il più generale, ma lo fa considerevolmente più veloce? Perché sì, controllando "manualmente" che c'è la stessa quantità di ogni articolo: (La complessità di questo è O(N^2), quindi non va bene nemmeno per input di grandi dimensioni; Sulla mia macchina, 10k articoli possono richiedere un secondo, ma con input più piccoli, come 10 elementi, questo è veloce come gli altri)

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

Per ottenere le migliori prestazioni, si potrebbe desiderare di provare dictprima il metodo basato su, tornare al sortedmetodo basato su se ciò fallisce a causa di valori non lavabili, e infine ritornare al countmetodo basato su se anche quello fallisce a causa di valori non ordinabili.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.