ScalaでVectorを選択する必要があるのはいつですか?


200

VectorScalaコレクションパーティーに遅れたようで、影響力のあるすべてのブログ投稿はすでに残されていました。

Java ArrayListではデフォルトのコレクションです-私は使用するかもしれませんLinkedListが、アルゴリズムを熟考し、最適化するのに十分注意した場合のみです。Scala Vectorでは、デフォルトとしてを使用する必要がありますSeqか、それともList実際に適切な場合に解決する必要がありますか?


1
ここで私が言っているのは、JavaでList<String> l = new ArrayList<String>()Scalaブログを作成するとしたら、永続的なコレクションの良さを得るために誰もがListを使用していると信じ込ませることだと思います。
ダンカンマックレガー、

9
@Debilski:それが何を意味するのだろうと思います。REPLで入力Listするとaが表示さSeq()れます。
missingfaktor 2011

1
うーん、まあ、それはドキュメントでそう言っています。多分これはにのみ当てはまりますIndexedSeq
Debilski、2011

1
のデフォルトの具象タイプに関するコメントはSeq3年以上前のものです。Scala 2.11.4(およびそれ以前)のデフォルトの具象型はSeqですList
Mark Canlas、2014年

3
ランダムアクセスの場合、ベクトルの方が適しています。頭、尾のアクセスについては、リストが優れています。map、filter、vectorなどの一括操作の場合、ベクトルはチャンクとして32要素で構成されますが、リストは相互にポインタを持つ要素で構成されているため、これらの要素が互いに近いという保証はありません。
johnsam 2016

回答:


280

原則として、デフォルトではを使用しVectorます。ほとんどすべてのものよりも高速でList、サイズが大きいシーケンスではメモリ効率が高くなります。他のコレクションと比較したベクターの相対的なパフォーマンスに関するこのドキュメントを参照してください。と行くにはいくつかの欠点があります。具体的には:Vector

  • 先頭での更新はList(あなたが思うほどではないが)より遅い

Scala 2.10以前のもう1つの欠点は、パターンマッチングのサポートがの方が優れていることですListが、これは2.10で一般化さ+:れた:+エクストラクタで修正されました。

この問題に取り組むためのより抽象的な代数的な方法もあります。概念的にどのようなシーケンスを持っていますか?また、概念的には何を使っていますか?を返す関数が表示された場合Option[A]、その関数にはドメインにいくつかの穴がある(つまり部分的である)ことがわかります。これと同じロジックをコレクションに適用できます。

typeのシーケンスがある場合List[A]、2つのことを効果的に主張しています。まず、私のアルゴリズム(およびデータ)は完全にスタック構造になっています。第二に、私がこのコレクションでやろうとしていることは、完全なO(n)トラバーサルであると断言します。これら2つは本当に密接に関係しています。逆に、何かタイプがある場合Vector[A]、私が主張している唯一のことは、データが明確に定義された順序と有限の長さを持っていることです。したがって、アサーションはで弱くなりVector、これにより柔軟性が高まります。


2
2.10はしばらくの間リリースされましたが、リストパターンのマッチングはベクターよりも優れていますか
Tim Gautier

3
リストのパターンマッチングはもはや良くありません。実際、それは全く逆です。たとえば、頭と尾を取得するには、case head +: tailまたはを実行しcase tail :+ headます。空と照合するにはcase Seq()、次のようにします。あなたが必要とするすべては、より汎用性よりもAPIでありListさん
甲斐Sellgren

List一重リンクリストで実装されています。VectorJavaのようなものを実装していますArrayList
Josiah Yoder 2015

6
@JosiahYoder ArrayListのように実装されていません。ArrayListは、動的にサイズ変更される配列をラップします。ベクトルはトライです。キーは値のインデックスです。
John Colanduoni、2015

1
謝罪します。詳細についてはあいまいなWebソースを使用していました。以前のステートメントを修正する必要がありますか?それとも悪い形ですか?
Josiah Yoder

93

まあ、Listアルゴリズムをだけで実装できる場合、は非常に高速になる可能性があります::headそしてtail。つい最近、オブジェクトレッスンを受けました。Java ではsplit、のList代わりにを生成しArrayて、それ以外のものではこれを打ち負かすことができなかったときです。

ただし、List根本的な問題があります。並列アルゴリズムでは動作しません。List効率的な方法でa を複数のセグメントに分割したり、連結したりすることはできません。

