Компактний спосіб написання (a + b == c або a + c == b або b + c == a)


136

Чи є більш компактний чи пітонічний спосіб написати булевий вираз

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

Я придумав

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

але це трохи дивно.


16
Більш компактний? Можливо. Більш пітонічний? Навряд чи.
чепнер

126
Зробіть своє майбутнє собі користь і збережіть його в первісному вигляді: Це єдине, що негайно підкаже мету цього. Не змінюйте цього. "Просте - це краще, ніж складне". "Читання читається". "Якщо реалізацію важко пояснити, це погана ідея."
ткнути

21
Pythonic == нечитабельний?
nhaarman

3
@wwii Вони не є взаємовиключними. Дивіться a = 0, b = 0, c = 0;)
Хонза Брабек

1
Що сказав @phresnel. Замість того, щоб намагатися «спростити» вираз, оберніть його у функції з описовою назвою.
Головоногі

Відповіді:


206

Якщо ми подивимось на дзен Python, моє наголос:

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

Красиве краще, ніж потворне.
Явне краще, ніж неявне.
Простий - краще, ніж складний.
Комплекс краще, ніж складний.
Квартира краще, ніж вкладена.
Розріджений краще, ніж щільний.
Читання рахується.
Особливі випадки недостатньо спеціальні для порушення правил.
Хоча практичність перемагає чистоту.
Помилки ніколи не повинні проходити мовчки.
Якщо явно мовчати.
В умовах неоднозначності відмовтеся від спокуси здогадатися.
Повинно бути один - і бажано лише один - очевидний спосіб це зробити.
Хоча спочатку це може бути не очевидним, якщо ви не голландці.
Зараз краще, ніж ніколи.
Хоча ніколи не буває краще, ніж кращепрямо зараз.
Якщо реалізацію важко пояснити, це погана ідея.
Якщо реалізацію легко пояснити, це може бути хорошою ідеєю.
Простори імен - це чудова ідея - давайте зробимо більше таких!

Найбільш пітонічне рішення - це найяскравіше, найпростіше та найпростіше пояснити:

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

Ще краще, вам навіть не потрібно знати Python, щоб зрозуміти цей код! Це що легко. Це, без застереження, найкраще рішення. Все інше - інтелектуальна мастурбація.

Крім того, це, ймовірно, найкраще рішення, оскільки це єдине з усіх пропозицій, яке має коротке замикання. Якщо a + b == c, робиться лише одне доповнення та порівняння.


11
Ще краще, введіть у дужки трохи, щоб зробити наміри кристально зрозумілими.
Брайан Оуклі

3
Намір вже кристально чистий без дужок. Круглі дужки ускладнюють читання - чому автор використовує круглі дужки, коли пріоритет вже охоплює це?
Майлз Рут

1
Ще одна примітка про спробу бути занадто розумним: ви можете вводити непередбачені помилки через відсутні умови, які ви не враховували. Іншими словами, ви можете вважати, що ваше нове компактне рішення рівнозначне, але це не у всіх випадках. Якщо немає вагомих причин кодувати інше (продуктивність, обмеження пам’яті тощо), чіткість є коротким.
Роб Крейг

Це залежить від того, для чого формула. Подивіться на те, що "Явне ясніше краще, ніж неявне", можливо, підхід "сортування перших" чіткіше виражає те, що робить програма чи хтось із інших. Я не думаю, що ми можемо судити з цього питання.
Томас Ейл

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всіх елементів послідовності. Тут я перетворив ваше твердження в 3-елементний кортеж.

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

Зверніть увагу, що orце коротке замикання, тому, якщо обчислення індивідуальних умов коштує дорого, може бути краще зберегти оригінальну конструкцію.


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

42
@ TigerhawkT3 Не в цьому випадку; три вирази будуть оцінені до того, як кортеж існує, а кортеж буде існувати anyще до того, як навіть запуститись.
ткнути

13
А, бачу. Я думаю, це лише тоді, коли там є генератор чи подібний ледачий ітератор.
TigerhawkT3

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

