Forma compacta de escribir (a + b == c o a + c == b o b + c == a)

136

¿Hay alguna forma más compacta o pitónica de escribir la expresión booleana?

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

se me ocurrio

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

Pero eso es un poco extraño.

qwr
fuente
16
¿Mas Compacto? Posiblemente. ¿Más pitónico? Improbable.
chepner
126
Hazte un favor a ti mismo en el futuro y mantenlo en la forma original: es lo único que dice inmediatamente el propósito de esto. No modifiques eso. "Simple es mejor que complejo", "La legibilidad cuenta". "Si la implementación es difícil de explicar, es una mala idea".
meter
21
Pythonic == ilegible?
nhaarman
3
@wwii No son mutuamente excluyentes. Ver a = 0, b = 0, c = 0;)
Honza Brabec
1
Lo que dijo @phresnel. En lugar de tratar de "simplificar" la expresión, envuélvala en una función con un nombre descriptivo.
Cefalópodo

Respuestas:

206

Si miramos el Zen de Python, el énfasis es mío:

El zen de Python, por Tim Peters

Hermoso es mejor que feo.
Explícito es mejor que implícito.
Simple es mejor que complejo.
Complejo es mejor que complicado.
Plano es mejor que anidado.
Escaso es mejor que denso.
La legibilidad cuenta.
Los casos especiales no son lo suficientemente especiales como para romper las reglas.
Aunque la practicidad supera la pureza.
Los errores nunca deben pasar en silencio.
A menos que sea silenciado explícitamente.
Ante la ambigüedad, rechaza la tentación de adivinar.
Debe haber una, y preferiblemente solo una, forma obvia de hacerlo.
Aunque esa manera puede no ser obvia al principio a menos que seas holandés.
Ahora es mejor que nunca.
Aunque nunca es mejor quederecho ahora.
Si la implementación es difícil de explicar, es una mala idea.
Si la implementación es fácil de explicar, puede ser una buena idea.
Los espacios de nombres son una gran idea, ¡hagamos más de eso!

La solución más pitónica es la más clara, simple y fácil de explicar:

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

¡Aún mejor, ni siquiera necesita saber Python para entender este código! Es que fácil. Esta es, sin reservas, la mejor solución. Cualquier otra cosa es masturbación intelectual.

Además, esta es probablemente la mejor solución, ya que es la única de todas las propuestas que provocan cortocircuitos. Si a + b == c, solo se realiza una sola adición y comparación.

Barry
fuente
11
Aún mejor, agregue algunos paréntesis para aclarar la intención.
Bryan Oakley
3
La intención ya es clara como el cristal sin paréntesis. Los paréntesis dificultarían la lectura: ¿por qué el autor usa paréntesis cuando la precedencia ya cubre esto?
Miles Rout
1
Otra nota sobre tratar de ser demasiado inteligente: puede introducir errores imprevistos por faltar condiciones que no consideró. En otras palabras, puede pensar que su nueva solución compacta es equivalente, pero no lo es en todos los casos. A menos que exista una razón convincente para codificar de otra manera (rendimiento, restricciones de memoria, etc.), la claridad es la reina.
Rob Craig
Depende de para qué es la fórmula. Mire 'explícito es mejor que implícito', podría ser que el enfoque de 'ordenar primero' exprese más claramente lo que está haciendo el programa, o uno de los otros. No creo que podamos juzgar por la pregunta.
Thomas Ahle
101

Resolviendo las tres igualdades para a:

a in (b+c, b-c, c-b)
Alex Varga
fuente
44
El único problema con esto son los efectos secundarios. Si b o c son expresiones más complejas, se ejecutarán varias veces.
Silvio Mayolo
3
@Kroltan Mi punto fue que en realidad respondí su pregunta, que pedía una representación "más compacta". Ver: en.m.wikipedia.org/wiki/Short-circuit_evaluation
Alex Varga
24
Cualquiera que lea este código probablemente lo maldecirá por ser "inteligente".
Karoly Horvath
55
@SilvioMayolo Lo mismo es cierto del original
Izkata
1
@AlexVarga, "Mi punto era que realmente respondí su pregunta". Lo hiciste; utiliza un 30% menos de caracteres (colocando espacios entre operadores). No estaba tratando de decir que tu respuesta era incorrecta, solo comentaba cuán idiomática (pitónica) es. Buena respuesta.
Paul Draper
54

