Компактный способ записи (a + b == c или a + c == b или b + c == a)


137

Есть ли более компактный или питонический способ написать логическое выражение

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

Я придумал

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

но это немного странно.


16
Более компактный? Возможно. Более питонический? Навряд ли.
chepner

126
Сделайте себе одолжение и сохраните его в первоначальном виде: это единственное, что сразу говорит о цели этого. Не меняйте это. «Простое лучше, чем сложное», «Удобочитаемость имеет значение». «Если реализацию трудно объяснить, это плохая идея».
poke

21
Pythonic == нечитаемый?
nhaarman

3
@wwii Они не исключают друг друга. Смотрите a = 0, b = 0, c = 0;)
Honza Brabec

1
Что сказал @phresnel. Вместо того, чтобы пытаться «упростить» выражение, заключите его в функцию с описательным именем.
Cephalopod

Ответы:


206

Если мы посмотрим на дзен Python, акцент мой:

Дзен Питона, Тим Питерс

Красивое лучше уродливого.
Явное лучше, чем неявное.
Лучше простое, чем сложное.
Сложный лучше, чем сложный.
Плоский лучше, чем вложенный.
Лучше разреженное, чем плотное.
Читаемость имеет значение.
Особых случаев недостаточно, чтобы нарушать правила.
Хотя практичность лучше чистоты.
Ошибки никогда не должны проходить незаметно.
Если явно не отключен.
Перед лицом двусмысленности откажитесь от соблазна угадать.
Должен быть один - а желательно только один - очевидный способ сделать это.
Хотя сначала этот способ может быть не очевиден, если вы не голландец.
Лучше сейчас, чем никогда.
Хотя никогда не бывает лучше, чемпрямо сейчас.
Если реализацию трудно объяснить, это плохая идея.
Если реализацию легко объяснить, это может быть хорошей идеей.
Пространства имен - одна отличная идея - давайте сделаем их больше!

Самое питоническое решение - самое ясное, простое и легкое для объяснения:

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

Более того, вам даже не нужно знать Python, чтобы понять этот код! Это что легко. Это, безоговорочно, лучшее решение. Все остальное - интеллектуальная мастурбация.

Кроме того, это, вероятно, также лучшее решение, так как это единственное из всех предложений, которое вызывает короткое замыкание. Если a + b == cвыполняется только одно сложение и сравнение.


11
Еще лучше, добавьте скобки, чтобы сделать намерение кристально ясным.
Брайан Окли

3
Намерение уже совершенно ясно без скобок. Скобки затруднили бы чтение - почему автор использует скобки, когда приоритет уже покрывает это?
Miles Rout

1
Еще одно замечание о попытках быть слишком умным: вы можете внести непредвиденные ошибки, пропустив условия, которые вы не учли. Другими словами, вы можете подумать, что ваше новое компактное решение эквивалентно, но это не во всех случаях. Если нет веской причины кодировать иначе (производительность, ограничения памяти и т. Д.), Ясность имеет значение.
Роб Крейг

Это зависит от того, для чего предназначена формула. Посмотрите на «Явное лучше, чем неявное», возможно, подход «сначала сортировка» более четко выражает то, что делает программа или что-то еще. Не думаю, что мы можем судить по этому вопросу.
Thomas Ahle

101

Решая три равенства для a:

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

4
Единственная проблема с этим - побочные эффекты. Если b или c - более сложные выражения, они будут выполняться несколько раз.
Сильвио Майоло

3
@Kroltan Я хотел сказать, что на самом деле я ответил на его вопрос, который требовал «более компактного» представления. См .: en.m.wikipedia.org/wiki/Short-circuit_evaluation
Алекс Варга,

24
Любой, кто читает этот код, вероятно, проклянет вас за «умность».
Кароли Хорват

5
@SilvioMayolo То же самое и с оригиналом
Изката

1
@AlexVarga, «Я хотел сказать, что я действительно ответил на его вопрос». Ты сделал; в нем используется на 30% меньше символов (между операторами помещаются пробелы). Я не пытался сказать, что ваш ответ был неправильным, просто комментировал, насколько он идиоматичен (питонический). Хороший ответ.
Пол Дрейпер,

54

В Python есть anyфункция, которая воздействует orна все элементы последовательности. Здесь я преобразовал ваше утверждение в кортеж из трех элементов.

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

Обратите внимание, что orэто короткое замыкание, поэтому, если расчет индивидуальных условий дорогостоящий, может быть лучше сохранить исходную конструкцию.


2
any()и all()короткое замыкание тоже.
TigerhawkT3

43
@ TigerhawkT3 Но не в этом случае; три выражения будут вычислены до того, как кортеж будет существовать, и кортеж будет существовать до anyтого, как запустится.
poke