Це має перевагу в тому, що легко розділити на кілька рядків (подвійний відступ аргументів any, однозначний відступ ):у ifвиписці), що допомагає багато для читабельності, якщо задіяна математика
Izkata

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

Це досить піфонічно, і це, можливо, найефективніший спосіб це зробити (додаткова функція викликає сторону); хоча ви не повинні занадто сильно турбуватися про продуктивність, якщо це насправді не викликає проблеми.


особливо якщо ми можемо припустити, що a, b, c - всі негативні.
cphlewis

Я вважаю, що фраза "не завжди працює" трохи заплутаною. Перше рішення працює лише в тому випадку, якщо ви точно знаєте, що ваші номери негативні. Наприклад, з (a, b, c) = (-3, -2, -1) у вас є + b! = C, але b + c = a. Аналогічні випадки з (-1, 1, 2) і (-2, -1, 1).
usernumber

@usernumber, знаєте, я це помічав раніше; не впевнений, чому я цього не виправив.
Сифаза

Ваше найкраще рішення не працює для великого класу входів, тоді як пропозиція ОП працює для всіх вхідних даних. Як "не працює" більше пітонічно, ніж "працює"?
Баррі

3
Ой, хапайся. " Якщо ви знаєте, що маєте справу лише з позитивними цифрами , це буде спрацьовувати і досить чисто". Усі інші працюють для будь-яких чисел, але якщо ви знаєте, що ви маєте справу лише з позитивними числами , верхній дуже читабельний / піфонічний ІМО.
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
Ерік Ренуф

Дякую, що це правда, мені потрібно було б використовувати abs. Внесіть це коригування зараз.
ThatGuyRussell

2
Після пересипання його з півдесятка abs()дзвінків він стає пітонічним, ніж фрагмент ОП (я б фактично назвав його значно менш читабельним).
TigerhawkT3

Це дуже правда, я зараз це підкоригую
ThatGuyRussell,

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

Пітон 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 Чи жодна реалізація не поверне неправильних результатів через округлення з плаваючою комою? Навіть a + b == c
osundblad

@osundblad: Якщо a, b і c є ints, тоді (a + b + c) / 2 робить округлення (і може повернути неправильні результати), але a + b == c є точним.
пт

3
ділення на 2 просто зменшує показник на одиницю, тому воно буде точним для будь-якого цілого числа, меншого ніж 2 ^ 53 (частка дробу з поплавком у пітоні), а для більших цілих чисел можна використовувати десятковий . Наприклад, для перевірки цілих чисел, менших від 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 - Чому ти вважаєш, що це передчасно? Код слід писати з оптимізацією на увазі, а не оптимізувати як задум. Одного разу якийсь стажер скопіює цей фрагмент коду та вставить його в якийсь критичний цикл продуктивності на вбудованій платформі, який потім буде працювати на мільйонах пристроїв, не обов'язково повільних для того, що він робить, але, можливо, витрачає більше енергії акумулятора. Написання такого коду просто заохочує погану практику кодування. На мою думку, те, що ОП повинен було запитати, чи є спосіб оптимізувати цей логічний вираз.
Ігор Левицький

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

Ну, це два символи коротші за оригінал, але не той, який ОП дав одразу після цього, який він сказав, що він зараз використовує. Оригінал також включає багато пробілів, які це упускає, коли це можливо. Існує також невелика матерія функції, g()яку ви повинні визначити для цього. Враховуючи все це, я б сказав, що він значно більший.
TigerhawkT3

@ TigerhawkT3, я трактував це як запит на коротший вираз / рядок. див. редагування для подальшого вдосконалення .
wwii

4
Дуже погані назви функцій, підходять лише для кодового гольфу.
0xc0de

@ 0xc0de - вибачте, що не граю. Підходящий може бути досить суб’єктивним і залежним від обставин - але я відкладу громаду.
wwii

Я не бачу, наскільки це компактніше, якщо він містить більше символів, ніж вихідний код.
Ігор Левицький

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.