Python tiene una anyfunción que hace un orsobre todos los elementos de una secuencia. Aquí he convertido tu declaración en una tupla de 3 elementos.

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

Tenga en cuenta que ores un cortocircuito, por lo que si calcular las condiciones individuales es costoso, podría ser mejor mantener su construcción original.

Mark Ransom
fuente
2
any()y all()cortocircuito también.
TigerhawkT3
42
@ TigerhawkT3 Sin embargo, no en este caso; las tres expresiones se evaluarán antes de que exista la tupla, y la tupla existirá anyincluso antes de que se ejecute.
meter
13
Ah, ya veo. Supongo que es solo cuando hay un generador o un iterador perezoso similar allí.
TigerhawkT3
44
anyy all"cortocircuito" el proceso de examinar los iterables que reciben; pero si ese iterable es una secuencia en lugar de un generador, entonces ya se ha evaluado completamente antes de que ocurra la llamada a la función .
Karl Knechtel
Esto tiene la ventaja de que es fácil dividirlo en varias líneas (doble sangría en los argumentos any, sangría simple ):en la ifdeclaración), lo que ayuda mucho a la legibilidad cuando las matemáticas están involucradas
Izkata
40

Si sabe que solo está tratando con números positivos, esto funcionará y está bastante limpio:

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

Como dije, esto solo funciona para números positivos; pero si sabe que van a ser positivos, esta es una solución IMO muy legible, incluso directamente en el código en lugar de en una función.

Podrías hacer esto, lo que podría hacer un poco de cálculo repetido; pero no especificó el rendimiento como su objetivo:

from itertools import permutations

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

O sin permutations()y la posibilidad de cálculos repetidos:

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

Probablemente pondría esto, o cualquier otra solución, en una función. Entonces puede llamar a la función limpiamente en su código.

Personalmente, a menos que necesite más flexibilidad del código, solo usaría el primer método en su pregunta. Es simple y eficiente. Todavía podría ponerlo en una función:

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()

Eso es bastante Pythonic, y posiblemente sea la forma más eficiente de hacerlo (dejando de lado la función adicional); aunque no debería preocuparse demasiado por el rendimiento de todos modos, a menos que realmente esté causando un problema.

Cifrado
fuente
especialmente si podemos asumir que a, b, c no son todos negativos.
cphlewis
Encuentro la frase "no siempre funciona" un poco confusa. La primera solución solo funciona si sabe con certeza que sus números no son negativos. Por ejemplo, con (a, b, c) = (-3, -2, -1) tienes a + b! = C pero b + c = a. Casos similares con (-1, 1, 2) y (-2, -1, 1).
usuario
@usernumber, ya sabes, lo noté antes; No estoy seguro de por qué no lo arreglé.
Cyphase
Su solución principal no funciona para una gran clase de entradas, mientras que la sugerencia del OP funciona para todas las entradas. ¿Cómo "no funciona" más Pythonic que "trabajando"?
Barry
3
Ooh, chasquido. " Si sabes que solo estás tratando con números positivos , esto funcionará y está bastante limpio". Todos los demás funcionan para cualquier número, pero si sabes que solo estás tratando con números positivos , el primero es muy legible / Pythonic IMO.
Cyphase
17

Si solo usará tres variables, entonces su método inicial:

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

Ya es muy pitónico.

Si planea usar más variables, entonces su método de razonamiento con:

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

Es muy inteligente pero pensemos por qué. ¿Por qué funciona esto?
Bueno, a través de una aritmética simple, vemos que:

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

Y esto tendrá que ser verdad para A, B, o C, lo que significa que sí, va a ser igual a 2*a, 2*bo 2*c. Esto será cierto para cualquier número de variables.

Entonces, una buena manera de escribir esto rápidamente sería simplemente tener una lista de sus variables y verificar su suma con una lista de los valores duplicados.

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

De esta manera, para agregar más variables a la ecuación, todo lo que tiene que hacer es editar su lista de valores con 'n' nuevas variables, no escribir ecuaciones 'n'

