← Назад к вопросам

Подсчет вхождений слова в текст

2.2 Middle🔥 172 комментариев
#Теория тестирования

Условие

Напишите функцию, которая подсчитывает количество вхождений каждого слова в тексте.

Пример

Вход: hello world hello Выход: {hello: 2, world: 1}

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Развернутый ответ на задачу о подсчете вхождений слов

В качестве QA Automation инженера с 10+ лет опыта, я подхожу к этой задаче не только с точки зрения написания рабочего кода, но и с учетом тестируемости, производительности, читаемости и потенциальных крайних случаев. Задача кажется простой, но в реальных проектах она часто усложняется требованиями к обработке разных языков, пунктуации, регистра и производительности на больших объемах данных.

Ключевые аспекты реализации

  1. Нормализация текста: Приведение к нижнему регистру для регистронезависимого подсчета.
  2. Токенизация: Разделение текста на слова. Важно корректно обработать пунктуацию и пробелы.
  3. Структура данных: Использование словаря (хэш-таблицы) для подсчета, так как операции добавления и поиска в среднем занимают O(1).
  4. Обработка крайних случаев: Пустая строка, повторяющиеся пробелы, знаки препинания, смешанные языки.

Базовая реализация на Python

def count_words(text):
    """
    Подсчитывает количество вхождений каждого слова в тексте.
    
    Args:
        text (str): Входной текст для анализа.
    
    Returns:
        dict: Словарь, где ключи - слова, значения - количество их вхождений.
              Возвращает пустой словарь для пустого или состоящего только из пробелов текста.
    
    Пример:
        >>> count_words("hello world hello")
        {'hello': 2, 'world': 1}
        >>> count_words("Hello, world! Hello!")
        {'hello': 2, 'world': 1}
    """
    if not text or text.isspace():
        return {}

    # 1. Нормализация: приводим к нижнему регистру
    normalized_text = text.lower()
    
    # 2. Токенизация: удаляем знаки препинания и разбиваем по пробелам
    #    Более надежный способ - использовать регулярные выражения
    import re
    # \w+ соответствует последовательностям буквенно-цифровых символов и подчеркивания
    # Добавим поддержку апострофов в словах типа "it's" -> ["it", "s"]? Нет, оставим "it's".
    # Для большинства задач подходит шаблон, включающий буквы (включая Unicode) и апострофы.
    words = re.findall(r"[a-zа-яё0-9']+", normalized_text)
    
    # 3. Подсчет с использованием словаря
    word_count = {}
    for word in words:
        # Можно использовать .get() или collections.defaultdict
        word_count[word] = word_count.get(word, 0) + 1
    
    return word_count


# Пример использования
if __name__ == "__main__":
    test_text = "Hello, world! Hello again. The world is great, isn't it?"
    result = count_words(test_text)
    print(result)  # {'hello': 2, 'world': 2, 'again': 1, 'the': 1, 'is': 1, 'great': 1, "isn't": 1, 'it': 1}

Альтернативная, более эффективная реализация с использованием collections.Counter

from collections import Counter
import re

def count_words_counter(text):
    """Более питоничная и эффективная версия с использованием Counter."""
    if not text or text.isspace():
        return {}
    
    words = re.findall(r"[a-zа-яё0-9']+", text.lower())
    return dict(Counter(words))

Тестовое покрытие как QA Automation инженер

Для полноценного решения я бы обязательно написал юнит-тесты, покрывающие основные и крайние случаи:

import pytest

def test_count_words_basic():
    assert count_words("hello world hello") == {"hello": 2, "world": 1}

def test_count_words_case_insensitive():
    assert count_words("Hello World hElLo") == {"hello": 2, "world": 1}

def test_count_words_with_punctuation():
    assert count_words("Hello, world! Hello...") == {"hello": 2, "world": 1}

def test_count_words_empty_string():
    assert count_words("") == {}

def test_count_words_only_spaces():
    assert count_words("   \n\t  ") == {}

def test_count_words_single_word():
    assert count_words("test") == {"test": 1}

def test_count_words_with_apostrophe():
    # Важно: решение с шаблоном [a-zа-яё0-9']+ сохранит апостроф внутри слова
    result = count_words("It's a test. Don't panic!")
    assert result.get("it's") == 1
    assert result.get("don't") == 1
    assert result.get("a") == 1

def test_count_words_unicode():
    # Поддержка кириллицы (если шаблон включает а-яё)
    assert count_words("привет мир привет") == {"привет": 2, "мир": 1}

Обсуждение сложности и оптимизаций

  • Временная сложность: O(n), где n - длина текста. re.findall проходит по строке один раз, последующий подсчет - O(k), где k - количество уникальных слов.
  • Пространственная сложность: O(k) для хранения словаря с результатами.
  • Оптимизации для больших текстов:
    *   Для обработки файлов гигабайтного размера стоит использовать **поточную обработку** (чтение блоками).
    *   Можно рассмотреть использование более эффективных структур данных, таких как **Trie (префиксное дерево)**, если нужен поиск по префиксам или автодополнение.
    *   Для многопоточной обработки очень больших текстов можно разбить текст на части и использовать **многопоточность** или **MapReduce** паттерн.

Вывод для собеседования

Как QA Automation специалист, я бы акцентировал внимание на том, что даже простая задача требует:

  1. Анализа требований (нужна ли поддержка Unicode, как обрабатывать составные слова, гифены).
  2. Выбора корректных инструментов (регулярные выражения vs простой split, словарь vs Counter).
  3. Написания тестов для валидации всех сценариев, включая крайние случаи.
  4. Документирования функции, особенно поведения с граничными значениями.
  5. Размышления о масштабировании решения для обработки больших данных.

Эта задача отлично демонстрирует разницу между кодом, который "просто работает", и промышленным решением, готовым к интеграции в реальный проект с высокими требованиями к надежности и поддерживаемости.