RoyalSwish
RoyalSwish

Reputation: 1573

Python - sort method parameter takes same precedence

In my program I want to order all the contacts by their name, last name first, then by first name.

I've got the code which does that for me but it doesn't do it exactly as I want it to.

For example, if I have a list of names ordered as the current code would do, it would go like this:

Luke
Riyaan
Amanda Benson

As you can see, the code still takes None as a value to sort on, what I want is this:

Amanda Benson
Luke
Riyaan

So basically, if the last name returns None then I want the program to order the first name with the same precedence of an object that does have a last name.

Here's the code that I'm currently using to sort the names:

import operator

...

addressBook = AddressBook()
addressBook.contactsList
addressBook.contactsList.sort(key = operator.attrgetter("lastName", "firstName"))

Upvotes: 0

Views: 121

Answers (2)

jonrsharpe
jonrsharpe

Reputation: 122106

The best way to do this is to implement the rich comparison magic methods in your (I guess) Contact class:

class Contact(object):

    def __init__(self, first_name, last_name=""):
        self.first_name = first_name
        self.last_name = last_name

    def __repr__(self): # to make the demo clearer!
        if not self.last_name:
            return str(self.first_name)
        return "{0.first_name} {0.last_name}".format(self)

    def __eq__(self, other):
        return (self.first_name == other.first_name and 
                self.last_name == other.last_name)

    def __lt__(self, other):
        if self.last_name and other.last_name:
            if self.last_name == other.last_name:
                return self.first_name < other.first_name
            return self.last_name < other.last_name
        else:
            if other.last_name:
                return self.first_name < other.last_name
            return self.first_name < other.first_name

Now you can just use sorted and list.sort without arguments:

>>> contacts_list = [Contact("Luke"), Contact("Riyaan"), Contact("Amanda", "Benson")]
>>> sorted(contacts_list)
[Amanda Benson, Luke, Riyaan]

Upvotes: 0

Martijn Pieters
Martijn Pieters

Reputation: 1123850

You'll need a custom sort function then that returns your first name as the first value in a tuple, when the last name is missing:

def name_sort(contact):
    if contact.lastName:
        return contact.lastName, contact.firstName
    return contact.firstName, ''

addressBook.contactsList.sort(key=name_sort)

You can use a lambda and conditional expression to fit that into the sort() call:

addressBook.contactsList.sort(key=lambda c: (c.lastName, c.firstName) if c.lastName else (c.firstName, ''))

I produce a 2-value tuple here for both cases, but a one-element tuple for the no-last-name case should suffice too.

If this is the only sort order you are interested in, you may want to look in providing the rich comparison functions so you can sort your objects without a key; the objects themselves are then compared and provide the sort order.

You don't have to implement all rich-comparison methods to do so; all you need is one of them, plus __eq__ (equality testing), and the @functools.total_ordering() class decorator:

from functools import total_ordering

@total_ordering
class Contact(object):
    # ...

    def __eq__(self, other):
        if self.lastName != other.lastName:
            return False
        return self.firstName == other.firstName

    def __lt__(self, other):
        if not self.lastName:
            if not other.lastName:
                return self.firstName < other.firstName
            return self.firstName < other.lastName
        return (self.lastName, self.firstName) < (other.lastName, other.firstName)

Upvotes: 3

Related Questions