Forma compacta de escrever (a + b == c ou a + c == b ou b + c == a)

136

Existe uma maneira mais compacta ou pitônica de escrever a expressão booleana

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

eu vim com

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

mas isso é um pouco estranho.

qwr
fonte
16
Mais compacto? Possivelmente. Mais pitônico? Improvável.
chepner
126
Faça um favor ao seu futuro e mantenha-o na forma original: é o único que informa imediatamente o objetivo disso. Não modifique isso. “Simples é melhor que complexo.”, “Legibilidade conta.”. "Se a implementação é difícil de explicar, é uma má idéia."
poke
21
Python == ilegível?
Nhaarman 19/08/2015
3
@wwii Eles não são mutuamente exclusivos. Ver a = 0, b = 0, c = 0;)
Honza Brabec
1
O que @phresnel disse. Em vez de tentar "simplificar" a expressão, envolva-a em uma função com um nome descritivo.
Cefalópode

Respostas:

206

Se olharmos para o Zen de Python, enfatize o meu:

O Zen de Python, de Tim Peters

Bonito é melhor que feio.
Explícito é melhor que implícito.
Simples é melhor que complexo.
Complexo é melhor que complicado.
Flat é melhor que aninhado.
Esparso é melhor que denso.
Legibilidade conta.
Casos especiais não são especiais o suficiente para violar as regras.
Embora praticidade supere a pureza.
Os erros nunca devem passar silenciosamente.
A menos que seja explicitamente silenciado.
Diante da ambiguidade, recuse a tentação de adivinhar.
Deve haver uma - e preferencialmente apenas uma - maneira óbvia de fazê-lo.
Embora esse caminho possa não ser óbvio a princípio, a menos que você seja holandês.
Agora é melhor do que nunca.
Embora nunca seja frequentemente melhor do queagora mesmo .
Se a implementação é difícil de explicar, é uma má ideia.
Se a implementação é fácil de explicar, pode ser uma boa ideia.
Os namespaces são uma ótima idéia - vamos fazer mais!

A solução mais pitônica é aquela que é mais clara, mais simples e mais fácil de explicar:

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

Melhor ainda, você nem precisa conhecer Python para entender esse código! É isso fácil. Esta é, sem reservas, a melhor solução. Qualquer outra coisa é masturbação intelectual.

Além disso, essa também é provavelmente a solução com melhor desempenho, pois é a única dentre todas as propostas que causam curto-circuito. Se a + b == c, apenas uma única adição e comparação for feita.

Barry
fonte
11
Melhor ainda, coloque alguns parênteses para tornar clara a intenção.
Bryan Oakley
3
A intenção já é clara, sem parênteses. Parênteses dificultariam a leitura - por que o autor usa parênteses quando a precedência já cobre isso?
Route de milhas
1
Outra observação sobre a tentativa de ser muito inteligente: você pode introduzir bugs imprevistos perdendo condições que não considerou. Em outras palavras, você pode pensar que sua nova solução compacta é equivalente, mas não é em todos os casos. A menos que haja uma razão convincente para codificar o contrário (desempenho, restrições de memória e assim por diante), a clareza é fundamental.
Rob Craig
Depende de qual é a fórmula. Veja 'Explícito é melhor que implícito'; pode ser que a abordagem 'classificar primeiro' expresse mais claramente o que o programa está fazendo, ou um dos outros. Acho que não podemos julgar a questão.
Thomas Ahle
101

Resolvendo as três igualdades para a:

a in (b+c, b-c, c-b)
Alex Varga
fonte
4
O único problema com isso são os efeitos colaterais. Se b ou c forem expressões mais complexas, elas serão executadas várias vezes.
Silvio Mayolo
3
@Kroltan Meu argumento foi que eu realmente respondi sua pergunta, que pedia uma representação "mais compacta". Veja: en.m.wikipedia.org/wiki/Short-circuit_evaluation
Alex Varga
24
Qualquer pessoa que esteja lendo este código provavelmente o amaldiçoará por ser "inteligente".
Karoly Horvath
5
@SilvioMayolo O mesmo acontece com o original
Izkata
1
@AlexVarga, "Meu argumento foi que eu realmente respondi sua pergunta". Você fez; ele usa 30% menos caracteres (colocando espaços entre operadores). Eu não estava tentando dizer que sua resposta estava errada, apenas comentando como é idiomática (pitônica). Boa resposta.
Paul Draper
54

