Modo di scrittura compatto (a + b == c o a + c == b o b + c == a)


136

Esiste un modo più compatto o pitonico per scrivere l'espressione booleana

a + b == c or a + c == b or b + c == a

Mi è venuta in mente

a + b + c in (2*a, 2*b, 2*c)

ma è un po 'strano.


16
Più compatto? Possibilmente. Più Pythonic? Improbabile.
Chepner,

126
Fai un favore al tuo futuro e mantienilo nella forma originale: è l'unico che dice immediatamente lo scopo di questo. Non modificarlo. "Semplice è meglio di complesso.", "La leggibilità conta.". "Se l'implementazione è difficile da spiegare, è una cattiva idea."
colpì il

21
Pythonic == illeggibile?
nhaarman,

3
@wwii Non si escludono a vicenda. Vedi a = 0, b = 0, c = 0;)
Honza Brabec,

1
Cosa ha detto @phresnel. Invece di provare a "semplificare" l'espressione, racchiudila in una funzione con un nome descrittivo.
Cefalopode

Risposte:


206

Se guardiamo allo Zen di Python, enfatizza il mio:

Lo Zen di Python, di Tim Peters

Bello è meglio che brutto.
Esplicito è meglio che implicito.
Semplice è meglio di complesso.
Complesso è meglio che complicato.
Flat è meglio di nidificato.
Sparse è meglio che denso.
La leggibilità conta.
I casi speciali non sono abbastanza speciali da infrangere le regole.
Sebbene la praticità superi la purezza.
Gli errori non dovrebbero mai passare silenziosamente.
A meno che non sia esplicitamente messo a tacere.
Di fronte all'ambiguità, rifiuta la tentazione di indovinare.
Dovrebbe esserci uno - e preferibilmente solo un - modo obsoleto di farlo.
Anche se in quel modo all'inizio potrebbe non essere ovvio a meno che tu non sia olandese.
Adesso è meglio che mai.
Anche se spesso non è mai meglio diproprio ora
Se l'implementazione è difficile da spiegare, è una cattiva idea.
Se l'implementazione è facile da spiegare, potrebbe essere una buona idea.
Gli spazi dei nomi sono un'ottima idea per suonare il clacson: facciamo di più!

La soluzione più Pythonic è quella più chiara, semplice e facile da spiegare:

a + b == c or a + c == b or b + c == a

Ancora meglio, non hai nemmeno bisogno di conoscere Python per capire questo codice! È così facile Questa è, senza riserve, la migliore soluzione. Qualcos'altro è la masturbazione intellettuale.

Inoltre, questa è probabilmente anche la soluzione più performante, in quanto è l'unica tra tutte le proposte a cortocircuitare. Se a + b == cviene eseguita solo una singola aggiunta e confronto.


11
Ancora meglio, aggiungi alcune parentesi per rendere l'intento cristallino.
Bryan Oakley,

3
L'intenzione è già cristallina senza parentesi. Le parentesi renderebbero più difficile la lettura: perché l'autore usa le parentesi quando la precedenza copre già questo?
Miles Rout,

1
Un'altra nota sul tentativo di essere troppo intelligente: potresti introdurre bug imprevisti a causa di condizioni mancanti che non hai considerato. In altre parole, potresti pensare che la tua nuova soluzione compatta sia equivalente, ma non lo è in tutti i casi. A meno che non vi sia una ragione convincente per codificare diversamente (prestazioni, vincoli di memoria e così via), la chiarezza è il re.
Rob Craig,

Dipende da cosa serve la formula. Guarda "Esplicito è meglio che implicito", potrebbe darsi che l'approccio del "primo ordinamento" esprima più chiaramente ciò che il programma sta facendo, o uno degli altri. Non penso che possiamo giudicare dalla domanda.
Thomas Ahle,

101

Risolvere le tre uguaglianze per un:

a in (b+c, b-c, c-b)

4
L'unico problema con questo è gli effetti collaterali. Se bec sono espressioni più complesse, verranno eseguite più volte.
Silvio Mayolo,

3
@Kroltan Il mio punto era che in realtà ho risposto alla sua domanda, che chiedeva una rappresentazione "più compatta". Vedi: en.m.wikipedia.org/wiki/Short-circuit_evaluation
Alex Varga

