3

『Programming In Scala』の第23章で、著者は次のような例を示しています。

case class Book(title: String, authors: String*)
val books: List[Book] = // list of books, omitted here
// find all authors who have published at least two books

for (b1 <- books; b2 <- books if b1 != b2;
    a1 <- b1.authors; a2 <- b2.authors if a1 == a2)
yield a1

著者によると、これは次のように翻訳されます。

books flatMap (b1 =>
   books filter (b2 => b1 != b2) flatMap (b2 =>
      b1.authors flatMap (a1 =>
        b2.authors filter (a2 => a1 == a2) map (a2 =>
           a1))))

しかし、マップとフラットマップのメソッド定義(TraversableLike.scala)を調べると、それらはforループとして定義されていることがわかります。

   def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
    val b = bf(repr)
    b.sizeHint(this) 
    for (x <- this) b += f(x)
    b.result
  }

  def flatMap[B, That](f: A => Traversable[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
    val b = bf(repr)
    for (x <- this) b ++= f(x)
    b.result
  }

ええと、これは継続的にforeachに変換されてから、式ではなく構成であるwhileステートメントに変換されると思います。scalaにはfor構成がありません。これは、forが常に何かを生成することを望んでいるためです。

それで、私があなたと話したいのは、なぜScalaがこれを「翻訳のために」行うのかということです。books著者の例では4つのジェネレーターを使用しましたが、これは最終的に4レベルのネストされたforループに変換されます。これは、が大きい場合に非常に恐ろしいパフォーマンスになると思います。

Scalaは、この種の「シンタックスシュガー」の使用を推奨しています。フィルター、マップ、フラットマップを多用するコードを常に見ることができます。プログラマーは、あるループを別のループの中にネストしていることを忘れているようです。コードを少し短く見せます。あなたの考えは何ですか?

4

5 に答える 5

7

内包表記は、糖衣構文の変換のための糖衣構文であり、そのため、あらゆる種類の場所で役立ちます。その点で、Scalaでは同等のHaskellコンストラクトよりもはるかに冗長です(もちろん、Haskellはデフォルトでは厳密ではないため、Scalaのようにコンストラクトのパフォーマンスについて話すことはできません)。

また重要なのは、この構成により、実行されていることを明確に保ち、​​インデントの急速な拡大や不要なプライベートメソッドのネストを回避することです。

最終的な考慮事項に関しては、それが複雑さを隠しているかどうかにかかわらず、私はこれを仮定します:

for {
  b1 <- books
  b2 <- books
  if b1 != b2
  a1 <- b1.authors
  a2 <- b2.authors 
  if a1 == a2
} yield a1

何が行われているのかを確認するのは非常に簡単で、複雑さは明らかです。b^ 2 * a ^ 2(フィルターは複雑さを変更しません)、本の数と著者の数。ここで、Javaで同じコードを、深いインデントまたはプライベートメソッドを使用して記述し、コードの複雑さを簡単に確認してみてください。

だから、私見、これは複雑さを隠すことはありませんが、それどころか、それを明確にします。

mapあなたが言及する//定義に関してはflatMap、それらは他のクラスにfilter属していないので、適用されません。List基本的に、

for(x <- List(1, 2, 3)) yield x * 2

に翻訳されます

List(1, 2, 3) map (x => x * 2)

それはと同じことではありません

map(List(1, 2, 3), ((x: Int) => x * 2)))

これは、渡した定義がどのように呼び出されるかを示しています。mapちなみに、 onの実際の実装Listは次のとおりです。

def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
  val b = bf(repr)
  b.sizeHint(this) 
  for (x <- this) b += f(x)
  b.result
}
于 2010-11-18T22:55:46.643 に答える
6

理解しやすく、保守しやすいようにコードを書いています。次に、プロファイルを作成します。ボトルネックがある場合は、そこに注意を向けます。あなたが説明したようなものであれば、私は別の方法で問題を攻撃します。それまでは「砂糖」が大好きです。それは私に物事を書き出すか、それについて一生懸命考える手間を省きます。

于 2010-11-18T04:51:37.267 に答える
4

実際には6つのループがあります。フィルタ/フラットマップ/マップごとに1つのループ

フィルタ->マップのペアは、コレクションのレイジービューを使用して1つのループで実行できます(イテレータメソッド)

一般に、ttは、本に対して2つのネストされたループを実行してすべての本のペアを検索し、次に2つのネストされたループを実行して1つの本の著者が他の本の著者のリストにあるかどうかを検索します。

単純なデータ構造を使用すると、明示的にコーディングするときに同じことを行います。

そしてもちろん、ここでの例は、最も効率的なコードを書くのではなく、複雑な「for」ループを示すことです。たとえば、一連の作成者の代わりに、Setを使用して、交差点が空でないかどうかを確認できます。

for (b1 <- books; b2 <- books; a <- (b1.authors & b2.authors)) yield a
于 2010-11-18T05:19:53.553 に答える
2

2.8では、filter呼び出しがレイジーに変更されwithFilter、中間構造の構築を回避することに注意してください。フィルタからwithFilterに移動するためのガイドを参照してください。。

、および(および存在する場合は値の定義)forに変換される理由は、モナドの使用を容易にするためだと思います。mapflatMapwithFilter

一般に、実行している計算に4回のループが含まれる場合は、forループを使用しても問題ないと思います。計算をより効率的に実行でき、パフォーマンスが重要な場合は、より効率的なアルゴリズムを使用する必要があります。

于 2010-11-18T16:35:18.170 に答える
0

アルゴリズムの効率に関する@IttayDの回答に対する1つのフォローアップ。元の投稿(および本)のアルゴリズムは、ネストされたループ結合であることに注意してください。実際には、これは大規模なデータセットには効率的なアルゴリズムではなく、ほとんどのデータベースは代わりにここでハッシュ集計を使用します。Scalaでは、ハッシュ集計は次のようになります。

(for (book <- books;
      author <- book.authors) yield (book, author)
).groupBy(_._2).filter(_._2.size > 1).keys
于 2014-02-19T16:12:11.740 に答える