123

I have the following code:

# initialize
a = []

# create the table (name, age, job)
a.append(["Nick", 30, "Doctor"])
a.append(["John",  8, "Student"])
a.append(["Paul", 22, "Car Dealer"])
a.append(["Mark", 66, "Retired"])    

# sort the table by age
import operator
a.sort(key=operator.itemgetter(1))    

# print the table
print(a)

It creates a 4x3 table and then it sorts it by age. My question is, what exactly key=operator.itemgetter(1) does? Does the operator.itemgetter function return the item's value? Why can't I just type something like key=a[x][1] there? Or can I? How could with operator print a certain value of the form like 3x2 which is 22?

  1. How does exactly Python sort the table? Can I reverse-sort it?

  2. How can I sort it based on two columns like first age, and then if age is the same b name?

  3. How could I do it without operator?

martineau
  • 119,623
  • 25
  • 170
  • 301
Nickl
  • 1,403
  • 3
  • 14
  • 18
  • Related to this post https://stackoverflow.com/questions/3121979/how-to-sort-list-tuple-of-lists-tuples – jdhao Dec 18 '18 at 06:45

6 Answers6

172

Looks like you're a little bit confused about all that stuff.

operator is a built-in module providing a set of convenient operators. In two words operator.itemgetter(n) constructs a callable that assumes an iterable object (e.g. list, tuple, set) as input, and fetches the n-th element out of it.

So, you can't use key=a[x][1] there, because python has no idea what x is. Instead, you could use a lambda function (elem is just a variable name, no magic there):

a.sort(key=lambda elem: elem[1])

Or just an ordinary function:

def get_second_elem(iterable):
    return iterable[1]

a.sort(key=get_second_elem)

So, here's an important note: in python functions are first-class citizens, so you can pass them to other functions as a parameter.

Other questions:

  1. Yes, you can reverse sort, just add reverse=True: a.sort(key=..., reverse=True)
  2. To sort by more than one column you can use itemgetter with multiple indices: operator.itemgetter(1,2), or with lambda: lambda elem: (elem[1], elem[2]). This way, iterables are constructed on the fly for each item in list, which are than compared against each other in lexicographic(?) order (first elements compared, if equal - second elements compared, etc)
  3. You can fetch value at [3,2] using a[2,1] (indices are zero-based). Using operator... It's possible, but not as clean as just indexing.

Refer to the documentation for details:

  1. operator.itemgetter explained
  2. Sorting list by custom key in Python
ctholho
  • 801
  • 11
  • 18