24
Chiunque legga questo codice probabilmente ti maledirà per essere "intelligente".
Karoly Horvath,

5
@SilvioMayolo Lo stesso vale per l'originale
Izkata,

1
@AlexVarga, "Il mio punto era che in realtà ho risposto alla sua domanda". L'hai fatto; utilizza il 30% di caratteri in meno (inserendo spazi tra operatori). Non stavo cercando di dire che la tua risposta era sbagliata, solo commentando quanto sia idiomatica (pitonica). Bella risposta.
Paul Draper,

54

Python ha una anyfunzione che fa una orsu tutti gli elementi di una sequenza. Qui ho convertito la tua dichiarazione in una tupla a 3 elementi.

any((a + b == c, a + c == b, b + c == a))

Si noti che orè un corto circuito, quindi se il calcolo delle singole condizioni è costoso, potrebbe essere meglio mantenere il costrutto originale.


2
any()e anche in all()corto circuito.
TigerhawkT3,

42
@ TigerhawkT3 Non in questo caso però; le tre espressioni verranno valutate prima che esista la tupla e la tupla esisterà anyanche prima dell'esecuzione.
colpì il

13
Ah, capisco. Immagino sia solo quando c'è un generatore o un simile iteratore pigro lì dentro.
TigerhawkT3,

4
anye all"cortocircuito" il processo di esame dell'iterabile che viene loro dato; ma se quell'iterabile è una sequenza piuttosto che un generatore, allora è già stato completamente valutato prima che si verifichi la chiamata di funzione .
Karl Knechtel,

