Сложное лямбда-выражение в качестве ключевого аргумента сортируемой функции

#python #sorting

Вопрос:

У меня есть пример кода:

 In [60]: sorted([3, 1, None], key=lambda x: (x is None, x))
Out[60]: [1, 3, None]

In [61]: sorted([3, 1, None], key=lambda x: (x is not None, x))
Out[61]: [None, 1, 3]

 

Я думаю, что понимаю, что он делает — похоже, он позволяет пропускать None значения ключа во время сортировки, иначе сортировка вызовет TypeError попытку сравнения int None , — но я не понимаю, как и почему он работает так, как работает. В частности, меня смущает лямбда-функция, которая возвращает кортеж.

Я не смог найти ничего подходящего в инструкции по сортировке. Я был бы признателен за объяснение или ссылку на то, где задокументировано такое поведение.

Комментарии:

1. Вы понимаете, как работает сравнение кортежей? Учитывая ваши данные, понимаете ли вы, как (True, 1) < (False, None) их можно было бы сравнить?

2. Лямбда создает кортеж, и кортежи сравниваются по элементам.

Ответ №1:

Кортежи сравниваются и сортируются в лексикографическом порядке:

 >>> sorted([(0, 17), (1,15), (0,12), (0, 9), (1, 8), (1, 7), (0, 2)])
[(0, 2), (0, 9), (0, 12), (0, 17), (1, 7), (1, 8), (1, 15)]
>>> (0, 12) < (1, 9)
True
>>> (0, 12) < (0, 13)
True
>>> (1, 12) < (0, 9)
False
 

«Лексикографический порядок» — это просто модное слово для «как слова в словаре английского языка»: сначала сравните первую букву, и только если первая буква равна, затем сравните вторую букву, и только если первые две буквы равны, затем сравните третью букву и т. Д.

Используя кортеж в качестве ключа, кортежи также будут сравниваться в лексикографическом порядке.

В вашем случае x is None и x is not None оцените до логических значений, True или False .

Логические значения также можно сравнивать:

 >>> False < True
True
>>> True < False
False
 

В результате sorted([3, 1, None], key=lambda x: (x is None, x)) будем считать, что наименьшими элементами являются те, для которых x is None это Ложь, а самыми большими элементами являются те, для которых x is None это Правда:

 >>> sorted([3, 1, None], key=lambda x: (x is None, x))
[1, 3, None]
>>> sorted(map(lambda x: (x is None, x), [3, 1, None]))
[(False, 1), (False, 3), (True, None)]
 

Комментарии:

1. Теперь я все понимаю. Я упустил тот момент, что результаты лямбда-выражений сравнивались друг с другом, а не с самими элементами итерации. Сочетая это понимание с тем, как работает сравнение кортежей, теперь все это имеет смысл. Спасибо!

2. @AndriiYurchuk Да, и такое поведение характерно практически для всех функций python, у которых есть key необязательный аргумент, а не только sorted и list.sort . Например, max([3, -12, 5], key = abs) вернется -12 , потому что он имеет наибольшее абсолютное значение, и [list(g) for k,g in itertools.groupby([1, 0, 2, 4, 8, 9, 7], key=lambda x: x % 2)] сгруппирует соседние элементы по четности: [[1], [0, 2, 4, 8], [9, 7]]

Ответ №2:

key Параметр of sorted используется для ранжирования значений в порядке элементов, если их несколько.

Что делает ваш код, так это ставит None первым (или последним) и в качестве вторичного ключа сортирует элементы, не содержащие None

Первый ключ: [3, 1, None] сопоставляется с [False, False, True] тем, что эквивалентно [0, 0, 1] , и нажимает None до конца.

Второй ключ-разорвать связь между 3 и 1

Комментарии:

1. говоря о том, почему None ставится как в прошлом, когда он только что вернулся бы True потому, что число True это 1 и False равна 0, так что (в первом случае), он будет сортировать сначала [False (3), False (1), True (None)] (правда целочисленное значение больше, и по умолчанию в порядке возрастания), а затем оттуда она будет уходить [1, 3, None] , потому что при использовании первичного ключа None выше, поэтому он остается там, а остальное значения не устроюсь, потому что они имеют одинаковое значение первичного ключа

2. @Matiiss спасибо, я мог бы дать только быстрый ответ, но я вижу, что сейчас есть полный ответ 😉