Напишите через Stream API: посчитать частоту слов в списке и вывести топ-3 самых частых.
Короткий ответ
- groupingBy по слову + Collectors.counting даёт карту частот
- Нормализация toLowerCase до группировки
- Для топ-3: stream по entrySet, сортировка по значению вниз, limit
- comparingByValue().reversed() — готовый компаратор
- Результат собрать в LinkedHashMap, чтобы сохранить порядок
- Объяснить сложность: подсчёт O(n), сортировка O(k log k)
Задача на два шага: groupingBy + counting для частот, затем сортировка entrySet по значению с limit(3).
Как сказать вслух
пример ответаСначала группирую слова по самим себе со счётчиком — получаю карту «слово — количество». Перед этим привожу к нижнему регистру, чтобы «Java» и «java» считались одним словом. Потом беру стрим по записям карты, сортирую по значению по убыванию, ограничиваюсь тремя и собираю в LinkedHashMap, чтобы не потерять порядок. На словах важно оговорить, что обычный toMap порядок бы не сохранил.
Подробный ответ
Основной ответ
Решение в два конвейера. Первый: words.stream().collect(groupingBy(String::toLowerCase, counting())) — карта частот за O(n). Второй: по entrySet сортируем Map.Entry.comparingByValue(reverseOrder()) или .reversed(), берём limit(3) и собираем toMap с LinkedHashMap::new — обычный HashMap разрушил бы порядок сортировки, а это типовая ловушка. Полезные уточнения, которые ценит интервьюер: при равных частотах добавить вторичный компаратор по ключу для детерминизма; если вход — строка текста, предварительно split по регулярному выражению \\s+ и фильтр пустых; для больших данных топ-k эффективнее через кучу размера k, но для собеседования сортировки достаточно.
Ключевые моменты
- groupingBy + counting. Каноническая пара для частот; downstream-коллектор считает элементы группы.
- Сортировка карты. Map не сортируется сама — нужен stream по entrySet и компаратор по значению.
- LinkedHashMap в toMap. Четырёхаргументный toMap с фабрикой — иначе порядок топа потеряется.
- Крайние случаи. Регистр, пустые строки, ничья по частоте — проговорить до написания кода.
Практический контекст
Одна из самых частых разминочных задач на Java-собеседованиях: проверяет знание коллекторов глубже, чем map/filter. По той же схеме решаются «сгруппировать сотрудников по отделу», «средняя зарплата по городу» — меняется только downstream-коллектор. Сильный кандидат пишет решение сразу, проговаривает ловушку с порядком в toMap и предлагает обработку регистра и знаков препинания.
Пример кода
List<String> words = List.of("java", "Go", "java", "rust", "go", "java");
Map<String, Long> freq = words.stream()
.collect(Collectors.groupingBy(String::toLowerCase,
Collectors.counting()));
Map<String, Long> top3 = freq.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed()
.thenComparing(Map.Entry.comparingByKey()))
.limit(3)
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(a, b) -> a, LinkedHashMap::new));
System.out.println(top3); // {java=3, go=2, rust=1}Частые ошибки
- Собирают отсортированный результат в обычный HashMap и теряют порядок
- Забывают merge-функцию в toMap и получают исключение на дубликатах
- Пытаются отсортировать Map без стрима по entrySet, утверждая, что sorted применится к карте