Questo ha il vantaggio che è facile da dividere in più linee (doppio trattino gli argomenti any, single-trattino l' ):nel ifcomunicato), che aiuta molto per la leggibilità quando si tratta di matematica
Izkata

40

Se sai che hai a che fare solo con numeri positivi, funzionerà ed è abbastanza pulito:

a, b, c = sorted((a, b, c))
if a + b == c:
    do_stuff()

Come ho detto, questo funziona solo con numeri positivi; ma se sai che saranno positivi, questa è una soluzione IMO molto leggibile, anche direttamente nel codice invece che in una funzione.

Potresti farlo, il che potrebbe fare un po 'di calcolo ripetuto; ma non hai specificato le prestazioni come obiettivo:

from itertools import permutations

if any(x + y == z for x, y, z in permutations((a, b, c), 3)):
    do_stuff()

O senza permutations()e la possibilità di calcoli ripetuti:

if any(x + y == z for x, y, z in [(a, b, c), (a, c, b), (b, c, a)]:
    do_stuff()

Probabilmente metterei questa o qualsiasi altra soluzione in una funzione. Quindi puoi semplicemente chiamare chiaramente la funzione nel tuo codice.

Personalmente, a meno che non avessi bisogno di maggiore flessibilità dal codice, utilizzerei solo il primo metodo nella tua domanda. È semplice ed efficiente. Potrei ancora metterlo in una funzione:

def two_add_to_third(a, b, c):
    return a + b == c or a + c == b or b + c == a

if two_add_to_third(a, b, c):
    do_stuff()

Questo è piuttosto Pythonic, ed è probabilmente il modo più efficiente per farlo (la funzione extra chiama a parte); anche se non dovresti preoccuparti troppo delle prestazioni, a meno che non stia effettivamente causando un problema.


specialmente se possiamo supporre che a, b, c siano tutti non negativi.
Cphlewis,

Trovo la frase "non sempre funziona" un po 'confusa. La prima soluzione funziona solo se sai con certezza che i tuoi numeri non sono negativi. Ad esempio con (a, b, c) = (-3, -2, -1) hai a + b! = C ma b + c = a. Casi simili con (-1, 1, 2) e (-2, -1, 1).
utente

@numero, sai, l'ho notato prima; non so perché non l'ho riparato.
Cyphase

La tua soluzione migliore non funziona per una grande classe di input, mentre il suggerimento dell'OP funziona per tutti gli input. In che modo "non funziona" più Pythonic di "lavorare"?
Barry,

3
Ooh, schiocca. " Se sai che hai a che fare solo con numeri positivi , funzionerà ed è abbastanza pulito". Tutti gli altri funzionano per qualsiasi numero, ma se sai che hai a che fare solo con numeri positivi , quello in alto è molto leggibile / Pythonic IMO.
Cyphase

17

Se utilizzerai solo tre variabili, il tuo metodo iniziale:

a + b == c or a + c == b or b + c == a

È già molto pitonico.

Se prevedi di utilizzare più variabili, il tuo metodo di ragionamento con:

a + b + c in (2*a, 2*b, 2*c)

È molto intelligente ma pensiamo al perché. Perché funziona?
Bene attraverso qualche semplice aritmetica vediamo che:

a + b = c
c = c
a + b + c == c + c == 2*c
a + b + c == 2*c

E questo dovrà valere sia per a, b, c, nel senso che sì, sarà uguale 2*a, 2*bo 2*c. Questo sarà vero per qualsiasi numero di variabili.

Quindi un buon modo per scrivere rapidamente sarebbe semplicemente avere un elenco delle variabili e verificare la loro somma con un elenco dei valori raddoppiati.

values = [a,b,c,d,e,...]
any(sum(values) in [2*x for x in values])

In questo modo, per aggiungere più variabili all'equazione, tutto ciò che devi fare è modificare la tua lista di valori con 'n' nuove variabili, non scrivere 'n' equazioni


4
Che dire a=-1, b=-1, c=-2, quindi a+b=c, ma a+b+c = -4e 2*max(a,b,c)è-2
Eric Renouf

Grazie è vero, avrei bisogno di usare gli addominali. Effettuare quell'adeguamento ora.
ThatGuyRussell

2
Dopo averlo peppato con mezza dozzina di abs()chiamate, è Pythonic rispetto allo snippet dell'OP (in realtà lo definirei significativamente meno leggibile).
TigerhawkT3,

È vero, lo aggiusterò ora
ThatGuyRussell

1
@ThatGuyRussell Per cortocircuito, dovresti usare un generatore ... qualcosa del genere any(sum(values) == 2*x for x in values), in questo modo non dovresti fare tutto il raddoppio in anticipo, se necessario.
Barry,

12

Il seguente codice può essere usato per confrontare iterativamente ogni elemento con la somma degli altri, che viene calcolata dalla somma dell'intero elenco, escludendo quell'elemento.

 l = [a,b,c]
 any(sum(l)-e == e for e in l)

2
Bello :) Penso che se rimuovi le []parentesi dalla seconda riga, questo potrebbe anche cortocircuitare come l'originale con or...
psmears

1
che è sostanzialmente any(a + b + c == 2*x for x in [a, b, c]), abbastanza vicino al suggerimento del PO
njzk2

Questo è simile, tuttavia questo metodo si estende a qualsiasi numero di variabili. Ho incorporato il suggerimento di @psmears sul corto circuito.
Arcanum,

10

Non provare a semplificarlo. Invece, dai un nome a cosa stai facendo con una funzione:

def any_two_sum_to_third(a, b, c):
  return a + b == c or a + c == b or b + c == a

if any_two_sum_to_third(foo, bar, baz):
  ...

Sostituire la condizione con qualcosa di "intelligente" potrebbe renderla più breve, ma non renderla più leggibile. Lasciarlo così com'è non è comunque molto leggibile, perché è difficile sapere il perché stai controllando queste tre condizioni a colpo d'occhio. Questo rende assolutamente cristallino ciò che stai cercando.

Per quanto riguarda le prestazioni, questo approccio aggiunge l'overhead di una chiamata di funzione, ma non sacrifica mai la leggibilità per le prestazioni a meno che non sia stato trovato un collo di bottiglia che è assolutamente necessario correggere. E misura sempre, poiché alcune implementazioni intelligenti sono in grado di ottimizzare e integrare alcune chiamate di funzione in alcune circostanze.


1
Le funzioni devono essere scritte solo se si prevede di utilizzare lo stesso codice in più di uno spot o se il codice è complesso. Non vi è alcuna menzione del riutilizzo del codice nella domanda originale e la scrittura di una funzione per una singola riga di codice non è solo eccessiva ma compromette la leggibilità.
Igor Levicki,

5
Proveniente dalla scuola di cose FP, praticamente non sono completamente d'accordo e affermare che le funzioni a una riga ben definite sono alcuni dei migliori strumenti per aumentare la leggibilità che tu abbia mai trovato. Crea una funzione ogni volta che i passaggi che fai per fare qualcosa non portano immediatamente chiarezza a ciò che stai facendo, poiché il nome della funzione ti consente di specificare cosa meglio di qualsiasi commento possa fare.
Jack,

Qualunque sia la scuola che invochi, è male aderire ciecamente a una serie di regole. Dover passare a un'altra parte del codice sorgente per leggere quella riga di codice nascosta all'interno di una funzione solo per essere in grado di verificare che faccia effettivamente ciò che dice nel nome, e quindi tornare al luogo di una chiamata a assicurarsi di passare i parametri corretti è una commutazione del contesto completamente inutile. A mio avviso, ciò compromette sia la leggibilità che il flusso di lavoro. Infine, né il nome di una funzione né i commenti sul codice sostituiscono correttamente la documentazione del codice.
Igor Levicki,

9

Python 3:

(a+b+c)/2 in (a,b,c)
(a+b+c+d)/2 in (a,b,c,d)
...

Si adatta a qualsiasi numero di variabili:

arr = [a,b,c,d,...]
sum(arr)/2 in arr

Tuttavia, in generale, sono d'accordo sul fatto che, a meno che tu non abbia più di tre variabili, la versione originale è più leggibile.


3
Ciò restituisce risultati errati per alcuni input a causa di errori di arrotondamento in virgola mobile.
punti

La divisione dovrebbe essere evitata per motivi di prestazioni e precisione.
Igor Levicki,

1
@pts Nessuna implementazione restituirà risultati errati a causa dell'arrotondamento in virgola mobile? Anche a + b == c
osundblad il

@osundblad: se a, bec sono ints, allora (a + b + c) / 2 esegue l'arrotondamento (e può restituire risultati errati), ma a + b == c è preciso.
punti

3
la divisione per 2 sta semplicemente diminuendo l'esponente di uno, quindi sarà accurato per qualsiasi numero intero che sia inferiore a 2 ^ 53 (la parte della frazione di un float in python) e per numeri interi più grandi puoi usare il decimale . Ad esempio, per controllare numeri interi con esecuzione inferiore a 2 ^ 30[x for x in range(pow(2,30)) if x != ((x * 2)/ pow(2,1))]
Vitalii Fedorenko,

6
(a+b-c)*(a+c-b)*(b+c-a) == 0

Se la somma di due termini qualsiasi è uguale al terzo termine, uno dei fattori sarà zero, rendendo l'intero prodotto zero.


Stavo pensando esattamente la stessa cosa, ma non posso negare che la sua proposta originale sia molto più pulita ...
user541686

@Mehrdad - Sicuramente. Non è davvero diverso da(a+b<>c) && (a+c<>b) && (b+c<>a) == false
mbeckish,

È solo che la moltiplicazione è più costosa delle espressioni logiche e dell'aritmetica di base.
Igor Levicki,

@IgorLevicki - Sì, anche se questa è una preoccupazione di ottimizzazione MOLTO prematura. Verrà eseguito decine di migliaia di volte al secondo? Se sì, allora probabilmente vorresti guardare qualcos'altro.
mbeckish,

@mbeckish - Perché pensi che sia prematuro? Il codice dovrebbe essere scritto pensando all'ottimizzazione, non ottimizzato come ripensamento. Un giorno alcuni stagisti copieranno questo frammento di codice e lo incolleranno in alcuni loop critici per le prestazioni su una piattaforma integrata che verrà eseguita su milioni di dispositivi non necessariamente lenti per quello che fa, ma forse sprecando più energia della batteria. La scrittura di tale codice incoraggia solo cattive pratiche di codifica. A mio avviso, ciò che OP avrebbe dovuto chiedere è se esiste un modo per ottimizzare tale espressione logica.
Igor Levicki,

6

Che ne dici di:

a == b + c or abs(a) == abs(b - c)

Nota che questo non funzionerà se le variabili non sono firmate.

Dal punto di vista dell'ottimizzazione del codice (almeno sulla piattaforma x86) questa sembra essere la soluzione più efficiente.

I compilatori moderni incorporeranno entrambe le chiamate alle funzioni abs () ed eviteranno il test dei segni e il successivo ramo condizionale utilizzando una sequenza intelligente di istruzioni CDQ, XOR e SUB . Il suddetto codice di alto livello verrà quindi rappresentato con solo istruzioni ALU a bassa latenza e alta produttività e solo due condizionali.


E penso che fabs()possa essere usato per i floattipi;).
shA.t

