132

I was refactoring some old code of mine and came across of this:

alist.sort(cmp_items)

def cmp_items(a, b):
    if a.foo > b.foo:
        return 1
    elif a.foo == b.foo:
        return 0
    else:
        return -1

The code works (and I wrote it some 3 years ago!) but I cannot find this thing documented anywhere in the Python docs and everybody uses sorted() to implement custom sorting. Can someone explain why this works?

3
  • sorted() and sort() offer custom sorting in much the same way, modulo the difference in calling convention. Commented Aug 7, 2012 at 17:44
  • 2
    Indeed, what happens is that using a key parameter is preferred over passing a cmp function. (The later is not even implemented in Python 3) Commented Aug 8, 2012 at 2:50
  • It's kind of ambiguous, depends on what the items in the list were; your code requires that they have an attribute foo, otherwise it blows up. Better to define a custom __lt__() method for your class, then sorted() and list.sort() will work out-of-the-box. (Btw, objects no longer need to define __cmp__(), just __lt__() . See this Commented Sep 10, 2018 at 3:00

6 Answers 6

154

As a side note, here is a better alternative to implement the same sorting:

alist.sort(key=lambda x: x.foo)

Or alternatively:

import operator
alist.sort(key=operator.attrgetter('foo'))

Check out the Sorting How To, it is very useful.

Sign up to request clarification or add additional context in comments.

1 Comment

TIL about operator, very useful.
72

It's documented here.

The sort() method takes optional arguments for controlling the comparisons.

cmp specifies a custom comparison function of two arguments (list items) which should return a negative, zero or positive number depending on whether the first argument is considered smaller than, equal to, or larger than the second argument: cmp=lambda x,y: cmp(x.lower(), y.lower()). The default value is None.

7 Comments

Thanks miles82 I was checking here and couldn't see it in the method signature docs.python.org/tutorial/datastructures.html
I don't see the same text on the page you linked to. Did the documentation change. Besides, when I try to use cmp, I get TypeError: 'cmp' is an invalid keyword argument for this function. What is going on here?
@HelloGoodbye sort() doesn't have a cmp argument in Python 3. This is an old answer when the docs link was for Python 2. You can find the old docs here or read more about it here. If you're using Python 3, use the key argument instead.
And what if you actually want to provide a comparison function? I want to treat numbers in a string (of any length, picked out greedily) as symbols, equivalently to how individual characters are otherwise treated. I know how to achieve that trivially if I may provide a comparison function, but not if I must provide a key function. Why was this changed?
I guess it can still be achieved if each number contained in the string is encoded using an encoding that orders the numbers lexicographically, such as Levenshtein coding. But I consider this more as a workaround to the fact that sort doesn’t take a comparison function as argument in Python 3, and not as something that I actually would like to do.
|
34

Just like this example. You want sort this list.

[('c', 2), ('b', 2), ('a', 3)]

output:

[('a', 3), ('b', 2), ('c', 2)]

you should sort the tuples by the second item, then the first:

def letter_cmp(a, b):
    if a[1] > b[1]:
        return -1
    elif a[1] == b[1]:
        if a[0] > b[0]:
            return 1
        else:
            return -1
    else:
        return 1

Then convert it to a key function:

from functools import cmp_to_key
letter_cmp_key = cmp_to_key(letter_cmp))

Now you can use your custom sort order:

[('c', 2), ('b', 2), ('a', 3)].sort(key=letter_cmp_key)

2 Comments

How does it know what list to sort?
@CameronMonks yourList.sort(letter_cmp)
21

This does not work in Python 3.

You can use functools cmp_to_key to have old-style comparison functions work though.

from functools import cmp_to_key

def cmp_items(a, b):
    if a.foo > b.foo:
        return 1
    elif a.foo == b.foo:
        return 0
    else:
        return -1

cmp_items_py3 = cmp_to_key(cmp_items)

alist.sort(key = cmp_items_py3)

1 Comment

cmp_to_key also works when used as a no-argument decorator (put @cmp_to_key on the line before your def for the comparing function) so you don't need to call cmp_to_key and assign the result yourself
16

I know many have already posted some good answers. However I want to suggest one nice and easy method without importing any library.

l = [(2, 3), (3, 4), (2, 4)]
l.sort(key = lambda x: (-x[0], -x[1]) )
print(l)
l.sort(key = lambda x: (x[0], -x[1]) )
print(l)

Output will be

[(3, 4), (2, 4), (2, 3)]
[(2, 4), (2, 3), (3, 4)]

The output will be sorted based on the order of the parameters we provided in the tuple format

1 Comment

For sorting, first, it will check the first item in the tuple and it will try to sort according to the sign you have given ('-' indicates reverse sorting). If it fails to sort using the first item, then it considers the second item in the tuple..and so on...
6

Even better:

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]

sorted(student_tuples, key=lambda student: student[2])   # sort by age
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Taken from: https://docs.python.org/3/howto/sorting.html

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.