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

Несколько генераторов

Вложенная итерация

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

Вложенная итерация в плоской записи

For-выражение может содержать несколько генераторов. Каждый из них — это строка с <-, которая перебирает коллекцию. Вместе они создают все комбинации — своего рода вложенный цикл, записанный в плоском виде.

object Main {
  def main(args: Array[String]): Unit = {
    val pairs = for {
      x <- List(1, 2)
      y <- List('a', 'b')
    } yield (x, y)
    println(pairs)
  }
}

Это декартово произведение

Два генератора размеров m и n создают m × n результатов — полное декартово произведение. Первый генератор образует внешний цикл, второй — внутренний.

object Main {
  def main(args: Array[String]): Unit = {
    val grid = for {
      row <- 1 to 3
      col <- 1 to 3
    } yield s"r$row-c$col"
    println(grid.mkString(", "))
  }
}

Внутренний генератор может зависеть от внешнего

Последующий генератор может использовать значения, связанные предыдущими генераторами. При обычном декартовом произведении это невозможно; именно поэтому for-выражения настолько гибки.

object Main {
  def main(args: Array[String]): Unit = {
    val upperTriangle = for {
      i <- 1 to 4
      j <- i to 4
    } yield (i, j)
    println(upperTriangle)
  }
}

Три и более генератора

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

object Main {
  def main(args: Array[String]): Unit = {
    val triples = for {
      a <- 1 to 2
      b <- 1 to 2
      c <- 1 to 2
    } yield (a, b, c)
    println(triples.size + " combinations")
    triples.foreach(println)
  }
}

Классический пример с теоремой Пифагора

Известный пример — поиск пифагоровых троек. Мы перебираем три числа и оставляем только те, для которых выполняется a*a + b*b == c*c, сочетая несколько генераторов с защитным условием.

object Main {
  def main(args: Array[String]): Unit = {
    val triples = for {
      a <- 1 to 20
      b <- a to 20
      c <- b to 20
      if a * a + b * b == c * c
    } yield (a, b, c)
    triples.foreach(println)
  }
}

Как выполняется преобразование

Несколько генераторов превращаются во вложенные вызовы flatMap с завершающим map:

  • xs.flatMap(x => ys.flatMap(y => zs.map(z => ...)))

Каждый дополнительный генератор добавляет ещё один уровень вложенности.

object Main {
  def main(args: Array[String]): Unit = {
    val sugar = for {
      x <- List(1, 2)
      y <- List(3, 4)
    } yield x * y
    val desugared = List(1, 2).flatMap(x => List(3, 4).map(y => x * y))
    println(sugar == desugared)
  }
}

Сочетание генераторов и защитных условий

Генераторы и защитные условия можно свободно чередовать. Защитное условие между двумя генераторами отбрасывает внешние значения ещё до запуска внутреннего цикла.

object Main {
  def main(args: Array[String]): Unit = {
    val result = for {
      x <- 1 to 5
      if x % 2 == 1
      y <- 1 to 5
      if y > x
    } yield (x, y)
    println(result)
  }
}

Объединение вложенных коллекций в одну

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

object Main {
  def main(args: Array[String]): Unit = {
    val matrix = List(List(1, 2), List(3, 4), List(5, 6))
    val flat = for {
      row <- matrix
      value <- row
    } yield value
    println(flat)
  }
}

Создание строковой таблицы

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

object Main {
  def main(args: Array[String]): Unit = {
    val table = for {
      i <- 1 to 3
      j <- 1 to 3
    } yield s"$i*$j=${i * j}"
    println(table.grouped(3).map(_.mkString("  ")).mkString("\n"))
  }
}

Сочетание коллекций разного размера

Генераторы не обязаны иметь одинаковую длину. Создаётся каждая комбинация, состоящая из одного элемента каждой коллекции.

object Main {
  def main(args: Array[String]): Unit = {
    val sizes = List("S", "M", "L")
    val colors = List("red", "blue")
    val products = for {
      s <- sizes
      c <- colors
    } yield s"$c-$s"
    println(products)
  }
}

Порядок влияет на последовательность результатов

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

object Main {
  def main(args: Array[String]): Unit = {
    val ab = for { x <- List(1, 2); y <- List(9, 8) } yield (x, y)
    val ba = for { y <- List(9, 8); x <- List(1, 2) } yield (x, y)
    println(ab)
    println(ba)
  }
}

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

Сколько кортежей создаёт for { x <- List(1,2,3); y <- List('a','b') } yield (x, y)?

Итоги

Вы узнали о нескольких генераторах:

  • Каждая строка с <- добавляет уровень вложенной итерации
  • Вместе они создают декартово произведение
  • Внутренние генераторы могут зависеть от значений, связанных внешними генераторами
  • Они преобразуются во вложенные вызовы flatMap и завершающий map
  • Порядок генераторов изменяет порядок результатов

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

Урок «Несколько генераторов» бесплатный?

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

Чему я научусь в уроке «Несколько генераторов»?

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

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

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

Сколько времени занимает урок «Несколько генераторов»?

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

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

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

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

  1. Десахаризация for
  2. Фильтрация с помощью if
  3. Несколько генераторов
  4. Использование Option и Future
← Назад к Scala for Backend Engineering & Functional Programming