← все задачи

Python · задача 2 из 10

Топ-N самых частых слов

Начальный 10–15 минут Counterheapqнормализация текста

Условие

Дан текст. Верните N самых частых слов с их количеством, от частых к редким. Слова сравниваются без учёта регистра, знаки препинания не считаются частью слова.

Что требуется

  • Регистр не важен: «Python» и «python» — одно слово
  • При равной частоте порядок — алфавитный
  • N больше числа уникальных слов — вернуть всё, что есть

Пример

text = "Python is great. python IS fast, and Python is simple!"

top_words(text, 2)
# [("python", 3), ("is", 3)]  -> при равной частоте сначала "is"
# правильный ответ: [("is", 3), ("python", 3)]

Сначала уточните

Вопросы до кода — половина оценки. Молча начать печатать хуже, чем задать два вопроса.

  • Что считать словом: только буквы, или дефисы и апострофы тоже (don-t, кто-то)?
  • Нужно ли выбрасывать стоп-слова вроде «и», «the»?
  • Как разрешать равные частоты — по алфавиту или порядок не важен?
Показать решение Скрыть решение

Решение

import re
from collections import Counter

WORD_RE = re.compile(r"[a-zа-яё0-9]+(?:-[a-zа-яё0-9]+)*", re.IGNORECASE)


def top_words(text, n):
    words = WORD_RE.findall(text.lower())
    counts = Counter(words)

    # сортируем по убыванию частоты, при равной частоте — по алфавиту
    return sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:n]


# Если уникальных слов очень много, а N маленькое:
import heapq


def top_words_heap(text, n):
    counts = Counter(WORD_RE.findall(text.lower()))
    return heapq.nsmallest(n, counts.items(), key=lambda kv: (-kv[1], kv[0]))

Почему так

Почему Counter, а не ручной словарь

  • Counter — это тот же dict, но написанный за вас и на C: короче и быстрее ручного цикла
  • У него есть most_common(n), но он не решает вопрос равных частот — порядок будет «как легло», поэтому в решении явный sorted
  • Знание most_common и его ограничения — ровно то, что проверяет интервьюер

Почему ключ сортировки (-count, word)

  • Один проход сортировки вместо двух: минус перед количеством даёт убывание, слово следом — алфавит при равенстве
  • Так результат детерминирован: на одних и тех же данных всегда один ответ, что важно и для тестов
  • reverse=True здесь не подходит: он развернул бы и алфавитный порядок тоже

Когда heapq лучше sorted

  • sorted — O(u log u) по числу уникальных слов, heapq.nsmallest — O(u log n), и при n=10 против миллиона уникальных это заметно
  • На тексте из задачи разницы нет — говорите об этом как об оптимизации «если данных станет много»
  • Преждевременно писать heapq сразу — минус: сначала простое решение, потом обсуждение

Что спросят дальше

  • text.split() вместо регулярки — тогда «great.» и «great» станут разными словами; это первое, что проверят на примере
  • Спросят про огромный файл: читать построчно и обновлять Counter, а не грузить текст целиком
  • Могут попросить без Counter — покажите тот же цикл с dict.get(word, 0) + 1

Следующая задача

Разложить вложенный словарь в плоский — Живая задача из интеграций: конфиг или JSON от чужого API нужно превратить в плоские ключи.