4

La soluzione fornita da Alex Varga "a in (b + c, bc, cb)" è compatta e matematicamente bella, ma in realtà non scriverei il codice in questo modo perché il prossimo sviluppatore che arriva non capirà immediatamente lo scopo del codice .

La soluzione di Mark Ransom di

any((a + b == c, a + c == b, b + c == a))

è più chiaro ma non molto più conciso di

a + b == c or a + c == b or b + c == a

Quando scrivo un codice che qualcun altro dovrà guardare, o che dovrò guardare molto tempo dopo, quando ho dimenticato cosa stavo pensando quando l'ho scritto, essere troppo basso o intelligente tende a fare più male che bene. Il codice dovrebbe essere leggibile. Così succinto è buono, ma non così succinto che il prossimo programmatore non può capirlo.


Domanda onesta: perché la gente presume sempre che il prossimo programmatore sarà un idiota incapace di leggere il codice? Personalmente trovo questa idea offensiva. Se il codice deve essere scritto per essere palesemente ovvio per ogni programmatore, ciò implica che noi come professione stiamo provvedendo al minimo comune denominatore, il meno esperto tra noi. Se continuiamo a farlo, come potranno mai migliorare le loro abilità personali? Non lo vedo in altre professioni. Quando è stata l'ultima volta che hai visto un compositore scrivere una semplice partitura musicale in modo che ogni musicista potesse suonarla indipendentemente dal livello di abilità?
Igor Levicki,

