0Pricing
Scala for Backend Engineering & Functional Programming · Урок

reduce и агрегация

Другие способы объединять элементы.

«reduce и агрегация» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

От свёртки к reduce

Иногда требуется объединить элементы без отдельного начального значения.

reduce использует первый элемент как начальный аккумулятор и объединяет с ним остальные.

Это более лаконичный родственник fold для случаев, когда тип элементов и тип результата совпадают.

Основы reduce

reduce принимает бинарную функцию (a, b) и применяет её ко всей коллекции без явно заданного начального значения.

Для суммы он просто складывает все элементы.

val xs = List(1, 2, 3, 4)
val total = xs.reduce((a, b) => a + b)

@main def run(): Unit =
  println(total)  // 10

Проблема пустой коллекции

Поскольку у reduce нет начального значения, вызов для пустой коллекции выбрасывает UnsupportedOperationException.

fold и foldLeft безопасны для пустых коллекций, поскольку используют предоставленное начальное значение.

Используйте reduce только тогда, когда уверены, что коллекция непуста.

// List.empty[Int].reduce(_ + _)
// throws UnsupportedOperationException

List.empty[Int].foldLeft(0)(_ + _)  // safe, returns 0

Безопасность с reduceOption

reduceOption возвращает Option, выдавая None для пустой коллекции вместо создания ошибки.

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

val xs = List(5, 9, 2)
val maybeMax = xs.reduceOption(_ max _)

@main def run(): Unit =
  println(maybeMax)  // Some(9)

reduceLeft и reduceRight

Как и у свёртки, у reduce есть варианты с направлением.

reduceLeft объединяет элементы слева направо, а reduceRight — справа налево. Обычный reduce не гарантирует порядок для параллельных коллекций, поэтому, когда направление важно, используйте явный вариант.

val xs = List(1, 2, 3)
val l = xs.reduceLeft(_ - _)   // (1-2)-3 = -4
val r = xs.reduceRight(_ - _)  // 1-(2-3) = 2

@main def run(): Unit =
  println((l, r))  // (-4, 2)

Зачем нужен aggregate

fold и reduce требуют, чтобы типы аккумулятора и элементов были связаны простым образом.

aggregate — наиболее общий вариант свёртки: он позволяет использовать аккумулятор другого типа AND указывает Scala, как объединять частичные аккумуляторы, что важно при параллельной обработке.

Сигнатура aggregate

aggregate принимает начальное значение, операцию свёртки элемента в аккумулятор и операцию объединения двух аккумуляторов.

В последовательной коллекции операция объединения используется редко, а в параллельной соединяет результаты обработки каждого фрагмента.

val xs = List(1, 2, 3, 4)
val sum = xs.aggregate(0)(
  (acc, x) => acc + x,   // seqop
  (a, b)   => a + b      // combop
)

@main def run(): Unit = println(sum)  // 10

aggregate с другим типом

Здесь aggregate сворачивает список слов в общую длину типа Int.

Операция свёртки добавляет длину каждого слова, а операция объединения складывает два частичных результата.

Тип аккумулятора (Int) отличается от типа элементов (String).

val words = List("hi", "there", "you")
val chars = words.aggregate(0)(
  (acc, w) => acc + w.length,
  (a, b)   => a + b
)

@main def run(): Unit = println(chars)  // 10

Параллельная агрегация

Настоящая сила aggregate проявляется при работе с параллельными коллекциями.

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

// Conceptual: par splits the work
// val n = data.par.aggregate(0)(_ + _.length, _ + _)
// seqop runs per chunk, combop merges chunk results

Выбор подходящего инструмента

Используйте reduce или reduceOption, когда тип результата совпадает с типом элементов, а объединение ассоциативно.

Используйте foldLeft, когда в последовательной обработке нужны начальное значение или другой тип результата.

Используйте aggregate, когда типы аккумулятора и элементов различаются и Вам нужно объединение, подходящее для параллельной обработки.

// reduce:    same type, non-empty, associative
// foldLeft:  seed + different type, sequential
// aggregate: different type + parallel merge

Объединение за один проход

aggregate может вычислять несколько значений одновременно, накапливая их в кортеже.

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

val xs = List(2, 4, 6, 8)
val (s, c) = xs.aggregate((0, 0))(
  (acc, x) => (acc._1 + x, acc._2 + 1),
  (a, b)   => (a._1 + b._1, a._2 + b._2)
)

@main def run(): Unit = println((s, c))  // (20, 4)

Быстрая проверка

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

Итоги

reduce объединяет элементы без начального значения, используя первый элемент как начало; для пустой коллекции он создаёт ошибку, а reduceOption вместо этого возвращает None.

aggregate — наиболее общий вариант свёртки: начальное значение, операция свёртки элементов и операция объединения частичных аккумуляторов для параллельной работы.

Выбирайте reduce для ассоциативного объединения значений одного типа, foldLeft — для последовательной свёртки с начальным значением, а aggregate — когда типы различаются или обработка выполняется параллельно.

Часто задаваемые вопросы

Урок «reduce и агрегация» бесплатный?

Да — полный текст урока «reduce и агрегация» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Чему я научусь в уроке «reduce и агрегация»?

Другие способы объединять элементы. Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?

Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «reduce и агрегация»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?

Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Рекурсивное мышление
  2. Шаблоны с аккумулятором
  3. foldLeft и foldRight
  4. reduce и агрегация
← Назад к Scala for Backend Engineering & Functional Programming