0Pricing
Scala for Backend Engineering & Functional Programming · レッスン

複数のジェネレーター

ネストした反復処理です。

「複数のジェネレーター」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これは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の2つのジェネレーターからは、完全な直積であるm × n個の結果が生成されます。最初のジェネレーターが外側のループ、2番目が内側のループになります。

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)
  }
}

3つ以上のジェネレーター

ジェネレーターはいくつでも積み重ねられます。3つのジェネレーターを使うと、3重にネストしたループになり、タプルや計算結果を生成できます。

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)
  }
}

古典的なピタゴラス数の例

有名な用途の一つに、ピタゴラス数の組を見つける処理があります。3つの数を反復し、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)
  }
}

糖衣構文展開の仕組み

複数のジェネレーターは、最後にmapを置いた、ネストしたflatMapの呼び出しになります。

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

ジェネレーターが1つ増えるたびに、ネストの階層が1つ増えます。

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)
  }
}

ジェネレーターとガードの組み合わせ

ジェネレーターとガードは自由に交互に配置できます。2つのジェネレーターの間に置いたガードは、内側のループが実行される前に外側の値を取り除きます。

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)
  }
}

ネストしたコレクションを平坦化する

コレクションのコレクションを扱う場合、2つのジェネレーターで自然に平坦化できます。最初のジェネレーターが外側のコレクションを反復し、2番目が各内側のコレクションを反復します。

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"))
  }
}

サイズの異なるコレクションを組み合わせる

ジェネレーターの長さをそろえる必要はありません。それぞれから1つずつ要素を選んだ、すべての組み合わせが生成されます。

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)は、タプルをいくつ生成しますか?

まとめ

複数のジェネレーターについて学びました。

  • 各<-行が、ネストした反復の階層を1つ追加します
  • 組み合わせると直積が生成されます
  • 内側のジェネレーターは、外側で束縛された値に依存できます
  • ネストしたflatMapと最後のmapに糖衣構文展開されます
  • ジェネレーターの順序によって結果の並びが変わります

よくある質問

「複数のジェネレーター」レッスンは無料ですか?

はい。「複数のジェネレーター」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。

「複数のジェネレーター」で何を学びますか?

ネストした反復処理です。 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン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に戻る