4

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']

3 Answers 3

12

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.

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

4 Comments

One can use generator instead of list comprehension by removing square bracket
@GuillaumeJacquenot: Haven't tested it but I'm sure there's no real performance boost in using a generator here. Might be wrong though.
IMX the generator approach is usually actually slower - but it thrashes memory less (which may prevent a MemoryError if you need to sort something that takes up a large percentage of available memory), and is more elegant. Of course, you still need parentheses to use a generator expression for the letters, since there is more than one argument for the call.
@KarlKnechtel: Good to know. I've thought of another approach (in the updated answer), maybe there's an advantage when using generators.
3

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']
    

1 Comment

You can, of course, "negate" the strings using ord, and you can even make that work for longer strings by mapping ord onto each character. This seems more elegant to me, though. Notice that there isn't a requirement for all the tuples to have the same types in each position - as long as comparisons between incompatible types aren't ever required. We can guarantee that statically here.
0

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

1 Comment

This doesn't really answer the question... The question is basically what would be that key...

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.