Python Forum

Full Version: list sorting question
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I was checking up on sorting 2D lists, and found this (works fine):
#sort on first element
def takeFirst(elem):
    return elem[0]
# list
lst = [(2, 2), (3, 4), (4, 1), (1, 3)]
# sort 
lst.sort(key=takeFirst)
# print list
print('Sorted list:', lst)
At first i thought that takeFirst, takeSecond, were some kind of "reserved" words,
but when i replaced takeFirst with "chicken", it also worked !

Question: by what mechanism does takeFirst() know what "elem" is?

thx,
Paul
It appears that elem is a list datatype because it is iterable. The fact that you are identifying elements by position suggests to Python iterability, thus it attempts to reference it as such. Since takeFirst(...) is a function, the assumption is that elem is defined somewhere (by assignment) upon its first use as a variable before being passed.
lists are referenced by index starting at 0 with syntax like list[index_number] or list[0].
elem[0] is equal to lst[0] or (2, 2).
(Jun-17-2020, 09:22 AM)DPaul Wrote: [ -> ]Question: by what mechanism does takeFirst() know what "elem" is?

For each element in the list, the key-function is called with the element.

If your list is [(0,"A"), (1, "B"), (2, "C")], your key-function is called with:

key_func((0, "A"))
key_func((1, "B"))
key_func((2, "C"))

The key function could return a single comparable object or sequences (list, tuple, str).
If the key function return iterables, the different values must be comparable.
E.G. field1 of all elements must be comparable, field2 of all elements must be comparable and so far.

Calling the sorted function or inline sort method of list without a key-function, sorts iterables by first field, then by second and so far.

Now something which would fail:
my_list = [(0,"A"), (0, "B"), (0, 1)]
my_list.sort()
Error:
TypeError: '<' not supported between instances of 'int' and 'str'
And now a hidden error, which could appear:
my_list = [(0,"A"), (1, "B"), (2, 1)]
my_list.sort()
This code do not fail because Python don't check the second field, if the first field is unique.
But in the example before, all first fields had the same value. This is why the second field were checked and failed, because the last comparison was str vs int.

A helper function is operators.itemgetter, which is a callable, which return a new callable, which is the same as your key-function.

import operator

get_first = operator.itemgetter(0)
get_second = operator.itemgetter(1)

my_element = ["A", "B"]
print(get_first(my_element))
print(get_second(my_element))
Output:
A B
If you've instances of classes which do not support comparison and want to sort them by attributes, you can use the attrgetter from operator.
import random
import operator


# just a dummy class to demo
class Rand:
    def __init__(self):
        self.value = random.randint(0, 100)
    def __repr__(self):
        return f"Random {self.value}"
    __str__ = __repr__


rand_instances = [Rand() for _ in range(10)]
print("Original order:")
print(rand_instances)
print()

sorted_instances = sorted(rand_instances, key=operator.attrgetter("value"))
print(*sorted_instances, sep="\n")
Calling a plain sorted without key function on the list with the instances, will throw an Exception:
Error:
TypeError: '<' not supported between instances of 'Rand' and 'Rand'
I won't go further, just as a reference, you can implement comparison to a class:
https://docs.python.org/3.8/library/func...l_ordering
Thank you all for your elaborate answers.
Paul
(Jun-17-2020, 09:33 AM)kmzelikahle Wrote: [ -> ]The fact that you are identifying elements by position suggests to Python iterability

Actually, no. Sets and dictionaries are iterable but don't have a notion of position of elements. Iterable objects implement __iter__ (see the docs).