web-dev-qa-db-ja.com

zipWith(複数のSeqにマッピング)in Scala

私が持っているとしましょう

val foo : Seq[Double] = ...
val bar : Seq[Double] = ...

そして、baz(i)= foo(i)+ bar(i)であるseqを作成したいと思います。これを行うために私が考えることができる1つの方法は

val baz : Seq[Double] = (foo.toList Zip bar.toList) map ((f: Double, b : Double) => f+b)

ただし、これは醜く非効率的だと感じます。両方のシーケンスをリスト(レイジーリストで爆発する)に変換し、このタプルの一時リストを作成して、その上にマップしてGCする必要があります。ストリームが怠惰な問題を解決するかもしれませんが、いずれにせよ、これは不必要に醜いように感じます。 LISPでは、map関数は複数のシーケンスにマップします。私は書くだろう

(mapcar (lambda (f b) (+ f b)) foo bar)

また、一時的なリストはどこにも作成されません。 Scalaにmap-over-multiple-lists関数がありますか、それともZipと組み合わせてこれを行うための本当に「正しい」方法ですか?

34
bsdfish

必要な関数はzipWithと呼ばれますが、標準ライブラリの一部ではありません。 2.8になります(更新:どうやらそうではありません、コメントを参照してください)。

foo zipWith((f: Double, b : Double) => f+b) bar

このTracチケット を参照してください。

16
Alexey Romanov

In Scala 2.8:

val baz = (foo, bar).zipped map (_ + _)

また、3つ以上のオペランドに対して同じように機能します。つまりその後、次のようにフォローアップできます。

(foo, bar, baz).zipped map (_ * _ * _)
82
Martin Odersky

さて、それ、Zipの欠如は、Scalaの2.7Seqの欠陥です。 Scala 2.8はよく考えられたコレクションデザインを持っており、2.7に存在するコレクションが生まれたアドホックな方法を置き換えます(すべてが一度に作成されたわけではなく、統一されたデザインであることに注意してください) )。

ここで、一時的なコレクションの作成を避けたい場合は、Scala 2.7で "projection"を使用するか、Scala 2.8で "view"を使用する必要があります。特定の命令、特にmap、flatMap、filterが厳密ではないコレクションタイプ。OnScala 2.7、リストの射影はストリームです。OnScala 2.8、SequenceのSequenceViewがありますが、SequenceのすぐそこにzipWithがあり、それは必要ありません。

とは言っても、前述のように、JVMは一時的なオブジェクトの割り当てを処理するように最適化されており、サーバーモードで実行している場合、実行時の最適化は驚異的です。したがって、時期尚早に最適化しないでください。実行される条件でコードをテストします。サーバーモードで実行する予定がない場合は、コードが長時間実行されることが予想される場合は考え直し、必要に応じて、いつ、どこで、最適化してください。

[〜#〜]編集[〜#〜]

Scala 2.8で実際に利用できるようになるのはこれです:

(foo,bar).zipped.map(_+_)
10

レイジーリストはリストのコピーではなく、単一のオブジェクトのようなものです。怠惰なZip実装の場合、次のアイテムを要求されるたびに、2つの入力リストのそれぞれからアイテムを取得し、それらからタプルを作成します。次に、パターンマッチングを使用してタプルを分解します。あなたのラムダ。

したがって、入力リストの操作を開始する前に、入力リスト全体の完全なコピーを作成する必要はありません。つまり、JVMで実行されているアプリケーションと非常によく似た割り当てパターンになります。つまり、JVMが処理するように最適化された、非常に短命ですが小さな割り当てがたくさんあります。

更新:明確にするために、リストではなくストリーム(レイジーリスト)を使用する必要があります。 Scalaのストリームには怠惰な方法で機能するZipがあるため、物事をリストに変換するべきではありません。

理想的には、アルゴリズムは2つの無限ストリームを爆破することなく処理できる必要があります(もちろん、foldingを実行しないと仮定します)。 、ただし、ストリームを読み取って生成するだけです)。

4

同様の課題に直面したとき、私は次のポン引きをIterablesに追加しました。

implicit class IterableOfIterablePimps[T](collOfColls: Iterable[Iterable[T]]) {
  def mapZipped[V](f: Iterable[T] => V): Iterable[V] = new Iterable[V] {
    override def iterator: Iterator[V] = new Iterator[V] {
      override def next(): V = {
        val v = f(itemsLeft.map(_.head))
        itemsLeft = itemsLeft.map(_.tail)
        v
      }

      override def hasNext: Boolean = itemsLeft.exists(_.nonEmpty)

      private var itemsLeft = collOfColls
    }
  }
}

これがあれば、次のようなことができます。

val collOfColls = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
collOfColls.mapZipped { group =>
  group // List(1, 4, 7), then List(2, 5, 8), then List(3, 6, 9)
}

Iterabletailが繰り返し呼び出されるため、ネストされたheadとして渡されるコレクションタイプを慎重に検討する必要があることに注意してください。したがって、理想的には、Iterable[List]または other コレクションを高速のtailおよびheadで渡す必要があります。

また、このコードは、同じサイズのネストされたコレクションを想定しています。それが私のユースケースでしたが、必要に応じてこれを改善できると思います。

1
Tvaroh

PDATE:この「回答」は実際に質問されている質問に対応していないことが(コメントで)指摘されています。この回答は、foobarのすべての組み合わせにマッピングされ、N x M要素、要求されたmin(M、N)の代わりに、これはwrong、しかしそれは良い情報なので後世のために残しました。


これを行う最良の方法は、flatMapmapと組み合わせることです。コードは言葉よりも雄弁です:

foo flatMap { f => bar map { b => f + b } }

これにより、期待どおりに単一のSeq[Double]が生成されます。このパターンは非常に一般的であるため、Scalaには、実際にはそれを実装する構文上の魔法が含まれています。

for {
  f <- foo
  b <- bar
} yield f + b

または、代わりに:

for (f <- foo; b <- bar) yield f + b

for { ... }構文は、これを行うための最も慣用的な方法です。必要に応じて、ジェネレーター句(b <- barなど)を引き続き追加できます。したがって、マップする必要があるのが突然threeSeqsになった場合、要件に合わせて構文を簡単にスケーリングできます(フレーズを作成するため)。

1
Daniel Spiewak