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

Подсчёт частоты слов с использованием Stream API

1.7 Middle🔥 251 комментариев
#Stream API и функциональное программирование#Основы Java

Условие

Используя Stream API, подсчитайте частоту каждого слова в тексте и верните Map<String, Long>.

Пример

Входные данные: "java is great and java is fun"

Выходные данные:

{java=2, is=2, great=1, and=1, fun=1}

Требования

  • Используйте Stream API и Collectors
  • Игнорируйте регистр
  • Решение в одну строку (functional style)

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

Подсчёт частоты слов с Stream API

Это классическая задача на собеседовании, которая проверяет понимание Stream API, особенно groupingBy() коллектора.

Решение

import java.util.Map;
import java.util.stream.Collectors;
import java.util.Arrays;

public class WordFrequency {
    public static void main(String[] args) {
        String text = "java is great and java is fun";
        
        Map<String, Long> frequency = Arrays.stream(text.split(" "))
            .map(String::toLowerCase)
            .collect(Collectors.groupingBy(
                word -> word,
                Collectors.counting()
            ));
        
        System.out.println(frequency);
        // Вывод: {java=2, is=2, and=1, great=1, fun=1}
    }
}

Разбор решения пошагово

1. Разделение текста на слова

text.split(" ")  // Массив строк
// ["java", "is", "great", "and", "java", "is", "fun"]

2. Создание Stream из массива

Arrays.stream(text.split(" "))
// Stream<String>

3. Преобразование в нижний регистр

.map(String::toLowerCase)
// Stream<String> с «java», «is», «great»... (все нижнего регистра)

4. Группировка и подсчёт

.collect(Collectors.groupingBy(
    word -> word,                    // Ключ (само слово)
    Collectors.counting()            // Значение (кол-во)
))
// Map<String, Long>

Детальный разбор groupingBy()

groupingBy() — это коллектор, который группирует элементы по ключу:

// Синтаксис
Collectors.groupingBy(
    keyMapper,                       // Функция для извлечения ключа
    downstream                       // Коллектор для значения (опционально)
)

Что происходит:

  1. Для каждого элемента вычисляется ключ с помощью keyMapper
  2. Элементы группируются по этому ключу
  3. Для каждой группы применяется downstream коллектор
  4. Результат — Map<Key, Value>

Альтернативные решения

С регулярными выражениями (более гибко)

import java.util.regex.Pattern;

String text = "java is great and java is fun";

Map<String, Long> frequency = Pattern.compile(" ")
    .splitAsStream(text)
    .map(String::toLowerCase)
    .collect(Collectors.groupingBy(
        word -> word,
        Collectors.counting()
    ));

С собственной функцией извлечения

public class WordFrequency {
    public static void main(String[] args) {
        String text = "java is great and java is fun";
        
        Map<String, Long> frequency = Arrays.stream(text.split(" "))
            .map(String::toLowerCase)
            .map(String::trim)              // Удалить пробелы
            .filter(word -> !word.isEmpty()) // Исключить пустые
            .collect(Collectors.groupingBy(
                Function.identity(),        // Явно: слово -> слово
                Collectors.counting()
            ));
        
        System.out.println(frequency);
    }
}

С сортировкой по частоте

import java.util.LinkedHashMap;

Map<String, Long> frequency = Arrays.stream(text.split(" "))
    .map(String::toLowerCase)
    .collect(Collectors.groupingBy(
        word -> word,
        Collectors.counting()
    ))
    // Сортировка по значению (частоте) в убывающем порядке
    .entrySet().stream()
    .sorted((a, b) -> Long.compare(b.getValue(), a.getValue()))
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        Map.Entry::getValue,
        (e1, e2) -> e1,
        LinkedHashMap::new  // Сохранить порядок
    ));

С игнорированием пунктуации