ThatGuyRussell
fuente
44
¿Qué pasa con a=-1, b=-1, c=-2y, a continuación a+b=c, pero a+b+c = -4y 2*max(a,b,c)es-2
Eric Renouf
Gracias, eso es cierto, necesitaría usar abdominales. Haciendo ese ajuste ahora.
ThatGuyRussell
2
Después de salpicarlo con media docena de abs()llamadas, es Pythonic que el fragmento del OP (en realidad lo llamaría significativamente menos legible).
TigerhawkT3
Eso es muy cierto, me ajusto que ahora
ThatGuyRussell
1
@ThatGuyRussell Para hacer un cortocircuito, querrías usar un generador ... algo así any(sum(values) == 2*x for x in values), de esa manera no tendrías que duplicar por adelantado, tan necesario.
Barry
12

El siguiente código se puede usar para comparar iterativamente cada elemento con la suma de los otros, que se calcula a partir de la suma de la lista completa, excluyendo ese elemento.

 l = [a,b,c]
 any(sum(l)-e == e for e in l)
Arcano
fuente
2
Bonito :) Creo que si quitas los []corchetes de la segunda línea, esto incluso or
provocará un
1
que es básicamente any(a + b + c == 2*x for x in [a, b, c]), bastante cerca de la sugerencia del OP
njzk2
Eso es similar, sin embargo, este método se extiende a cualquier número de variables. Incorporé la sugerencia de @psmears sobre cortocircuito.
Arcanum
10

No intentes simplificarlo. En cambio, nombra lo que estás haciendo con una función:

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):
  ...

Reemplazar la condición con algo "inteligente" podría acortarlo, pero no lo hará más legible. Sin embargo, dejarlo así no es muy fácil de leer, porque es difícil saber por qué estás revisando esas tres condiciones de un vistazo. Esto hace que sea absolutamente claro lo que estás buscando.

Con respecto al rendimiento, este enfoque agrega la sobrecarga de una llamada a la función, pero nunca sacrifica la legibilidad por el rendimiento a menos que haya encontrado un cuello de botella que absolutamente debe corregir. Y siempre mida, ya que algunas implementaciones inteligentes son capaces de optimizar y alinear algunas llamadas de función en algunas circunstancias.

Jack
fuente
1
Las funciones deben escribirse solo si espera usar el mismo código en más de un lugar o si el código es complejo. No se menciona la reutilización del código en la pregunta original, y escribir una función para una sola línea de código no solo es excesivo, sino que en realidad perjudica la legibilidad.
Igor Levicki
55
Viniendo de la escuela de cosas de FP, tengo que estar en total desacuerdo y afirmar que las funciones de una línea bien nombradas son algunas de las mejores herramientas para aumentar la legibilidad que encontrarás. Haga una función cada vez que los pasos que está tomando para hacer algo no aporten claridad de inmediato a lo que está haciendo, ya que el nombre de la función le permite especificar qué es lo mejor que cualquier comentario.
Jack
Cualquiera sea la escuela que invoques, es malo cumplir ciegamente un conjunto de reglas. Tener que saltar a otra parte de la fuente para leer esa línea de código oculta dentro de una función solo para poder verificar que realmente hace lo que dice en el nombre, y luego tener que volver al lugar de una llamada para asegúrese de que está pasando los parámetros correctos es un cambio de contexto completamente innecesario En mi opinión, hacer eso perjudica tanto la legibilidad como el flujo de trabajo. Finalmente, ni el nombre de una función ni los comentarios del código son un reemplazo adecuado para la documentación del código.
Igor Levicki
9

Python 3:

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

Se escala a cualquier número de variables:

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

Sin embargo, en general, estoy de acuerdo en que, a menos que tenga más de tres variables, la versión original es más legible.