Python tem uma anyfunção que executa um orem todos os elementos de uma sequência. Aqui converti sua declaração em uma tupla de 3 elementos.

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

Observe que orhá um curto-circuito; portanto, se o cálculo das condições individuais for caro, talvez seja melhor manter sua construção original.

Mark Ransom
fonte
2
any()e all()curto-circuito também.
precisa saber é o seguinte
42
@ TigerhawkT3 Mas não neste caso; as três expressões serão avaliadas antes que a tupla exista e a tupla existirá antes anymesmo da execução.
Poke
13
Ah entendo. Eu acho que é apenas quando há um gerador ou um iterador lento semelhante lá.
precisa saber é o seguinte
4
anye all"curto-circuito" o processo de examinar o iterável que eles recebem; mas se esse iterável for uma sequência e não um gerador, ele já foi totalmente avaliado antes da ocorrência da chamada da função .
22415 Karl Knechtel
Isto tem a vantagem de que é fácil de dividir em várias linhas (double-indent os argumentos para any,-indent único a ):na ifdeclaração), o que ajuda muito para facilitar a leitura quando a matemática está envolvida
Izkata
40

Se você sabe que está lidando apenas com números positivos, isso funcionará e é bastante limpo:

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

Como eu disse, isso funciona apenas para números positivos; mas se você souber eles serão positivos, essa é uma solução IMO muito legível, mesmo diretamente no código e não em uma função.

Você pode fazer isso, o que pode fazer um pouco de computação repetida; mas você não especificou o desempenho como sua meta:

from itertools import permutations

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

Ou sem permutations()e a possibilidade 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()

Eu provavelmente colocaria isso, ou qualquer outra solução, em uma função. Então você pode simplesmente chamar a função no seu código.

Pessoalmente, a menos que eu precisasse de mais flexibilidade do código, usaria apenas o primeiro método na sua pergunta. É simples e eficiente. Eu ainda posso colocá-lo em uma função:

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

Isso é bastante pitonico, e é possivelmente a maneira mais eficiente de fazer isso (a função extra chama de lado); embora você não deva se preocupar muito com o desempenho, a menos que esteja causando um problema.

Cifase
fonte
especialmente se pudermos assumir que a, b, c são todos não negativos.
Cphlewis 19/08/2015
Acho a frase "nem sempre funciona" um pouco confusa. A primeira solução só funciona se você tiver certeza de que seus números não são negativos. Por exemplo, com (a, b, c) = (-3, -2, -1), você tem a + b! = C, mas b + c = a. Casos semelhantes com (-1, 1, 2) e (-2, -1, 1).
userNumber
@ número de usuário, você sabe, eu notei isso antes; não sei por que não consertei.
Cyphase
Sua solução principal não funciona para uma grande classe de entradas, enquanto a sugestão do OP funciona para todas as entradas. Como "não está funcionando" mais pitonico do que "está funcionando"?
Barry
3
Ooh, snap. " Se você sabe que está lidando apenas com números positivos , isso funcionará e é bastante limpo". Todos os outros trabalham para qualquer número, mas se você souber que está lidando apenas com números positivos , o primeiro é muito legível / IMO Pythonic.
Cifásica
17

Se você usará apenas três variáveis, seu método inicial:

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

Já é muito pitônico.

Se você planeja usar mais variáveis, seu método de raciocínio com:

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

É muito inteligente, mas vamos pensar no porquê. Por que isso funciona?
Bem, através de uma aritmética simples, vemos que:

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

E isso vai ter que segurar verdade para a, b ou c, o que significa que sim, vai igualar 2*a, 2*bou2*c . Isso será verdadeiro para qualquer número de variáveis.

Portanto, uma boa maneira de escrever isso rapidamente seria simplesmente ter uma lista de suas variáveis ​​e comparar sua soma com uma lista dos valores dobrados.

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

Dessa forma, para adicionar mais variáveis ​​à equação, tudo o que você precisa fazer é editar sua lista de valores por 'n' novas variáveis, não escrevendo 'n' equações