13
Ах я вижу. Я думаю, это только тогда, когда там есть генератор или подобный ленивый итератор.
TigerhawkT3,

4
anyи all"закоротить" процесс проверки итерации, которую они дают; но если эта итерация является последовательностью, а не генератором, то она уже была полностью оценена перед вызовом функции .
Карл Кнехтель

Это имеет то преимущество, что его легко разделить на несколько строк (двойной отступ для аргументов any, одинарный отступ ):в ifоператоре), что очень помогает для удобочитаемости, когда задействована математика
Изката

40

Если вы знаете, что имеете дело только с положительными числами, это сработает и довольно чисто:

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

Как я уже сказал, это работает только для положительных чисел; но если вы знаете, что они будут положительными, это очень удобочитаемое решение, IMO, даже непосредственно в коде, а не в функции.

Вы могли бы сделать это, что может потребовать повторных вычислений; но вы не указали производительность в качестве своей цели:

from itertools import permutations

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

Или без permutations()и возможности повторных вычислений:

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

Я бы, вероятно, поместил это или любое другое решение в функцию. Затем вы можете просто вызвать функцию в своем коде.

Лично, если мне не нужна была большая гибкость кода, я бы просто использовал первый метод в вашем вопросе. Это просто и эффективно. Я все еще могу поместить это в функцию:

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

Это довольно Pythonic, и, возможно, это наиболее эффективный способ сделать это (не говоря уже о дополнительном вызове функции); хотя в любом случае не стоит слишком беспокоиться о производительности, если только она не вызывает проблемы.


особенно если мы можем предположить, что все a, b, c неотрицательны.
cphlewis

Фраза «не всегда работает» меня немного сбивает с толку. Первое решение работает только в том случае, если вы точно знаете, что ваши числа неотрицательны. Например, с (a, b, c) = (-3, -2, -1) у вас есть a + b! = C, но b + c = a. Аналогичные случаи с (-1, 1, 2) и (-2, -1, 1).
usernumber

@usernumber, я заметил это раньше; не уверен, почему я не исправил это.
Cyphase

Ваше лучшее решение не работает для большого класса входов, тогда как предложение OP работает для всех входов. Как "не работает" больше Pythonic, чем "работает"?
Барри

3
Ох, хватка. « Если вы знаете, что имеете дело только с положительными числами , это будет работать, и это довольно чисто». Все остальные работают с любыми числами, но если вы знаете, что имеете дело только с положительными числами , верхний из них очень удобен для чтения / Pythonic IMO.
Cyphase

17

Если вы будете использовать только три переменные, тогда ваш исходный метод:

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

Уже очень питонический.

Если вы планируете использовать больше переменных, тогда ваш метод рассуждений с:

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

Очень умно, но давайте подумаем, почему. Почему это работает?
С помощью простой арифметики мы видим, что:

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

И это будет держать верно для любого а, Ь, или с, что означает , что да она будет равна 2*a, 2*bили 2*c. Это будет верно для любого количества переменных.

Поэтому хороший способ быстро написать это - просто иметь список ваших переменных и сравнивать их сумму со списком удвоенных значений.

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

Таким образом, чтобы добавить больше переменных в уравнение, все, что вам нужно сделать, это отредактировать список значений, добавив n новых переменных, а не писать n уравнений.


4
А как насчет a=-1, b=-1, c=-2, тогда a+b=c, но a+b+c = -4и 2*max(a,b,c)это-2
Эрик Ренуф

Спасибо, что правда, мне нужно было бы использовать пресс. Сделайте это сейчас.
ThatGuyRussell

2
После добавления полдюжины abs()вызовов это Pythonic, чем фрагмент OP (на самом деле я бы назвал его значительно менее читаемым).
TigerhawkT3

Это очень верно, я
исправлю

1
@ThatGuyRussell Для короткого замыкания вам нужно использовать генератор ... что-то вроде того any(sum(values) == 2*x for x in values), чтобы вам не пришлось делать все дублирование заранее, когда это необходимо.
Барри

12

Следующий код можно использовать для итеративного сравнения каждого элемента с суммой других, которая вычисляется из суммы всего списка, исключая этот элемент.

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

2
Приятно :) Я думаю, что если вы удалите []скобки из второй строки, это приведет к короткому замыканию, как в оригинале, с or...
psmears

1
что, по сути any(a + b + c == 2*x for x in [a, b, c]), довольно близко к предложению ОП
njzk2

Это похоже, однако этот метод распространяется на любое количество переменных. Я учел предложение @psmears о коротком замыкании.
Arcanum

10

Не пытайтесь его упростить. Вместо этого назовите то, что вы делаете с функцией:

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

Замена условия на что-нибудь «умное» может сделать его короче, но не сделает его более читабельным. Однако оставить его в таком виде тоже не очень удобно, потому что сложно сразу понять, почему вы проверяете эти три условия. Это делает абсолютно ясным, что вы проверяете.

