Prashant Mall
Prashant Mall

Reputation: 37

Python Sorted() function multiple Keys

I came across a question on HackerRack website, which I solved using my own logic but someone had a much better solution which I am having a hard time to understand.

Question: Your task is to sort the string in the following manner: All sorted lowercase letters are ahead of uppercase letters. All sorted uppercase letters are ahead of digits. All sorted odd digits are ahead of sorted even digits.

Answer:

print(*sorted(input(), key=lambda c: (c.isdigit() - c.islower(), c in '02468', c)), sep='')

Can someone please explain the lambda function above? More specifically what the c.isdigit() - c.islower() is doing?

Upvotes: 3

Views: 434

Answers (3)

Vishal Singh
Vishal Singh

Reputation: 6234

a little bit about sorted() built-in function that builds a new sorted list from an iterable

sorted() have a key parameter to specify a function to be called on each list element before making comparisons.

The value of the key parameter should be a function that takes a single argument and returns a key(in this particular case it is a tuple) to use for sorting purposes. This technique is fast because the key function is called exactly once for each input record.

let's break the sorted function into a more readable piece of code.

def key_function(character):
    print((character.isdigit() - character.islower(), character in "02468", character))
    return (character.isdigit() - character.islower(), character in "02468", character)


input_string = "1949 Film Prejudice"

print(*sorted(input_string, key=key_function), sep="")

This is the intermediate list of tuple which I have contrived just for the sake of explanation of the problem.

[
    (1, False, "1"),
    (1, False, "9"),
    (1, True, "4"),
    (1, False, "9"),
    (0, False, " "),
    (0, False, "F"),
    (-1, False, "i"),
    (-1, False, "l"),
    (-1, False, "m"),
    (0, False, " "),
    (0, False, "P"),
    (-1, False, "r"),
    (-1, False, "e"),
    (-1, False, "j"),
    (-1, False, "u"),
    (-1, False, "d"),
    (-1, False, "i"),
    (-1, False, "c"),
    (-1, False, "e"),
]

The comparison between these tuples is the key to understand how this works.
If you'll just sort this list(which uses tuple comparison to sort) you will get the desired result.

We'll go through the 3 aspects of comparison happening during (1, False, "1") > (1, False, "9")

  1. Integer comparison
  2. Boolean comparison
  3. Character comparison

Integer comparison

Integer comparison intuitive because it's just integers we have to deal with.

Boolean comparison

just to give the glimpse of what True was False used to be

PEP 285

(True and False constants were added to the built-ins in Python 2.2.1, but the 2.2.1 versions are simply set to integer values of 1 and 0 and aren't a different type.)

so the comparison between them is also the same as between integers.
True - False is 1 and False - True is -1.

Character comparison

These strings are being ordered by the ASCII values of their characters (p is 112 in ASCII and P is 80). Technically Python compares the Unicode code point (which is what ord does) for these characters and that happens to be the same as the ASCII value for ASCII characters.

so ultimately it also boils down to integer comparison.

Hopefully, now you'll have a good understanding of what is happening in that sorted function :)


for more information read this article on Tuple ordering and deep comparisons in Python

https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/

Upvotes: 2

vvvvv
vvvvv

Reputation: 31609

If I try to substract two booleans, it will give me an integer since booleans are subclasses of int:

>>> True - True
0
>>> False - False
0
>>> False - True
-1

In your case, the first boolean correspond to the result of c.isdigit() and the second one to c.islower().

So:

>>> True - True  # ex: '7' -> isdigit(): True, islower(): True -> 1 - 1 = 0
0
>>> False - False  # ex: 'B' -> isdigit(): False, islower(): False -> 0 - 0 = 0
0
>>> False - True  # ex: 'g' -> isdigit(): False, islower(): True -> 0 - 1 = -1
-1

Every item in the tuple will be used to check order. Starting from the first item, and going to the 2nd and 3rd in case of ties.

Knowing that booleans can be compared to each other ((True > False) is True) and that strings can be too ('abc' < 'xyz'), you can have the following order of tuples:

(-1, 0, 'g') < (0, 0, 'B') < (0, 0, '7') < (0, 1, '6') < (0, 1, '8')

Then, since sorted does sort in increasing order by default, you get what you were expecting: sorted lowercase letters ahead of sorted uppercase letters ahead of sorted odd digits ahead of sorted even digits.

Upvotes: 2

user13823606
user13823606

Reputation:

The key here is a tuple and Python will determine the sorted position of elements on the bases of the first element of key to satisfy a unique position i.e. a comparison resulting in inequality. In this case the tuple key is assigning different key to each value.

For example: c='1234ABab', now for the 0th element '1' the isdigit() will return True being casted into 1 and islower() to 0 hence the first element of the key will be 1 second will be 0 and third will be '1' itself. Hence the key for this element is (1,0,'1') and this is the case for all odd nums. For evens the key will be (1,1,'2'), for lowers (-1,0,'a') and for capitals (0,0,'A'). This way sorting will be done sequentially satisfying the key elements.

For more, you can search for tuple keys in sorting.

Upvotes: 1

Related Questions