public class WordFrequencyAdvanced {
    public static void main(String[] args) {
        String text = "Java is great! And Java is fun."
        
        Map<String, Long> frequency = Arrays.stream(text.split("\\s+"))
            .map(word -> word.replaceAll("[^a-zA-Z]", ""))  // Удалить не-буквы
            .map(String::toLowerCase)
            .filter(word -> !word.isEmpty())                 // Исключить пустые
            .collect(Collectors.groupingBy(
                word -> word,
                Collectors.counting()
            ));
        
        System.out.println(frequency);
        // Вывод: {java=2, is=2, and=1, great=1, fun=1}
    }
}

Вывод результата красиво

Map<String, Long> frequency = Arrays.stream(text.split(" "))
    .map(String::toLowerCase)
    .collect(Collectors.groupingBy(
        word -> word,
        Collectors.counting()
    ));

// Способ 1: Простая печать
System.out.println(frequency);

// Способ 2: Красивый вывод
frequency.forEach((word, count) -> 
    System.out.println(word + ": " + count)
);

// Способ 3: С форматированием
frequency.entrySet().stream()
    .sorted((a, b) -> Long.compare(b.getValue(), a.getValue()))
    .forEach(entry -> 
        System.out.printf("%s: %d%n", entry.getKey(), entry.getValue())
    );

Полный пример с тестами

import java.util.Map;
import java.util.Arrays;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

public class WordFrequencyTest {
    
    public static Map<String, Long> countWords(String text) {
        return Arrays.stream(text.split(" "))
            .map(String::toLowerCase)
            .collect(Collectors.groupingBy(
                word -> word,
                Collectors.counting()
            ));
    }
    
    @Test
    public void testBasicFrequency() {
        String text = "java is great and java is fun";
        Map<String, Long> result = countWords(text);
        
        assertEquals(2, result.get("java"));
        assertEquals(2, result.get("is"));
        assertEquals(1, result.get("great"));
        assertEquals(1, result.get("and"));
        assertEquals(1, result.get("fun"));
    }
    
    @Test
    public void testCaseInsensitive() {
        String text = "Java JAVA java";
        Map<String, Long> result = countWords(text);
        
        assertEquals(1, result.size());
        assertEquals(3, result.get("java"));
    }
    
    @Test
    public void testEmptyString() {
        String text = "";
        Map<String, Long> result = countWords(text);
        
        assertEquals(1, result.size());
        // Один элемент - пустая строка
    }
    
    @Test
    public void testSingleWord() {
        String text = "java";
        Map<String, Long> result = countWords(text);
        
        assertEquals(1, result.size());
        assertEquals(1, result.get("java"));
    }
}

Объяснение для интервьюера

Почему этот подход хороший:

  1. Читаемость — код на функциональном стиле, понятно намерение
  2. Производительность — Streams оптимизированы и потокобезопасны
  3. Простота — одна строка вместо цикла с if/else
  4. Масштабируемость — легко добавить фильтры или преобразования

Сложность:

  • Временная: O(n) — проходим по всем словам один раз
  • Пространственная: O(k) — где k = количество уникальных слов

Вариант с игнорированием пунктуации и специальных символов

public static Map<String, Long> countWordsAdvanced(String text) {
    return Arrays.stream(text.toLowerCase().split("[\\s\\p{Punct}]+"))
        .filter(word -> !word.isEmpty())
        .collect(Collectors.groupingBy(
            word -> word,
            Collectors.counting()
        ));
}

// Пример
String text = "Hello, world! Hello Java. Java is great.";
Map<String, Long> freq = countWordsAdvanced(text);
// {hello=2, world=1, java=2, is=1, great=1}

Заключение

Эта задача демонстрирует мастерство в:

  • Stream API и терминальных операциях
  • Collectors и особенно groupingBy()
  • Функциональном стиле программирования
  • Обработке строк

Ключевой моменты для ответа на собеседовании:

  1. Используй groupingBy() с counting() коллектором
  2. Преобразуй текст в нижний регистр для case-insensitive
  3. Объясни, как работает groupingBy() под капотом
  4. Упомяни возможные улучшения (пунктуация, производительность)