1

Say, I have two lists:

a = [1,2,3] and b = [2,3,1]

If I do a a == b it returns False,

If I check sorted(a) == sorted(b), it returns True.

Now, I have two objects:

obj1 = {'a': 1, 'b': 2, 'c': [1, 2]} and obj2 = {'b': 2, 'a': 1, 'c': [1, 2]}

obj1 == obj2 is True, irrespective of the order of keys.

But if obj2 = {'b': 2, 'a': 1, 'c': [2, 1]}

how do I test the equality? Obviously, obj1 == obj2 returns False. sorted(obj1) will have ['a', 'b', 'c'], so sorted(obj1) == sorted(obj2) is kind of waste check.

I should have probably overridden the equality method for the object, or use some library. Is, is there any way to write idiomatic python code for deep equality?

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
inquisitive
  • 3,738
  • 6
  • 30
  • 56
  • 1
    so what is your definition of equality for this? – Ma0 Aug 14 '18 at 11:53
  • I need `obj1 == obj2` return True, if `obj1 = {'a': 1, 'b': 2, 'c': [1, 2]}` and `obj2 = {'b': 2, 'a': 1, 'c': [2, 1]}` – inquisitive Aug 14 '18 at 11:59
  • `sorted(['a', 'b', 'c'])` isn't as wasteful as you might think since Python's timsort uses a clever hybrid sorting algorithm that performs well on already sorted (or almost sorted) data – Chris_Rands Aug 14 '18 at 12:08

2 Answers2

6

sort each element in the dict if it is of type list and then compare

>>> def sorted_element_dict(d):
...     return {k:sorted(v) if isinstance(v, list) else v for k,v in d.items()}
...
>>> sorted_element_dict(obj1) == sorted_element_dict(obj2)
True
Sunitha
  • 11,777
  • 2
  • 20
  • 23
1

If you are only interested in an equality check, consider using all. You could just apply a custom comparator to all the dictionary values:

def check_equal(d1, d2):
    if d1.keys() != d2.keys():
        return False
    return all(sorted(v) == sorted(d2[k]) if isinstance(v, list) else v == d2[k] for k, v in d1.items())

A more elegant way might be to factor out the comparator into a separate function and check against something more general, like collections.abc.Sequence:

def check_equal(d1, d2):
    if d1.keys() != d2.keys():
        return False
    def cmp(v1, v2):
        if isinstance(v1, Sequence) and isinstance(v2, sequence):
            return sorted(v1) == sorted(v2)
        return v1 == v2
    return all(cmp(v, d2[k]) for k, v in d1.items())

This has the advantage of not storing all the intermediate sorted products. If, on the other hand, you need to do the comparison frequently, it may be better to transform your dictionaries before using regular ==:

def normalize(d):
    for k, v in d.items():
        if isinstance(v, Sequence):
            d[k] = sorted(v)

Notice that I used a loop instead of a comprehension here. That way, your dictionary is transformed in-place, rather than allocating a whole new hash table.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264