Vitalii Fedorenko
fuente
3
Esto devuelve resultados incorrectos para algunas entradas debido a errores de redondeo de punto flotante.
pts
Se debe evitar la división por razones de rendimiento y precisión.
Igor Levicki
1
@pts ¿Ninguna implementación devolverá resultados incorrectos debido al redondeo de punto flotante? Incluso a + b == c
osundblad
@osundblad: si a, byc son ints, entonces (a + b + c) / 2 redondea (y puede devolver resultados incorrectos), pero a + b == c es correcto.
pts
3
la división por 2 simplemente disminuye el exponente por uno, por lo que será preciso para cualquier número entero que sea menor que 2 ^ 53 (la parte de fracción de un flotador en python), y para números enteros más grandes puede usar decimal . Por ejemplo, para verificar enteros que son menos de 2 ^ 30 corridos[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

Si la suma de dos términos cualquiera es igual al tercer término, entonces uno de los factores será cero, haciendo que todo el producto sea cero.

mbeckish
fuente
Estaba pensando exactamente lo mismo, pero no puedo negar que su propuesta original es más limpio manera ...
user541686
@Mehrdad - Definitivamente. Realmente no es diferente a(a+b<>c) && (a+c<>b) && (b+c<>a) == false
mbeckish
Es solo que la multiplicación es más costosa que las expresiones lógicas y la aritmética básica.
Igor Levicki
@IgorLevicki: Sí, aunque esa es una preocupación de optimización MUY prematura. ¿Se realizará esto decenas de miles de veces por segundo? En caso afirmativo, es probable que desee ver otra cosa.
mbeckish
@mbeckish - ¿Por qué crees que es prematuro? El código debe escribirse teniendo en cuenta la optimización, no optimizado como una idea posterior. Un día, un pasante copiará este fragmento de código y lo pegará en un bucle crítico de rendimiento en una plataforma integrada que luego se ejecutará en millones de dispositivos que no necesariamente son lentos para lo que hace, pero tal vez desperdicien más energía de la batería. Escribir dicho código solo fomenta las malas prácticas de codificación. En mi opinión, lo que OP debería haber preguntado es si hay una manera de optimizar esa expresión lógica.
Igor Levicki
6

¿Qué tal solo:

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

Tenga en cuenta que esto no funcionará si las variables no están firmadas.

Desde el punto de vista de la optimización del código (al menos en la plataforma x86), esta parece ser la solución más eficiente.

Los compiladores modernos alinearán ambas llamadas a funciones abs () y evitarán la prueba de signos y la ramificación condicional posterior mediante el uso de una secuencia inteligente de instrucciones CDQ, XOR y SUB . Por lo tanto, el código de alto nivel anterior se representará con solo instrucciones ALU de baja latencia y alto rendimiento y solo dos condicionales.

Igor Levicki
fuente
Y creo que fabs()se puede usar para floattipos;).
shA.t
4

La solución proporcionada por Alex Varga "a in (b + c, bc, cb)" es compacta y matemáticamente hermosa, pero en realidad no escribiría el código de esa manera porque el próximo desarrollador que viene no entendería de inmediato el propósito del código .

La solución de Mark Ransom de

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

es más claro pero no mucho más sucinto que

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

Cuando escribo un código que alguien más tendrá que mirar, o que tendré que mirar mucho después, cuando haya olvidado lo que estaba pensando cuando lo escribí, ser demasiado corto o inteligente tiende a hacer más daño que bien. El código debe ser legible. Tan sucinto es bueno, pero no tan sucinto que el próximo programador no pueda entenderlo.

Paul J Abernathy
fuente
Pregunta honesta: ¿por qué la gente siempre asume que el próximo programador será un idiota incapaz de leer el código? Personalmente encuentro esa idea insultante. Si el código debe ser escrito para ser evidentemente obvio para todos los programadores, eso implica que nosotros, como profesión, estamos atendiendo al mínimo común denominador, el menos calificado entre nosotros. Si seguimos haciendo eso, ¿cómo van a mejorar sus habilidades personales? No veo esto en otras profesiones. ¿Cuándo fue la última vez que viste a un compositor escribiendo una partitura musical simple para que cada músico pueda tocarla sin importar el nivel de habilidad?
Igor Levicki
66
El problema es que incluso los programadores tienen energía mental limitada, por lo tanto, ¿desea gastar su energía mental limitada en el algoritmo y los aspectos de nivel superior del programa, o en descubrir qué significa una línea de código complicada cuando se puede expresar de manera más simple? ? La programación es difícil, así que no se lo dificulte innecesariamente, ya que un corredor olímpico no correría una carrera con una mochila pesada solo porque puede hacerlo. Como dice Steve McConell en Code Complete 2, la legibilidad es uno de los aspectos más importantes del código.
Paul J Abernathy
2

