Home » Python » Compact way of writing (a + b == c or a + c == b or b + c == a)

Compact way of writing (a + b == c or a + c == b or b + c == a)

Posted by: admin November 1, 2017 Leave a comment

Questions:

Is there a more compact or pythonic way to write the boolean expression

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

I came up with

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

but that is a little strange.

Answers:

If we look at the Zen of Python, emphasis mine:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let’s do more of those!

The most Pythonic solution is the one that is clearest, simplest, and easiest to explain:

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

Even better, you don’t even need to know Python to understand this code! It’s that easy. This is, without reservation, the best solution. Anything else is intellectual masturbation.

Furthermore, this is likely the best performing solution as well, as it is the only one out of all the proposals that short circuits. If a + b == c, only a single addition and comparison is done.

Questions:
Answers:

Solving the three equalities for a:

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

Questions:
Answers:

Python has an any function that does an or on all the elements of a sequence. Here I’ve converted your statement into a 3-element tuple.

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

Note that or is short circuiting, so if calculating the individual conditions is expensive it might be better to keep your original construct.

Questions:
Answers:

If you know you’re only dealing with positive numbers, this will work, and is pretty clean:

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

As I said, this only works for positive numbers; but if you know they’re going to be positive, this is a very readable solution IMO, even directly in the code as opposed to in a function.

You could do this, which might do a bit of repeated computation; but you didn’t specify performance as your goal:

from itertools import permutations

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

Or without permutations() and the possibility of repeated computations:

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

I would probably put this, or any other solution, into a function. Then you can just cleanly call the function in your code.

Personally, unless I needed more flexibility from the code, I would just use the first method in your question. It’s simple and efficient. I still might put it into a function:

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

That’s pretty Pythonic, and it’s quite possibly the most efficient way to do it (the extra function call aside); although you shouldn’t worry too much about performance anyway, unless it’s actually causing an issue.

Questions:
Answers:

If you will only be using three variables then your initial method:

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

Is already very pythonic.

If you plan on using more variables then your method of reasoning with:

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

Is very smart but lets think about why. Why does this work?

Well through some simple arithmetic we see that:

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

And this will have to hold true for either a,b, or c, meaning that yes it will equal 2*a, 2*b, or 2*c. This will be true for any number of variables.

So a good way to write this quickly would be to simply have a list of your variables and check their sum against a list of the doubled values.

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

This way, to add more variables into the equation all you have to do is edit your values list by ‘n’ new variables, not write ‘n’ equations

Questions:
Answers:

The following code can be used to iteratively compare each element with the sum of the others, which is computed from sum of the whole list, excluding that element.

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

Questions:
Answers:

Don’t try and simplify it. Instead, name what you’re doing with a function:

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

Replace the condition with something “clever” might make it shorter, but it won’t make it more readable. Leaving it how it is isn’t very readable either however, because it’s tricky to know why you’re checking those three conditions at a glance. This makes it absolutely crystal clear what you’re checking for.

Regarding performance, this approach does add the overhead of a function call, but never sacrifice readability for performance unless you’ve found a bottleneck you absolutely must fix. And always measure, as some clever implementations are capable of optimizing away and inlining some function calls in some circumstances.

Questions:
Answers:

Python 3:

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

It scales to any number of variables:

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

However, in general I agree that unless you have more than three variables, the original version is more readable.

Questions:
Answers:
(a+b-c)*(a+c-b)*(b+c-a) == 0

If the sum of any two terms is equal to the third term, then one of the factors will be zero, making the entire product zero.

Questions:
Answers:

How about just:

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

Note that this won’t work if variables are unsigned.

From the viewpoint of code optimization (at least on x86 platform) this seems to be the most efficient solution.

Modern compilers will inline both abs() function calls and avoid sign testing and subsequent conditional branch by using a clever sequence of CDQ, XOR, and SUB instructions. The above high-level code will thus be represented with only low-latency, high-throughput ALU instructions and just two conditionals.

Questions:
Answers:

The solution provided by Alex Varga “a in (b+c, b-c, c-b)” is compact and mathematically beautiful, but I wouldn’t actually write code that way because the next developer coming along would not immediately understand the purpose of the code.

Mark Ransom’s solution of

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

is more clear but not much more succinct than

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

When writing code that someone else will have to look at, or that I will have to look at a long time later when I have forgotten what I was thinking when I wrote it, being too short or clever tends to do more harm than good. Code should be readable. So succinct is good, but not so succinct that the next programmer can’t understand it.

Questions:
Answers:

Request is for more compact OR more pythonic – I tried my hand at more compact.

given

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

This is 2 characters less than the original

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

test with:

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

additionally, given:

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

This is equivalent

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

Questions:
Answers:

I want to present what I see as the most pythonic answer:

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

The general case, non-optimized:

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 terms of the Zen of Python I think the emphasized statements are more followed than from other answer:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let’s do more of those!

Questions:
Answers:

As an old habit of my programming, I think placing complex expression at right in a clause can make it more readable like this:

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

Plus ():

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

And also I think using multi-lines can also make more senses like this:

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

Questions:
Answers:

In a generic way,

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

if, manipulating an input variable is OK for you,

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

if you want to exploit using bit hacks, you can use “!”, “>> 1” and “<< 1”

I avoided division though it enables use to avoid two multiplications to avoid round off errors. However, check for overflows

Questions:
Answers:
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