6
Il problema è che anche i programmatori hanno un'energia mentale limitata, quindi vuoi spendere la tua energia mentale limitata sull'algoritmo e sugli aspetti di livello superiore del programma, o per capire cosa significa una linea di codice complicata quando può essere espressa più semplicemente ? La programmazione è difficile, quindi non complicarti inutilmente te stesso, proprio come un corridore olimpico non farebbe una gara con uno zaino pesante solo perché può farlo. Come afferma Steve McConell in Code Complete 2, la leggibilità è uno degli aspetti più importanti del codice.
Paul J Abernathy,

2

La richiesta è più compatta O più pitonica - ho provato a essere più compatta.

dato

import functools, itertools
f = functools.partial(itertools.permutations, r = 3)
def g(x,y,z):
    return x + y == z

Questo è di 2 caratteri in meno rispetto all'originale

any(g(*args) for args in f((a,b,c)))

prova con:

assert any(g(*args) for args in f((a,b,c))) == (a + b == c or a + c == b or b + c == a)

inoltre, dato:

h = functools.partial(itertools.starmap, g)

Questo è equivalente

any(h(f((a,b,c))))

Bene, sono due personaggi più corti dell'originale, ma non quello che l'OP ha dato subito dopo, che ha detto che sta attualmente usando. L'originale include anche un sacco di spazi bianchi, che questo omette quando possibile. C'è anche la piccola questione della funzione che g()devi definire affinché funzioni. Detto questo, direi che è significativamente più grande.
TigerhawkT3,

@ TigerhawkT3, l'ho interpretato come una richiesta per un'espressione / riga più breve. vedi modifica per ulteriori miglioramenti .
seconda guerra mondiale,

4
Nomi di funzioni molto cattivi, adatti solo per un codice golf.
0xc0de

@ 0xc0de - scusa se non gioco. Adatto può essere piuttosto soggettivo e dipendente dalle circostanze, ma rimanderò alla comunità.
seconda guerra mondiale il

Non vedo come sia più compatto quando ha più caratteri del codice originale.
Igor Levicki,

1

Voglio presentare quella che vedo come la risposta più pitonica :

def one_number_is_the_sum_of_the_others(a, b, c):
    return any((a == b + c, b == a + c, c == a + b))