J0HN
  • 26,063
  • 5
  • 54
  • 85
  • 2
    How would you sort reversed by one column and not reversed by another column? – yayitswei Oct 07 '16 at 11:37
  • 1
    `operator.itemgetter(n)` doesn't seem to work with `set` or `dict` (maybe it will work with `dict` if `n` is a valid key instead of some `int` you come up with). – Vim Dec 01 '17 at 11:52
  • 2
    @Vim [According to docs](https://docs.python.org/2/library/operator.html#operator.itemgetter), for dict it should work with any hashable value. `itemgetter(n)` means it'll try to do dict.__getitem__ lookup, which (unless you're not actually using a dict, but a custom class extending dict and overriding __getitem__) is equivalent to `dict[n]` - and it will obviously fail if there are no `n` key in the dict. For dicts where you're not sure if a key exists you might want to stick with something like `lambda key: dict.get(key, None)` or similar. Example: https://ideone.com/PJk8vd – J0HN Dec 03 '17 at 06:41
  • 1
    @yayitswei if one column is a number, use `lambda row: (row.one_thing, -row.number_thing)`, supplying `reverse=True` if you want to reverse by `one_thing`. If you don't have an easy numeric option like that, you'd have to finagle more. – pydsigner Aug 27 '18 at 23:15
  • Is it possible to sort by two columns by only one of them reversed? – vw511 Nov 16 '18 at 00:51
  • 1
    @yahyitswei yes, please see the itemgetter sorting solution for varying reverse arguments for multiple columns here, you then need to arrange your sorting in multiple steps in a row: https://stackoverflow.com/questions/14466068/sort-a-list-of-tuples-by-second-value-reverse-true-and-then-by-key-reverse-fal – questionto42 May 18 '20 at 15:58
53

Answer for Python beginners

In simpler words:

  1. The key= parameter of sort requires a key function (to be applied to be objects to be sorted) rather than a single key value and
  2. that is just what operator.itemgetter(1) will give you: A function that grabs the first item from a list-like object.

(More precisely those are callables, not functions, but that is a difference that can often be ignored.)

Lutz Prechelt
  • 36,608
  • 11
  • 63
  • 88
  • 1
    Thank you - as a still-learning python programmer, THIS is what I was looking for to understand what was happening. It wasn't clear to me that ".sort(key = attrgetter("index"))" in my code was sending the FUNCTION attrgetter() with the argument "index" to the Sort() function; I thought it was sending a result as in C and other languages. – Locane Jun 02 '17 at 23:44
  • 7
    @Locane: No, that is not what's happening. `attrgetter` will construct an appropriate function for you and return it. It _is_ sending a "result". Only that result is a function. – Lutz Prechelt Jun 06 '17 at 07:22
21

You are asking a lot of questions that you could answer yourself by reading the documentation, so I'll give you a general advice: read it and experiment in the python shell. You'll see that itemgetter returns a callable:

>>> func = operator.itemgetter(1)
>>> func(a)
['Paul', 22, 'Car Dealer']
>>> func(a[0])
8

To do it in a different way, you can use lambda:

a.sort(key=lambda x: x[1])

And reverse it:

a.sort(key=operator.itemgetter(1), reverse=True)

Sort by more than one column:

a.sort(key=operator.itemgetter(1,2))

See the sorting How To.

Paulo Almeida
  • 7,803
  • 28
  • 36
1
#sorting first by age then profession,you can change it in function "fun".
a = []

def fun(v):
    return (v[1],v[2])

# create the table (name, age, job)
a.append(["Nick", 30, "Doctor"])
a.append(["John",  8, "Student"])
a.append(["Paul",  8,"Car Dealer"])
a.append(["Mark", 66, "Retired"])

a.sort(key=fun)


print a
0
a = []
a.append(["Nick", 30, "Doctor"])
a.append(["John",  8, "Student"])
a.append(["Paul",  8,"Car Dealer"])
a.append(["Mark", 66, "Retired"])
print a

[['Nick', 30, 'Doctor'], ['John', 8, 'Student'], ['Paul', 8, 'Car Dealer'], ['Mark', 66, 'Retired']]

def _cmp(a,b):     

    if a[1]<b[1]:
        return -1
    elif a[1]>b[1]:
        return 1
    else:
        return 0

sorted(a,cmp=_cmp)

[['John', 8, 'Student'], ['Paul', 8, 'Car Dealer'], ['Nick', 30, 'Doctor'], ['Mark', 66, 'Retired']]

def _key(list_ele):

    return list_ele[1]

sorted(a,key=_key)

[['John', 8, 'Student'], ['Paul', 8, 'Car Dealer'], ['Nick', 30, 'Doctor'], ['Mark', 66, 'Retired']]
>>> 
miken32
  • 42,008
  • 16
  • 111
  • 154
0

The easiest way to sort an array using a user-defined function is to use cmp_to_key from Functools. here is a sample code:

from functools import cmp_to_key

def mine(x,y):
    if(x[1]!=y[1]): return x[1]>y[1]
    else: return x[0]>y[0]
    
a = []
a.append(["Nick", 30, "Doctor"])
a.append(["John",  8, "Student"])
a.append(["Paul", 22, "Car Dealer"])
a.append(["Mark", 66, "Retired"])  

def mine(a,b):
    if a[1] > b[1]:
        return 1
    elif a[1] < b[1]:
        return -1
    else:
        if a[0] > b[0]:
            return 1
        else:
            return 0
                     
print(sorted(a,key = cmp_to_key(mine)))