ThatGuyRussell
fonte
4
Que tal a=-1, b=-1, c=-2,, em seguida a+b=c, mas a+b+c = -4e 2*max(a,b,c)é-2
Eric Renouf
Obrigado que é verdade, eu precisaria usar abs. Fazendo esse ajuste agora.
precisa saber é o seguinte
2
Depois de atendê-lo com meia dúzia de abs()chamadas, é Pythonic do que o trecho do OP (na verdade, eu o chamaria significativamente menos legível).
precisa saber é o seguinte
Isso é muito verdadeiro, vou ajustar isso agora
ThatGuyRussell
1
@ThatGuyRussell A fim de causar um curto-circuito, você gostaria de usar um gerador ... algo como any(sum(values) == 2*x for x in values), dessa forma, você não precisaria fazer toda a duplicação na frente, conforme necessário.
Barry
12

O código a seguir pode ser usado para comparar iterativamente cada elemento com a soma dos outros, que é calculada a partir da soma de toda a lista, excluindo esse elemento.

 l = [a,b,c]
 any(sum(l)-e == e for e in l)
Arcano
fonte
2
Nice :) Eu acho que se você remover os []suportes da segunda linha, isso vai mesmo curto-circuito como o original com or...
psmears
1
que é basicamente any(a + b + c == 2*x for x in [a, b, c]), muito próximo a sugestão do OP
njzk2
Isso é semelhante, porém esse método se estende a qualquer número de variáveis. Incorporei a sugestão do @psmears sobre curtos-circuitos.
Arcano
10

Não tente simplificá-lo. Em vez disso, nomeie o que você está fazendo com uma função:

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

Substitua a condição por algo "inteligente" que a torne mais curta, mas não a tornará mais legível. No entanto, deixar como está também não é muito legível, porque é complicado saber por que você está verificando essas três condições rapidamente. Isso torna absolutamente claro o que você está procurando.

Em relação ao desempenho, essa abordagem adiciona a sobrecarga de uma chamada de função, mas nunca sacrifica a legibilidade pelo desempenho, a menos que você encontre um gargalo que você absolutamente deve corrigir. E sempre meça, pois algumas implementações inteligentes são capazes de otimizar e incluir algumas chamadas de função em algumas circunstâncias.

Jack
fonte
1
As funções devem ser escritas apenas se você espera usar o mesmo código em mais de um ponto ou se o código for complexo. Não há menção à reutilização de código na pergunta original, e escrever uma função para uma única linha de código não é apenas um exagero, mas na verdade prejudica a legibilidade.
Igor Levicki
5
Vindo da escola de FP, eu tenho que discordar completamente e afirmar que as funções de uma linha bem nomeadas são algumas das melhores ferramentas para aumentar a legibilidade que você encontrará. Faça uma função sempre que as etapas que você estiver executando para fazer algo não tragam clareza imediatamente ao que você está fazendo, pois o nome da função permite especificar o que melhor do que qualquer comentário poderia.
Jack
Qualquer escola que você invoque, é ruim aderir cegamente a um conjunto de regras. Ter que pular para outra parte da fonte para ler essa linha de código oculta dentro de uma função, apenas para poder verificar se realmente faz o que diz o nome e depois voltar ao local de uma chamada para verifique se você está passando os parâmetros corretos, é uma mudança de contexto completamente desnecessária. Na minha opinião, isso prejudica a legibilidade e o fluxo de trabalho. Finalmente, nem o nome de uma função nem os comentários do código são um substituto adequado para a documentação do 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)
...

É escalável para qualquer número de variáveis:

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

No entanto, em geral, concordo que, a menos que você tenha mais de três variáveis, a versão original é mais legível.