並列処理をより適切に処理できる他の種類のコレクションがあります-そして Vector、それらの1つです。Vector局所性も優れてListいますが、そうではありません。これは、一部のアルゴリズムにとって真のプラスとなります。

したがって、他のコレクションのいずれかを望ましいものにする特定の考慮事項がない限り、すべてを考慮することがVector最善の選択です。たとえば、Stream、あなたが遅延評価とキャッシングをしたい場合は、(Iterator高速ですが、キャッシュされません)、またはList場合アルゴリズムは、私が述べた操作で自然に実装されます。

ちなみに、使用することが好ましいSeqか、IndexedSeqあなたはAPIの特定の部分を(のような場合を除きListさん::)、あるいはGenSeqあるいはGenIndexedSeq場合、あなたのアルゴリズムを並列に実行することができます。


3
答えてくれてありがとう。「地域性が高い」とはどういう意味ですか?
Ngoc Dao 2012

10
@ngocdaothanhこれは、データがメモリ内で密接にグループ化されることを意味し、必要なときにデータがキャッシュに入れられる可能性が向上します。
ダニエルC.ソブラル2012

1
@ user247077はい、私が言及した詳細を考えると、リストはパフォーマンスでベクターを上回る可能性があります。また、ベクトルのすべてのアクションが償却されるわけではありませんO(1)。実際、不変のデータ構造(この場合)では、どちらかの端での代替の挿入/削除は、まったく償却されません。その場合、常にベクターをコピーするため、キャッシュは役に立ちません。
ダニエルC.ソブラル2014

1
@ user247077 VectorScalaの不変のデータ構造であることに気付いていないのでしょうか?
ダニエルC.ソブラル2014

1
@ user247077追加のコストを下げるために内部で変更可能なものを含む、それよりもはるかに複雑ですが、不変のリストの最適なシナリオであるスタックとして使用すると、リンクされたリストと同じメモリ特性を持つことになりますが、はるかに大きなメモリ割り当てプロファイル。
ダニエルC.ソブラル

29

ここでのステ​​ートメントのいくつかは、特にScalaのimmutable.VectorがArrayListのようなものであるという考えを混乱させたり、さらには間違っています。ListとVectorはどちらも不変で永続的な(つまり、「変更されたコピーを取得するのに安価」)データ構造です。変更可能なデータ構造の可能性があるため、適切なデフォルトの選択はありませんが、アルゴリズムの動作に依存します。Listは単一リンクリストですが、Vectorはbase-32整数トライです。つまり、32度のノードを持つ一種の検索ツリーです。この構造を使用すると、Vectorは最も一般的な操作をかなり高速に、つまりO(log_32( n))。これは、先頭/末尾のプリペンド、追加、更新、ランダムアクセス、分解で機能します。順次の反復は線形です。一方、リストは、線形反復と一定時間プリペンド、ヘッド/テールの分解を提供します。

これは、ほとんどすべての場合でVectorがListの優れた代替品のように見えるかもしれませんが、関数プログラムのシーケンスに対するプリペンド、分解、および反復は、しばしば重要な操作であり、これらの操作の定数は、Vectorのための(はるかに)高いですより複雑な構造に。私はいくつかの測定を行ったので、反復はリストの約2倍、プリペンドはリストの約100倍、ヘッド/テールの分解はリストの約10倍、トラバーサブルからの生成はベクトルの約2倍高速です。(これはおそらく、要素を1つずつ追加または追加する代わりにビルダーを使用して構築するときに、Vectorが32要素の配列を一度に割り当てることができるためです)。

では、どのデータ構造を使用すればよいでしょうか?基本的に、4つの一般的なケースがあります。

  • マップ、フィルター、フォールドなどの操作でシーケンスを変換する必要があるだけです。基本的には問題ではありません。アルゴリズムを一般的にプログラムする必要があり、並列シーケンスを受け入れることで利益を得ることもできます。順次操作の場合、リストはおそらく少し高速です。しかし、最適化する必要がある場合は、ベンチマークする必要があります。
  • 多くのランダムアクセスとさまざまな更新が必要なため、ベクターを使用する必要があります。リストが非常に遅くなります。
  • 古典的な機能的な方法でリストを操作し、再帰的な分解によってプリペンドと反復によってリストを構築します。リストを使用すると、ベクトルは10〜100倍以上遅くなります。
  • 基本的に命令型であり、リストでランダムアクセスを実行する、パフォーマンスクリティカルなアルゴリズムがあります。たとえば、配置されたクイックソートのように、ローカルでArrayBufferなどの命令型データ構造を使用し、そこからデータをコピーします。