Il caso generale, non ottimizzato:

def one_number_is_the_sum_of_the_others(numbers):
    for idx in range(len(numbers)):
        remaining_numbers = numbers[:]
        sum_candidate = remaining_numbers.pop(idx)
        if sum_candidate == sum(remaining_numbers):
            return True
    return False 

In termini di Zen di Python, penso che le affermazioni enfatizzate siano più seguite che da altre risposte:

Lo Zen di Python, di Tim Peters

Bello è meglio che brutto.
Esplicito è meglio che implicito.
Semplice è meglio di complesso.
Complesso è meglio che complicato.
Flat è meglio di nidificato.
Sparse è meglio che denso.
La leggibilità conta.
I casi speciali non sono abbastanza speciali da infrangere le regole.
Sebbene la praticità superi la purezza.
Gli errori non dovrebbero mai passare silenziosamente.
A meno che non sia esplicitamente messo a tacere.
Di fronte all'ambiguità, rifiuta la tentazione di indovinare.
Dovrebbe esserci uno - e preferibilmente solo un - modo obsoleto di farlo.
Anche se in quel modo potrebbe non essere ovvio all'inizio a meno che tu non sia olandese.
Adesso è meglio che mai.
Anche se spesso non è mai meglio diproprio ora
Se l'implementazione è difficile da spiegare, è una cattiva idea.
Se l'implementazione è facile da spiegare, potrebbe essere una buona idea.
Gli spazi dei nomi sono un'ottima idea per suonare il clacson: facciamo di più!


1

Come una vecchia abitudine della mia programmazione, penso che posizionare un'espressione complessa a destra in una clausola possa renderla più leggibile in questo modo:

a == b+c or b == a+c or c == a+b

Inoltre ():

((a == b+c) or (b == a+c) or (c == a+b))

E penso anche che l'uso di linee multiple possa anche fare più sensi in questo modo:

((a == b+c) or 
 (b == a+c) or 
 (c == a+b))

0

In modo generico,

m = a+b-c;
if (m == 0 || m == 2*a || m == 2*b) do_stuff ();

se, manipolare una variabile di input è giusto per te,

c = a+b-c;
if (c==0 || c == 2*a || c == 2*b) do_stuff ();

se vuoi sfruttare i bit hack, puoi usare "!", ">> 1" e "<< 1"

Ho evitato la divisione anche se mi consente di evitare due moltiplicazioni per evitare errori di arrotondamento. Tuttavia, controllare gli overflow


0
def any_sum_of_others (*nums):
    num_elements = len(nums)
    for i in range(num_elements):
        discriminating_map = map(lambda j: -1 if j == i else 1, range(num_elements))
        if sum(n * u for n, u in zip(nums, discriminating_map)) == 0:
            return True
    return False

print(any_sum_of_others(0, 0, 0)) # True
print(any_sum_of_others(1, 2, 3)) # True
print(any_sum_of_others(7, 12, 5)) # True
print(any_sum_of_others(4, 2, 2)) # True
print(any_sum_of_others(1, -1, 0)) # True
print(any_sum_of_others(9, 8, -4)) # False
print(any_sum_of_others(4, 3, 2)) # False
print(any_sum_of_others(1, 1, 1, 1, 4)) # True
print(any_sum_of_others(0)) # True
print(any_sum_of_others(1)) # False

Le funzioni devono essere scritte solo se si prevede di utilizzare lo stesso codice in più di uno spot o se il codice è complesso. Non vi è alcuna menzione del riutilizzo del codice nella domanda originale e la scrittura di una funzione per una singola riga di codice non è solo eccessiva ma compromette la leggibilità.
Igor Levicki,

Non sono d'accordo sul fatto che comprometta la leggibilità; se scegli un nome adatto può migliorare la leggibilità (ma non faccio alcuna dichiarazione sulla qualità del nome che ho scelto in questa risposta). Inoltre, può essere utile dare un nome a un concetto, che dovrai fare fintanto che ti costringi a dare un buon nome alla tua funzione. Le funzioni sono buone. Se la funzionalità sia abbastanza complessa da beneficiare dell'incapsulamento in una funzione, questo è un giudizio soggettivo.
Hammerite,
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.