Vitalii Fedorenko
fonte
3
Isso retorna resultados incorretos para algumas entradas devido a erros de arredondamento de ponto flutuante.
pts
A divisão deve ser evitada por razões de desempenho e precisão.
Igor Levicki
1
@pts Nenhuma implementação retornará resultados incorretos devido ao arredondamento do ponto flutuante? Mesmo a + b == c
osundblad
@osundblad: Se a, bec são ints, (a + b + c) / 2 faz arredondamento (e pode retornar resultados incorretos), mas a + b == c é preciso.
pts
3
A divisão por 2 simplesmente diminui o expoente em um, por isso será preciso para qualquer número inteiro menor que 2 ^ 53 (a parte da fração de um flutuador em python) e para números inteiros maiores você pode usar decimal . Por exemplo, para verificar números inteiros inferiores a 2 ^ 30, execute[x for x in range(pow(2,30)) if x != ((x * 2)/ pow(2,1))]
Vitalii Fedorenko 27/08/2015
6
(a+b-c)*(a+c-b)*(b+c-a) == 0

Se a soma de quaisquer dois termos for igual ao terceiro termo, um dos fatores será zero, tornando o produto inteiro zero.

mbeckish
fonte
Eu estava pensando exatamente a mesma coisa, mas eu não posso negar que a sua proposta original é a maneira mais limpa ...
user541686
@ Mehrdad - Definitivamente. Realmente não é diferente de(a+b<>c) && (a+c<>b) && (b+c<>a) == false
mbeckish 19/08/2015
É apenas que a multiplicação é mais cara que as expressões lógicas e a aritmética básica.
Igor Levicki 20/08/2015
@IgorLevicki - Sim, embora seja uma preocupação de otimização MUITO prematura. Isso será realizado dezenas de milhares de vezes por segundo? Se sim, você provavelmente gostaria de olhar para outra coisa.
mbeckish
@ embeckish - Por que você acha que é prematuro? O código deve ser escrito com a otimização em mente, não otimizado como uma reflexão posterior. Um dia, algum estagiário copiará esse trecho de código e o colará em algum loop crítico de desempenho em uma plataforma incorporada que será executada em milhões de dispositivos, não necessariamente demorando para o que faz, mas talvez gastando mais energia da bateria. Escrever esse código apenas incentiva práticas de codificação ruins. Na minha opinião, o que o OP deveria ter perguntado é se existe uma maneira de otimizar essa expressão lógica.
Igor Levicki
6

Que tal apenas:

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

Observe que isso não funcionará se as variáveis ​​não forem assinadas.

Do ponto de vista da otimização de código (pelo menos na plataforma x86), essa parece ser a solução mais eficiente.

Os compiladores modernos alinham as chamadas de função abs () e evitam o teste de sinais e a ramificação condicional subsequente usando uma sequência inteligente de instruções CDQ, XOR e SUB . O código de alto nível acima será representado apenas com instruções ALU de baixa latência e alta taxa de transferência e apenas dois condicionais.

Igor Levicki
fonte
E eu acho que fabs()pode ser usado para floattipos;).
shA.t 26/08/15
4

A solução fornecida por Alex Varga "a em (b + c, bc, cb)" é compacta e matematicamente bonita, mas eu não escreveria o código dessa maneira, porque o próximo desenvolvedor que chegasse não entenderia imediatamente o objetivo do código .

A solução de Mark Ransom

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

é mais claro, mas não muito mais sucinto do que

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

Ao escrever um código que outra pessoa terá que procurar, ou que eu precisarei procurar muito depois, quando me esqueci do que estava pensando quando o escrevi, ser muito curto ou inteligente tende a causar mais mal do que bem. O código deve ser legível. Tão sucinto é bom, mas não tão sucinto que o próximo programador não possa entender.

Paul J Abernathy
fonte
Pergunta honesta: por que as pessoas sempre assumem que o próximo programador será um idiota incapaz de ler código? Pessoalmente, acho essa idéia insultante. Se o código deve ser escrito para ser flagrantemente óbvio para todo programador, isso implica que nós, como profissão, estamos atendendo ao menor denominador comum, o menos qualificado entre nós. Se continuarmos fazendo isso, como eles vão melhorar suas habilidades pessoais? Não vejo isso em outras profissões. Quando foi a última vez que você viu um compositor escrevendo uma partitura simples, para que todo músico pudesse tocá-la, independentemente do nível de habilidade?
Igor Levicki
6
A questão é que mesmo os programadores têm energia mental limitada, então você deseja gastar sua energia mental limitada no algoritmo e nos aspectos de nível mais alto do programa, ou para descobrir o que significa alguma linha complicada de código quando pode ser expressa de maneira mais simples ? A programação é difícil, por isso não torne as coisas mais difíceis desnecessariamente, assim como um corredor olímpico não faria uma corrida com uma mochila pesada só porque pode. Como Steve McConell diz no Code Complete 2, a legibilidade é um dos aspectos mais importantes do código.
Paul J Abernathy
2