24

不変コレクションの場合、シーケンスが必要な場合、主な決定は、とのどちらを使用するIndexedSeqかでありLinearSeq、これによりパフォーマンスがさまざまに保証されます。IndexedSeqは、要素の高速ランダムアクセスと高速長さ操作を提供します。LinearSeqは、最初の要素への高速アクセスのみを提供しますheadが、tail操作も高速です。(Seqドキュメントから取得。)

IndexedSeqあなたは通常、選ぶだろうVectorRangesとWrappedStringsもIndexedSeqです。

LinearSeqあなたのために、あなたは通常、Listまたはその怠惰な同等物を選びますStream。他の例はQueuesとStacksです。

したがって、Java用語では、ArrayListScala VectorLinkedList同様に、Scala と同様に使用されListます。ただし、Scalaでは、ListよりもVectorよりもListを使用する傾向があります。Scalaは、マッピング、折りたたみ、反復など、シーケンスのトラバーサルを含む関数をはるかにサポートしているためです。これらの関数を使用して、リストをリストとして操作する傾向があります。個々の要素にランダムにアクセスするのではなく、全体。


しかし、Vectorの反復がListの反復よりも速く、フォールドなどもマップできる場合、一部の特殊なケース(本質的にはListに特化したすべてのFPアルゴリズム)を除いて、Listは本質的にレガシーであるようです。
ダンカンマクレガー

@Duncan Vectorの反復が速いと聞いたことがありますか?まず、現在のインデックスを追跡して更新する必要があります。リンクリストを使用する必要はありません。リスト関数を「特殊なケース」とは呼びません。これらは関数型プログラミングの基本です。それらを使用しないことは、forまたはwhileループなしでJavaをプログラミングしようとするようなものです。
Luigi Plinge、2011

2
私はVectorの反復より速いと確信していますが、誰かがそれを確実にするためにベンチマークする必要があります。
Daniel Spiewak、2011

私は(?)の要素Vectorが32のグループのRAM に物理的に一緒に存在し、CPUキャッシュにより完全に適合すると思います...キャッシュミスが少ない
リッチー

2

多くのランダムアクセスとランダムミューテーションを伴う状況では、a Vector(または- ドキュメントで言うように-aSeq)が適切な妥協案のようです。これは、パフォーマンス特性が示唆することでもあります。

また、Vector完全なオブジェクトに対してコピーオンライトを実行する必要がないため、データの重複が少ない分散環境でクラスが適切に機能するようです。(参照:http : //akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures


1
学ぶことはたくさんあります...デフォルトのSeqであるベクターは何を意味しますか?Seq(1、2、3)と書いた場合、Vector [Int]ではなくList [Int]を取得します。
Duncan McGregor

2
ランダムアクセスがある場合は、を使用しIndexedSeqます。これもですがVector、それは別の問題です。
ダニエルC.ソブラル2011

@DuncanMcGregor:VectorはIndexedSeqを実装するデフォルトですSeqSeq(1, 2, 3)LinearSeqを使用して実装されるListです。
pathikrit

0

不変にプログラミングしていて、ランダムアクセスが必要な場合は、Seqが適しています(実際に頻繁に行うSetが必要な場合を除きます)。それ以外の場合は、操作を並列化できないことを除いて、Listは適切に機能します。

不変のデータ構造が必要ない場合は、ArrayListと同等のScalaであるため、ArrayBufferを使用してください。


私は不変で永続的なコレクションの領域に固執しています。私のポイントは、ランダムアクセスが必要ない場合でも、VectorがListを効果的に置き換えているということです。
ダンカンマックレガー

2
ユースケースによって多少異なります。ベクトルはよりバランスが取れています。反復はリストよりも速く、ランダムアクセスははるかに高速です。ビルダーで実行できるフォールドからの一括更新でない限り、リストの先頭だけではないため、更新は遅くなります。とは言っても、Vectorは用途が広いため、デフォルトの選択として最適だと思います。
Joshua Hartman

私の質問の核心に到達すると思います-ベクトルは非常に優れているので、例が通常Listを表示する場合にも使用できます。
ダンカンマックレガー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.