0

I have a Pair class (it has a key and a value) and I'm trying to make a program which creates a bunch of Pair objects, adds them to a list, and performs stable quicksort on them. However, I can't seem to figure how to make the objects comparable so that the program automatically compares the keys of two objects when they have the same value. It's so easy to do this in Java but I just don't get how I'm supposed to do the equivalent in Python.

Thank you in advance!

class Pair(Generic[K,V]):
    def __init__(self, key: K, val: V):
        self.key = key
        self.value = val
  • Here: https://stackoverflow.com/questions/8276983/why-cant-i-use-the-method-cmp-in-python-3-as-for-python-2 – Swetank Poddar Jun 20 '20 at 08:41
  • Does this answer your question? [Comparable classes in Python 3](https://stackoverflow.com/questions/6907323/comparable-classes-in-python-3) – Georgy Aug 04 '20 at 16:26

3 Answers3

3

How about the following (Pair sortable by key but you can easily define any other way to sort them):

class Pair:
    def __init__(self, key, val):
        self.key = key
        self.value = val

    def __eq__(self, other: "Pair"):
        return self.key == other.key

    def __lt__(self, other: "Pair"):
        return self.key < other.key

    def __le__(self, other: "Pair"):
        return self.key <= other.key

    def __gt__(self, other: "Pair"):
        return self.key > other.key

    def __ge__(self, other: "Pair"):
        return self.key >= other.key

    def __str__(self):
        return f"{self.key}={self.value}"

    def __repr__(self):
        return f"{self.key}={self.value} ({id(self)})"


test = [
    Pair("a2", "1"), Pair("a1", "2"), Pair("b1", "3"),Pair("br", "4")
]

print(sorted(test))

Output:

$ python3 ~/tmp/test.py
[a1=2 (4352627216), a2=1 (4352622288), b1=3 (4352627344), br=4 (4352627408)]

To sort by value and then by key if the values are equal you would dp something like:

    def __lt__(self, other: "Pair"):
        if self.value != other.value:
            return self.value < other.value

        return self.key < other.key

example input/output with the above lt:

# Input
test = [
    Pair("a2", "1"), Pair("a1", "2"), Pair("b1", "1"),Pair("br", "4")
]

# Output
[a2=1 (4466773648), b1=1 (4466778768), a1=2 (4466778640), br=4 (4466778832)]

Additionally, if you plan to use the pairs as dictionary keys or in sets you can implement __hash__. For more operators see here

urban
  • 5,392
  • 3
  • 19
  • 45
  • Thank you so much! This was really thorough and really helpful – user11610667 Jun 20 '20 at 22:28
  • Also, why do you make the distinction of comparing keys if values are the same in just the `__lt__` method and nowhere else? – user11610667 Jun 21 '20 at 01:54
  • @user11610667 The idea was to show you the most important methods you can override. I implemented only `__lt__` just as example but it should be straight to do this in the rest ( this also the one used for sorting) – urban Jun 21 '20 at 09:48
  • Ah I see. And could you please tell me what the last two methods are (str & repr)? I don't think I've seen them before – user11610667 Jun 23 '20 at 03:55
  • 1
    @user11610667 They are both used to convert the object to string. Most often this is needed for printing. However, `str`'s goal is to make the object human readable while `repr` is more focused on producing a unique representation of the object (and thus its python `id()` and class might also appear). Both extremely useful in logging and debugging - read more [here](https://stackoverflow.com/a/2626364/3727050) – urban Jun 23 '20 at 08:43
0

Python uses so-called “dunder methods” (double-underscore methods) for this. You’re already using the init dunder method. Define __eq__.

Edit: see excellent dbader article

Frank
  • 459
  • 2
  • 9
0

We can also use @functools.total_ordering class decorator to reduce the number of class methods. As explained in the linked Python documentation page:

This simplifies the effort involved in specifying all of the possible rich comparison operations. The class must define one of lt(), le(), gt(), or ge(). In addition, the class should supply an eq() method.

With this, the implementation will look like:

from functools import total_ordering


@total_ordering
class Pair:
    def __init__(self, key, val):
        self.key = key
        self.value = val

    def __eq__(self, other):
        return self.key == other.key

    def __lt__(self, other):
        return self.key < other.key

    def __repr__(self):
        return self.key.__repr__() + ': ' + self.value.__repr__()


pairs = [Pair(1, 'one'), Pair(2, 'two'), Pair(3, 'three')]
print(sorted(pairs))
Jatin Sanghvi
  • 1,928
  • 1
  • 22
  • 33