A solicitação é mais compacta OU mais pitônica - tentei minha mão de forma mais compacta.

dado

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

São 2 caracteres a menos que o original

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

teste com:

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

além disso, dado:

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

Isso é equivalente

any(h(f((a,b,c))))
Segunda Guerra Mundial
fonte
Bem, são dois caracteres mais curtos que o original, mas não o que o OP deu logo depois, que ele disse que está usando no momento. O original também inclui muitos espaços em branco, omitidos sempre que possível. Há também a pequena questão da função que g()você precisa definir para que isso funcione. Dado tudo isso, eu diria que é significativamente maior.
precisa saber é o seguinte
@ TigerhawkT3, interpretei-o como um pedido para uma expressão / linha mais curta. veja editar para melhorias adicionais .
Wwii
4
Nomes de funções muito ruins, adequados apenas para um código de golfe.
0xc0de
@ 0xc0de - desculpe, eu não jogo. Adequado pode ser bastante subjetivo e dependente das circunstâncias - mas vou adiar para a comunidade.
Wwii
Não vejo como isso é mais compacto quando possui mais caracteres que o código original.
Igor Levicki
1

Quero apresentar o que considero a resposta mais 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))

O caso geral, não otimizado:

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 

Em termos do Zen de Python, acho que as declarações enfatizadas são mais seguidas do que em outras respostas:

O Zen de Python, de Tim Peters

Bonito é melhor que feio.
Explícito é melhor que implícito.
Simples é melhor que complexo.
Complexo é melhor que complicado.
Flat é melhor que aninhado.
Esparso é melhor que denso.
Legibilidade conta.
Casos especiais não são especiais o suficiente para violar as regras.
Embora praticidade supere a pureza.
Os erros nunca devem passar silenciosamente.
A menos que seja explicitamente silenciado.
Diante da ambiguidade, recuse a tentação de adivinhar.
Deve haver uma - e preferencialmente apenas uma - maneira óbvia de fazê-lo.
Embora esse caminho possa não ser óbvio a princípio, a menos que você seja holandês.
Agora é melhor do que nunca.
Embora nunca seja frequentemente melhor do queagora mesmo .
Se a implementação é difícil de explicar, é uma má ideia.
Se a implementação é fácil de explicar, pode ser uma boa ideia.
Os namespaces são uma ótima idéia - vamos fazer mais!

sevenforce
fonte
1

Como um hábito antigo da minha programação, acho que colocar expressões complexas à direita em uma cláusula pode torná-las mais legíveis assim:

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

Plus ():

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

E também acho que o uso de várias linhas também pode fazer mais sentidos assim:

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

De uma maneira genérica,

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

se manipular uma variável de entrada é bom para você,

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

se você quiser explorar usando hacks de bits, poderá usar "!", ">> 1" e "<< 1"

Evitei a divisão, embora ele permita o uso para evitar duas multiplicações para evitar erros de arredondamento. No entanto, verifique se há estouros

Padmabushan
fonte
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
Hammerite
fonte
As funções devem ser escritas apenas se você espera usar o mesmo código em mais de um ponto ou se o código for complexo. Não há menção à reutilização de código na pergunta original, e escrever uma função para uma única linha de código não é apenas um exagero, mas na verdade prejudica a legibilidade.
Igor Levicki
Não concordo que isso prejudique a legibilidade; se você escolher um nome adequado, poderá melhorar a legibilidade (mas não faço representação da qualidade do nome que escolhi nesta resposta). Além disso, pode ser útil dar um nome a um conceito, o que você precisará fazer desde que se force a dar um bom nome à sua função. Funções são boas. Se a funcionalidade é suficientemente complexa para se beneficiar de ser encapsulado em uma função, esse é um julgamento subjetivo.
precisa saber é o seguinte