La solicitud es más compacta O más pitónica: probé con una mano más compacta.

dado

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

Esto es 2 caracteres menos que el original

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

prueba con:

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

adicionalmente, dado:

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

Esto es equivalente

any(h(f((a,b,c))))
segunda Guerra Mundial
fuente
Bueno, son dos caracteres más cortos que el original, pero no el que dio el OP inmediatamente después, que dijo que está usando actualmente. El original también incluye muchos espacios en blanco, que esto omite siempre que sea posible. También está la pequeña cuestión de la función g()que debe definir para que esto funcione. Dado todo eso, diría que es significativamente más grande.
TigerhawkT3
@ TigerhawkT3, lo interpreté como una solicitud de una expresión / línea más corta. ver edición para más mejoras .
wwii
44
Nombres de funciones muy malos, solo adecuados para un código de golf.
0xc0de
@ 0xc0de - lo siento, no juego. Adecuado puede ser bastante subjetivo y dependiente de las circunstancias, pero diferiré a la comunidad.
wwii
No veo cómo es esto más compacto cuando tiene más caracteres que el código original.
Igor Levicki
1

Quiero presentar lo que veo como la respuesta más pitónica :

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

El caso general, no optimizado:

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 

En términos del Zen de Python, creo que las declaraciones enfatizadas son más seguidas que de otra respuesta:

El zen de Python, por Tim Peters

Hermoso es mejor que feo.
Explícito es mejor que implícito.
Simple es mejor que complejo.
Complejo es mejor que complicado.
Plano es mejor que anidado.
Escaso es mejor que denso.
La legibilidad cuenta.
Los casos especiales no son lo suficientemente especiales como para romper las reglas.
Aunque la practicidad supera la pureza.
Los errores nunca deben pasar en silencio.
A menos que sea silenciado explícitamente.
Ante la ambigüedad, rechaza la tentación de adivinar.
Debe haber una, y preferiblemente solo una, forma obvia de hacerlo.
Aunque esa manera puede no ser obvia al principio a menos que seas holandés.
Ahora es mejor que nunca.
Aunque nunca es mejor quederecho ahora.
Si la implementación es difícil de explicar, es una mala idea.
Si la implementación es fácil de explicar, puede ser una buena idea.
Los espacios de nombres son una gran idea, ¡hagamos más de eso!

sevenforce
fuente
1

Como un viejo hábito de mi programación, creo que colocar una expresión compleja a la derecha en una cláusula puede hacerlo más legible de esta manera:

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

Más ():

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

Y también creo que usar líneas múltiples también puede tener más sentidos como este:

((a == b+c) or 
 (b == a+c) or 
 (c == a+b))
shA.t
fuente
0

De manera genérica,

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

si, manipular una variable de entrada está bien para ti,

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

si quieres explotar usando hacks de bits, puedes usar "!", ">> 1" y "<< 1"

Evité la división, aunque permite el uso para evitar dos multiplicaciones para evitar errores de redondeo. Sin embargo, verifique si hay desbordamientos

Padmabushan
fuente
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
Hammerita
fuente
Las funciones deben escribirse solo si espera usar el mismo código en más de un lugar o si el código es complejo. No se menciona la reutilización del código en la pregunta original, y escribir una función para una sola línea de código no solo es excesivo, sino que en realidad perjudica la legibilidad.
Igor Levicki
No estoy de acuerdo con que perjudique la legibilidad; Si elige un nombre adecuado, puede mejorar la legibilidad (pero no hago ninguna representación en cuanto a la calidad del nombre que elegí en esta respuesta). Además, puede ser útil asignarle un nombre a un concepto, lo cual tendrá que hacer siempre y cuando se obligue a darle un buen nombre a su función. Las funciones son buenas. En cuanto a si la funcionalidad es lo suficientemente compleja como para beneficiarse de estar encapsulada en una función, ese es un juicio subjetivo.
Hammerite