Какие функции в Scala использовать для группировки и агрегации данных
Что проверяют
Знаете ли вы коллекции Scala на уровне, достаточном для работы с данными, и понимаете ли, где заканчиваются коллекции и начинается Spark. Java-разработчику почти всегда задают встречный вопрос: как то же самое выглядит через Collectors.groupingBy.
Как отвечать
Для обычных коллекций (Scala 2.13 и Scala 3 используют одну стандартную библиотеку):
groupBy:orders.groupBy(_.city)вернётMap[String, List[Order]], порядок ключей не гарантирован;groupMap: группирует и сразу преобразует элементы,orders.groupMap(_.city)(_.amount);groupMapReduce: группировка, преобразование и свёртка за один проход без промежуточных списков. Подсчёт слов:words.groupMapReduce(identity)(_ => 1)(_ + _);foldLeft: свёртка с начальным значением, тип результата может отличаться от типа элементов, например накоплениеMapили пары из суммы и количества;reduce: свёртка без начального значения, на пустой коллекции бросаетUnsupportedOperationException, безопаснееreduceOption;sum,maxBy,count,partition: готовые агрегаты для частых случаев.
Для сравнения с Java: groupMapReduce решает ту же задачу, что Collectors.groupingBy(f, Collectors.summingInt(g)) или Collectors.toMap с функцией слияния.
Когда данных много, речь уже о Spark. В DataFrame API это df.groupBy("city").agg(sum("amount"), count("id")) с импортом org.apache.spark.sql.functions._, плюс оконные функции через Window.partitionBy. В RDD важно различие: reduceByKey сворачивает значения внутри каждой партиции до shuffle, а groupByKey гонит по сети все значения и легко приводит к нехватке памяти на исполнителях.
Где спотыкаются
Советуют aggregate из старых статей. В Scala 2.13 он объявлен устаревшим для последовательных коллекций, вместо него foldLeft.
Пишут groupBy(...).mapValues(...), не зная, что в 2.13 mapValues на Map устарел и возвращает ленивое представление: функция пересчитывается при каждом обращении. Нужно .view.mapValues(...).toMap или сразу groupMapReduce.
Используют groupByKey там, где нужна сумма по ключу, и получают тяжёлый shuffle вместо дешёвой частичной агрегации.