Is there a more elegant way to express ((x == a and y == b) or (x == b and y == a))?

PythonBoolean Logic

Python Problem Overview


I'm trying to evaluate ((x == a and y == b) or (x == b and y == a)) in Python, but it seems a bit verbose. Is there a more elegant way?

Python Solutions


Solution 1 - Python

If the elements are hashable, you could use sets:

{a, b} == {y, x}

Solution 2 - Python

I think the best you could get is to package them into tuples:

if (a, b) == (x, y) or (a, b) == (y, x)

Or, maybe wrap that in a set lookup

if (a, b) in {(x, y), (y, x)}

Just since it was mentioned by a couple comments, I did some timings, and tuples and sets appear to perform identically here when the lookup fails:

from timeit import timeit

x = 1
y = 2
a = 3
b = 4

>>> timeit(lambda: (a, b) in {(x, y), (y, x)}, number=int(5e7))
32.8357742

>>> timeit(lambda: (a, b) in ((x, y), (y, x)), number=int(5e7))
31.6169182

Although tuples are actually faster when the lookup succeeds:

x = 1
y = 2
a = 1
b = 2

>>> timeit(lambda: (a, b) in {(x, y), (y, x)}, number=int(5e7))
35.6219458

>>> timeit(lambda: (a, b) in ((x, y), (y, x)), number=int(5e7))
27.753138700000008

I chose to use a set because I'm doing a membership lookup, and conceptually a set is a better fit for that use-case than a tuple. If you measured a significant different between the two structures in a particular use case, go with the faster one. I don't think performance is a factor here though.

Solution 3 - Python

Tuples make it slightly more readable:

(x, y) == (a, b) or (x, y) == (b, a)

This gives a clue: we're checking whether the sequence x, y is equal to the sequence a, b but ignoring ordering. That's just set equality!

{x, y} == {a, b}

Solution 4 - Python

The most elegant way, in my opinion, would be

(x, y) in ((a, b), (b, a))

This is a better way than using sets, i.e. {a, b} == {y, x}, as indicated in other answers because we don't need to think if the variables are hashable.

Solution 5 - Python

If the items aren't hashable, but support ordering comparisons, you could try:

sorted((x, y)) == sorted((a, b))

Solution 6 - Python

If these are numbers, you can use (x+y)==(a+b) and (x*y)==(a*b).

If these are comparable items, you can use min(x,y)==min(a,b) and max(x,y)==max(a,b).

But ((x == a and y == b) or (x == b and y == a)) is clear, safe, and more general.

Solution 7 - Python

As a generalization to more than two variables we can use itertools.permutations. That is instead of

(x == a and y == b and z == c) or (x == a and y == c and z == b) or ...

we can write

(x, y, z) in itertools.permutations([a, b, c])

And of course the two variable version:

(x, y) in itertools.permutations([a, b])

Solution 8 - Python

You can use tuples to represent your data and then check for set inclusion, like:

def test_fun(x, y):
    test_set = {(a, b), (b, a)}

    return (x, y) in test_set

Solution 9 - Python

You already got the most readable solution. There are other ways to express this, perhaps with less characters, but they are less straight-forward to read.

Depending on what the values actually represent your best bet is to wrap the check in a function with a speaking name. Alternatively or in addition, you can model the objects x,y and a,b each in dedicated higher class objects that you then can compare with the logic of the comparison in a class equality check method or a dedicated custom function.

Solution 10 - Python

It seems the OP was only concerned with the case of two variables, but since StackOverflow is also for those who search for the same question later, I'll try to tackle the generic case here in some detail; One previous answer already contains a generic answer using itertools.permutations(), but that method leads to O(N*N!) comparisons, since there are N! permutations with N items each. (This was the main motivation for this answer)

First, let's summarize how some of the methods in previous answers apply to the generic case, as motivation for the method presented here. I'll be using A to refer to (x, y) and B to refer to (a, b), which can be tuples of arbitrary (but equal) length.

set(A) == set(B) is fast, but only works if the values are hashable and you can guarantee that one of the tuples doesn't contain any duplicate values. (Eg. {1, 1, 2} == {1, 2, 2}, as pointed out by @user2357112 under @Daniel Mesejo's answer)

The previous method can be extended to work with duplicate values by using dictionaries with counts, instead of sets: (This still has the limitation that all values need to be hashable, so e.g. mutable values like list won't work)

def counts(items):
    d = {}
    for item in items:
        d[item] = d.get(item, 0) + 1
    return d

counts(A) == counts(B)

sorted(A) == sorted(B) doesn't require hashable values, but is slightly slower, and requires orderable values instead. (So e.g. complex won't work)

A in itertools.permutations(B) doesn't require hashable or orderable values, but like already mentioned, it has O(N*N!) complexity, so even with just 11 items, it can take over a second to finish.

So, is there a way to be as general, but do it considerably faster? Why yes, by "manually" checking that there's the same amount of each item: (The complexity of this one is O(N^2), so this isn't good for large inputs either; On my machine, 10k items can take over a second - but with smaller inputs, like 10 items, this is just as fast as the others)

def unordered_eq(A, B):
    for a in A:
        if A.count(a) != B.count(a):
            return False
    return True

To get the best performance, one might want to try the dict-based method first, fall back to the sorted-based method if that fails due to unhashable values, and finally fall back to the count-based method if that too fails due to unorderable values.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLetEpsilonBeLessThanZeroView Question on Stackoverflow
Solution 1 - PythonDani MesejoView Answer on Stackoverflow
Solution 2 - PythonCarcigenicateView Answer on Stackoverflow
Solution 3 - PythonThomasView Answer on Stackoverflow
Solution 4 - PythonWagner MacedoView Answer on Stackoverflow
Solution 5 - PythonjasonharperView Answer on Stackoverflow
Solution 6 - PythonlhfView Answer on Stackoverflow
Solution 7 - Pythona_guestView Answer on Stackoverflow
Solution 8 - PythonNils MüllerView Answer on Stackoverflow
Solution 9 - PythonFrank HopkinsView Answer on Stackoverflow
Solution 10 - PythonAleksi TorhamoView Answer on Stackoverflow