Что касается производительности, этот подход действительно увеличивает накладные расходы на вызов функции, но никогда не жертвует удобочитаемостью ради производительности, если вы не нашли узкое место, которое необходимо исправить. И всегда измеряйте, поскольку некоторые умные реализации могут оптимизировать и встраивать некоторые вызовы функций в некоторых случаях.


1
Функции следует писать только в том случае, если вы ожидаете использовать один и тот же код более чем в одном месте или если код сложный. В исходном вопросе нет упоминания о повторном использовании кода, и написание функции для одной строки кода не только излишне, но и фактически ухудшает читаемость.
Игорь Левицки

5
Исходя из школы вещей FP, я вынужден полностью не согласиться и заявить, что хорошо названные однострочные функции являются одними из лучших инструментов для повышения читабельности, которые вы когда-либо найдете. Создавайте функцию всякий раз, когда шаги, которые вы предпринимаете, чтобы что-то сделать, не сразу вносят ясность в то, что вы делаете, поскольку имя функции позволяет вам указать, что лучше, чем любой комментарий.
Джек

Какую бы школу вы ни выбрали, слепо придерживаться набора правил - плохо. Необходимость перехода к другой части источника, чтобы прочитать эту одну строку кода, спрятанную внутри функции, просто чтобы иметь возможность убедиться, что она действительно выполняет то, что написано в имени, а затем необходимость переключиться обратно на место вызова убедитесь, что вы передаете правильные параметры, это совершенно ненужное переключение контекста. На мой взгляд, это ухудшает читаемость и рабочий процесс. Наконец, ни имя функции, ни комментарии к коду не могут заменить документацию по коду.
Игорь Левицки

9

Python 3:

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

Он масштабируется до любого количества переменных:

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

Однако в целом я согласен с тем, что исходная версия более читабельна, если у вас не более трех переменных.


3
Это возвращает неверные результаты для некоторых входных данных из-за ошибок округления с плавающей запятой.
пц

Деления следует избегать из соображений производительности и точности.
Игорь Левицки

1
@pts Не будет ли какая-либо реализация возвращать неверные результаты из-за округления с плавающей запятой? Even a + b == c
osundblad

@osundblad: Если a, b и c - целые числа, тогда (a + b + c) / 2 округляет (и может возвращать неверные результаты), но a + b == c является точным.
пц

3
деление на 2 просто уменьшает показатель степени на единицу, поэтому оно будет точным для любого целого числа, которое меньше 2 ^ 53 (дробная часть числа с плавающей запятой в python), а для больших целых чисел вы можете использовать десятичное . Например, чтобы проверить целые числа, которые меньше 2 ^ 30, выполните[x for x in range(pow(2,30)) if x != ((x * 2)/ pow(2,1))]
Виталий Федоренко

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

Если сумма любых двух членов равна третьему члену, то один из множителей будет равен нулю, в результате чего весь продукт будет равен нулю.


Я думал о том же самом, но я не могу отрицать, что его первоначальное предложение намного чище ...
user541686

@Mehrdad - Определенно. Это действительно ничем не отличается от(a+b<>c) && (a+c<>b) && (b+c<>a) == false
mbeckish

Просто умножение дороже логических выражений и базовой арифметики.
Игорь Левицки

@IgorLevicki - Да, хотя это ОЧЕНЬ преждевременная проблема оптимизации. Будет ли это повторяться десятки тысяч раз в секунду? Если да, то вы, вероятно, захотите посмотреть на что-нибудь еще.
mbeckish

@mbeckish - Как вы думаете, почему это преждевременно? Код должен быть написан с учетом оптимизации, а не оптимизацией на потом. Однажды какой-нибудь стажер скопирует этот фрагмент кода и вставит его в некоторый критический для производительности цикл на встроенной платформе, которая затем будет работать на миллионах устройств, не обязательно медленных для своей работы, но, возможно, тратя больше энергии батареи. Написание такого кода просто поощряет плохие методы кодирования. На мой взгляд, OP должен был спросить, есть ли способ оптимизировать это логическое выражение.
Игорь Левицки

6

Как насчет всего:

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

Обратите внимание, что это не сработает, если переменные беззнаковые.

С точки зрения оптимизации кода (по крайней мере, на платформе x86) это кажется наиболее эффективным решением.

Современные компиляторы встраивают вызовы функций abs () и избегают проверки знаков и последующего условного перехода, используя продуманную последовательность инструкций CDQ, XOR и SUB . Таким образом, приведенный выше высокоуровневый код будет представлен только инструкциями ALU с низкой задержкой, высокой пропускной способностью и всего двумя условными выражениями.


