Помогите разобраться с решением задачи Python

Условие:

Дан текст на русском языке. Посчитайте, сколько раз встречается в тексте каждое слово.

Слово — это последовательность русских букв. Кроме слов, в тексте могут присутствовать пробелы и знаки препинания («,», «.», «!», «?», «:» и «;»). Других символов в тексте нет.

Слова необходимо сравнивать с точностью до регистра. То есть «Привет» и «привет» — это одно и то же слово, а вот «рыба», «рыбы» и «рыбу» — три разных.


Ввод:

В первой строчке задано одно целое число N (1 ≤ N ≤ 1000) — количество строк в тексте. В следующих N строках приведён сам русский текст. Текст состоит из русских букв, пробелов и знаков препинания: «,», «.», «!», «?», «:» и «;». Длина текста не превышает 100 000 символов.


Вывод: Выведите слова исходного текста, упорядоченные по убыванию количества их попаданий в текст. В случае, если два слова встречаются в тексте одинаковое количество раз, выведите их в лексикографическом порядке.

Каждое слово нужно вывести в отдельной строке. Первая буква слова должна быть заглавной (даже если она не была заглавной в исходном тексте), остальные — строчными.

Пример:

Ввод:

2
Ехал Грека через реку. Видит Грека в реке рак.
Сунул Грека руку в реку, рак за руку Греку цап.

Вывод:

Грека
В
Рак
Реку
Руку
Видит
Греку
Ехал
За
Реке
Сунул
Цап
Через

У меня даже получилось решить задачу, но не без помощи, мне подсказали использовать непонятную мне функцию:

words_dict = {}
sentences1 = [input().split() for i in range(int(input()))]
sentences = [j.strip('!?:;,.').capitalize() for i in sentences1 for j in i]
for i in sentences:
    words_dict[i] = words_dict[i] + 1 if i in words_dict else 1
for word, qwerty in sorted(words_dict.items(), key=lambda x: (-x[1], x[0])): # вот тут непонятно key=lambda x: (-x[1], x[0])
    print(word.capitalize())

На строке 6 в конце мне не до конца понятно, как работает выделенная функция, а именно, что она делает.

P.S. Заранее спасибо!


Ответы (2 шт):

Автор решения: Евгений

words_dict.items() - это список значений вашего словаря. Он имеет вид:

dict_items([('Ехал', 1), ('Грека', 3), ('Через', 1), ('Реку', 2), ('Видит', 1), ('В', 2), ('Реке', 1), ('Рак', 2), ('Сунул', 1), ('Руку', 2), ('За', 1), ('Греку', 1), ('Цап', 1)])

Зная это, можно дать пояснение к сортировке:

# Отсортированный words_dict.items(), 
# где сначала в обратном порядке производится сортировка по x[1], т.е. количеству,
# а потом - внутри секций в прямом порядке по x[0], т.е. по самим словам
sorted(words_dict.items(), key = lambda x: (-x[1], x[0]))

Параметр key в функции sorted указывает, по чём именно сортируется итерируемый объект.


Более простой пример: имея список кортежей [(1, 2), (-2, 3), (5, -10)], можно с помощью key отсортировать его, например, по нулевому элементу:

# x - это сами элементы списка, а сортируем мы по x[0]
>>> sorted([(1, 2), (-2, 3), (5, -10)], key = lambda x: x[0]) 
[(-2, 3), (1, 2), (5, -10)]
→ Ссылка
Автор решения: MaxU

Лямбда функция - это вариант безымянной функции, состоящей из одного выражения / команды. Является синтаксическим сахаром и всегда может быть заменена обычной функцией (см. пример ниже). Часто применяется для сокращения кода, в тех местах где нужно передавать функцию в качестве параметра, например в качестве параметра key во встроенных функциях: min(), max(), sorted(), etc..

Пример замены обычной однокомандной функции лямбда функцией:

In [38]: def revers(s):
    ...:     return s[::-1]
    ...:

In [39]: revers("AbCdE")
Out[39]: 'EdCbA'

In [40]: (lambda x: x[::-1])("AbCdE")
Out[40]: 'EdCbA'

Конечно выглядит это ужасно и никто не применяет лямда функции таким образом. Применение лямбда функций удобно и оправдано в том случае, когда на нужно передавать функцию в качестве аргумента другой функции или объекту класса.

Пример с сортировкой: - допустим надо надо отсортировать список кортежей сначала по второму элементу в убывающем порядке, а потом по первому элементу в возрастающем (как в вашем предыдущем вопросе):

In [42]: items = [(1,10), (1,20), (2,10), (2,20)]

In [43]: def f(x):
    ...:     return (-x[1], x[0])
    ...:

In [44]: sorted(items, key=f)
Out[44]: [(1, 20), (2, 20), (1, 10), (2, 10)]

того же эффекта можно добиться, не создавая именованной функции:

In [45]: sorted(items, key=lambda x: (-x[1], x[0]))
Out[45]: [(1, 20), (2, 20), (1, 10), (2, 10)]

Ответ на вопрос из комментария:

Как сортировать строки в обратном алфавитном порядке?

в том случае когда порядок сортировки всегда один - можно воспользоваться параметром sorted(..., reverse=True)

In [48]: sorted(res.items(), key=lambda x: (x[1], x[0]), reverse=True)
Out[48]:
[('грека', 3),
 ('руку', 2),
 ('в', 2),
 ('через', 1),
 ('цап', 1),
 ('сунул', 1),
 ('реку.', 1),
 ('реку,', 1),
 ('реке', 1),
 ('рак.', 1),
 ('рак', 1),
 ('за', 1),
 ('ехал', 1),
 ('греку', 1),
 ('видит', 1)]
→ Ссылка