In Python how would I compare with multiple values using custom keys?
python
Solution
if you want to order with list.sort() for example you can pass arguments to it:
Your code:
...
def __lt__(self, other):
return (self.c, self.a, self.b) < (other.c, other.a, other.b)
...
list.sort()
equivalent:
list.sort(key = lambda self: (self.c, self.a, self.b))
also equivalent:
list.sort(cmp = lambda self, other: \
(self.c, self.a, self.b) < (other.c, other.a, other.b))
so if you want to sort your answers in different ways i would propose:
class Foo(object):
@staticmethod
def cmp_absoluteOrder(self, other):
return (self.c, self.a, self.b) < (other.c, other.a, other.b)
@staticmethod
def cmp_otherOrder(self, other):
return ...
@staticmethod
def cmp_combinedSort(cmpA, cmpB, cmpC):
return lambda self, other: (0, 0, 0) < (cmpA(self.c, other.c), cmpA(self.a, other.a), cmpA(self.b, other.b), )
def __hash__(self):
return hash(self.c) ^ hashA(self.a) ^ hash(self.b)
...
list.sort(cmp = Foo.cmp_absoluteSorting)
list.sort(cmp = Foo.cmp_combinedSort(cmp, (lambda a1, a2: ...), cmp))
hashA = hash # or replace it if important # but the same a will retunrn the same hash
or something like this
Problem
When implementing the `__eq__` and `__lt__` methods of a class, it is common practice to use tuples to group the values you wish to compare, like so: ``` @total_ordering class Foo(object): def __init__(self, a, b, c): self.a = a self.b = b self.c = c def __hash__(self): return hash((self.c, self.a, self.b)) def __eq__(self, other): return (self.c, self.a, self.b) == (other.c, other.a, other.b) def __lt__(self, other): return (self.c, self.a, self.b) < (other.c, other.a, other.b) ``` However this uses the natural ordering of each key. What if I want to change, for example, how `a` sorts? This is what I've come up with so far, and while it seems to work OK, I was wondering if there is a better way to go about it: ``` @total_ordering class Foo(object): def __init__(self, a, b, c): self.a = MyA(a) # Note self.b = b self.c = c def __hash__(self): return hash((self.c, self.a, self.b)) def __eq__(self, other): return (self.c, self.a, self.b) == (other.c, other.a, other.b) def __lt__(self, other): return (self.c, self.a, self.b) < (other.c, other.a, other.b) class MyA(A): def __hash__(self): # ... def __eq__(self, other): # ... def __lt__(self, other): # ... ``` Subclassing `A` lets me define my custom ordering, and allows `MyA` to behave like a regular `A` in every other way which is nice, but it seems wasteful / needlessly verbose especially if I have to do this for multiple fields. Edit: As per user1320237's answer below, this is what I've come up with: ``` @total_ordering class Foo(object): def __init__(self, a, b, c): self.a = a self.b = b self.c = c def __hash__(self): return hash((self.c, self.a, self.b)) def __eq__(self, other): return (0, 0, 0) == (cmp(self.c, other.c), cmpA(self.a, other.a), cmp(self.b, other.b)) def __lt__(self, other): return (0, 0, 0) > (cmp(self.c, other.c), cmpA(self.a, other.a), cmp(self.b, other.b)) def cmpA(a1, a2): # ... ``` (Note the `>` in `__lt__` since `cmp(x, y)` returns `-1` if `x < y` and `__lt__` should return `True`)