André Krosby
André Krosby

Reputation: 1146

How to write complex sort in python?

Is there a concise way to sort a list by first sorting numbers in ascending order and then sort the characters in descending order?

How would you sort the following:

['2', '4', '1', '6', '7', '4', '2', 'K', 'A', 'Z', 'B', 'W']

To:

['1', '2', '2', '4', '4', '6', '7', 'Z', 'W', 'K', 'B', 'A']

Upvotes: 4

Views: 590

Answers (3)

Jan
Jan

Reputation: 43169

One way (there might be better ones) is to separate digits and letters beforehand, sort them appropriately and glue them again together in the end:

lst = ['2', '4', '1', '6', '7', '4', '2', 'K', 'A', 'Z', 'B', 'W']

numbers = sorted([number for number in lst if number.isdigit()])
letters = sorted([letter for letter in lst if not letter.isdigit()], reverse=True)
combined = numbers + letters
print(combined)

Another way makes use of ord(...) and the ability to sort by tuples. Here we use zero for numbers and one for letters:

def sorter(item):
    if item.isdigit():
        return 0, int(item)
    else:
        return 1, -ord(item)

print(sorted(lst, key=sorter))

Both will yield

['1', '2', '2', '4', '4', '6', '7', 'Z', 'W', 'K', 'B', 'A']

As for timing:

def different_lists():
    global my_list
    numbers = sorted([number for number in my_list if number.isdigit()])
    letters = sorted([letter for letter in my_list if not letter.isdigit()], reverse=True)
    return numbers + letters

def key_function():
    global my_list
    def sorter(item):
        if item.isdigit():
            return 0, int(item)
        else:
            return 1, -ord(item)

    return sorted(my_list, key=sorter)

from timeit import timeit
print(timeit(different_lists, number=10**6))
print(timeit(key_function, number=10**6))

This yields (running it a million times on my MacBook):

2.9208732349999997
4.54283629

So the approach with list comprehensions is faster here.

Upvotes: 12

Karl Knechtel
Karl Knechtel

Reputation: 61509

To elaborate on the custom-comparison approach: in Python the built-in sort does key comparison.

How to think about the problem: to group values and then sort each group by a different quality, we can think of "which group is a given value in?" as a quality - so now we are sorting by multiple qualities, which we do with a key that gives us a tuple of the value for each quality.

Since we want to sort the letters in descending order, and we can't "negate" them (in the arithmetic sense), it will be easiest to apply reverse=True to the entire sort, so we keep that in mind.

We encode: True for digits and False for non-digits (since numerically, these are equivalent to 1 and 0 respectively, and we are sorting in descending order overall). Then for the second value, we'll use the symbol directly for non-digits; for digits, we need the negation of the numeric value, to re-reverse the sort.

This gives us:

def custom_key(value):
    numeric = value.isdigit()
    return (numeric, -int(value) if numeric else value)

And now we can do:

my_list.sort(key=custom_key, reverse=True)

which works for me (and also handles multi-digit numbers):

>>> my_list
['1', '2', '2', '4', '4', '6', '7', 'Z', 'W', 'K', 'B', 'A']
    

Upvotes: 3

Or Y
Or Y

Reputation: 2118

You will have to implement your own comparison function and pass it as the key argument for the sorted function. What you are seeking is not a trivial comparison as you "assign" custom values to fields so you will have to let Python know how you value each one of them

Upvotes: 0

Related Questions