И думаю fabs()можно использовать для floatтипов;).
shA.t

4

Решение, предоставленное Алексом Варгой «a in (b + c, bc, cb)», компактно и математически красиво, но на самом деле я бы не стал писать код таким образом, потому что следующий разработчик не сразу поймет цель кода. .

Решение Марка Рэнсома

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

яснее, но не намного кратче, чем

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

При написании кода, на который придется взглянуть кому-то другому или на который мне придется взглянуть еще долгое время, когда я забуду, о чем думал, когда писал его, слишком короткий или умный подход приносит больше вреда, чем пользы. Код должен быть читабельным. Столь сжато - это хорошо, но не настолько кратко, чтобы следующий программист не мог этого понять.


Честный вопрос: почему люди всегда предполагают, что следующим программистом будет идиот, не умеющий читать код? Я лично считаю эту идею оскорбительной. Если код должен быть написан так, чтобы быть очевидным для каждого программиста, то это означает, что мы как профессия работаем с наименьшим общим знаменателем, наименее квалифицированными среди нас. Если мы продолжим это делать, как они когда-нибудь смогут улучшить свои личные навыки? Я не вижу этого в других профессиях. Когда вы в последний раз видели, как композитор пишет простую партитуру, чтобы каждый музыкант мог ее сыграть, независимо от уровня мастерства?
Игорь Левицки

6
Проблема в том, что даже у программистов ограниченная умственная энергия, поэтому хотите ли вы потратить свою ограниченную умственную энергию на алгоритм и аспекты программы более высокого уровня или на выяснение того, что означает некоторая сложная строка кода, когда ее можно выразить более просто? ? Программировать сложно, поэтому не усложняйте себе задачу излишне, точно так же, как олимпийский бегун не стал бы участвовать в гонке с тяжелым рюкзаком только потому, что может. Как говорит Стив МакКонелл в Code Complete 2, читаемость - один из наиболее важных аспектов кода.
Пол Дж. Абернати,

2

Запрос на более компактный ИЛИ более питонический - я пробовал более компактный.

дано

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

Это на 2 символа меньше оригинала

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

тест с:

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

дополнительно, учитывая:

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

Это эквивалентно

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

Ну, это на два символа короче оригинала, но не тот, который OP дал сразу после, который, по его словам, он сейчас использует. В оригинале также есть много пробелов, которые по возможности опускаются. Также есть небольшой вопрос о функции, которую g()вы должны определить, чтобы это работало. Учитывая все это, я бы сказал, что он значительно больше.
TigerhawkT3,

@ TigerhawkT3, я интерпретировал это как запрос на более короткое выражение / строку. см. редактирование для дальнейшего улучшения .
вторая мировая война,

4
Очень плохие имена функций, подходящие только для игры в гольф.
0xc0de

@ 0xc0de - извините, я не играю. Подходящий вариант может быть довольно субъективным и зависеть от обстоятельств, но я полагаюсь на сообщество.
вторая мировая война,

Я не понимаю, насколько это компактнее, если в нем больше символов, чем в исходном коде.
Игорь Левицки

1

Я хочу представить то, что считаю наиболее питоническим ответом:

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

Общий случай, неоптимизированный:

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 

Что касается дзен Python, я думаю, что подчеркнутые утверждения более следуют, чем из другого ответа:

Дзен Питона, Тим Питерс

Красивое лучше уродливого.
Явное лучше, чем неявное.
Лучше простое, чем сложное.
Сложный лучше, чем сложный.
Плоский лучше, чем вложенный.
Лучше разреженное, чем плотное.
Читаемость имеет значение.
Особых случаев недостаточно, чтобы нарушать правила.
Хотя практичность лучше чистоты.
Ошибки никогда не должны проходить незаметно.
Если явно не отключен.
Перед лицом двусмысленности откажитесь от соблазна угадать.
Должен быть один - а желательно только один - очевидный способ сделать это.
Хотя сначала этот способ может быть не очевиден, если вы не голландец.
Лучше сейчас, чем никогда.
Хотя никогда не бывает лучше, чемпрямо сейчас.
Если реализацию трудно объяснить, это плохая идея.
Если реализацию легко объяснить, это может быть хорошей идеей.
Пространства имен - одна отличная идея - давайте сделаем их больше!


1

Как старая привычка моего программирования, я считаю, что размещение сложного выражения справа в предложении может сделать его более читаемым, например:

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

Плюс ():

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

А также я думаю, что использование многострочных строк также может дать больше смысла:

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

0

В общем,

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

если вам подходит управление входной переменной,

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

если вы хотите использовать бит-хаки, вы можете использовать "!", ">> 1" и "<< 1"

Я избегал деления, хотя он позволяет избежать двух умножений во избежание ошибок округления. Однако проверьте, нет ли переполнений


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.