web-dev-qa-db-ja.com

Scalaでベクターを選択する必要があるのはいつですか?

VectorはScalaコレクションパーティーに遅れており、影響力のあるすべてのブログ投稿がすでに残っているようです。

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

182
Duncan McGregor

一般的なルールとして、デフォルトではVectorを使用します。すべてのalmostに対してListよりも高速であり、自明ではないサイズのシーケンスではよりメモリ効率が高くなります。こちらをご覧ください ドキュメント 他のコレクションと比較したベクターの相対的なパフォーマンス。 Vectorを使用することにはいくつかの欠点があります。具体的には:

  • 頭の更新はListよりも遅いです(ただし、考えているほどではありません)

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

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

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

257
Daniel Spiewak

アルゴリズムを::List、およびheadのみで実装できる場合、tailは非常に高速になります。 Javaのsplitの代わりにListを生成してJavaのArrayを打ち負かしたとき、私はそれに関するオブジェクトレッスンを受けました。

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

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

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

ところで、特定のAPI(Seq::など)、さらにはIndexedSeqが必要な場合を除き、ListまたはGenSeqを使用することをお勧めします。またはGenIndexedSeqは、アルゴリズムを並行して実行できる場合。

87

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

IndexedSeqの場合、通常はVectorを選択します。 RangesおよびWrappedStringsもIndexedSeqです。

LinearSeqの場合、通常はListまたはその遅延対応のStreamを選択します。他の例は、QueuesおよびStacksです。

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

ここでのステ​​ートメントの一部は、特にScalaのimmutable.VectorがArrayListのようなものであるという考えが紛らわしい、または間違っています。リストとベクターはどちらも不変で永続的な(つまり、「変更されたコピーを取得するための安い」)データ構造です。可変のデータ構造用である可能性があるため、合理的なデフォルトの選択はありませんが、それはむしろアルゴリズムが何をしているかに依存します。リストは単リンクリストであり、ベクターはベース32の整数トライです。つまり、32次のノードを持つ一種の検索ツリーです。この構造を使用すると、ベクターはほとんどの一般的な操作を合理的に高速に、つまりO(log_32( n))。これは、先頭/末尾のプリペンド、アペンド、更新、ランダムアクセス、分解に有効です。順次の反復は線形です。一方、リストは、線形反復と一定時間の前置、頭/尾の分解を提供します。他のすべては、一般に線形時間を要します。

これは、ほとんどすべての場合にベクターがリストの優れた代替品のように見えるかもしれませんが、多くの場合、プリペンド、分解、および反復が機能プログラムのシーケンスの重要な操作であり、これらの操作の定数はベクターのより複雑な構造に。いくつかの測定を行ったので、リストの反復は約2倍、リストのprependは約100倍、リストのhead/tailの分解は約10倍、traversableからの生成はベクトルの約2倍高速です。 (これはおそらく、要素を1つずつ追加または追加する代わりに、Builderを使用して構築するときにVectorが一度に32要素の配列を割り当てることができるためです)。もちろん、リストでは線形の時間を必要としますが、ベクターでは事実上一定の時間(ランダムアクセスまたは追加)を行うすべての操作は、大きなリストでは非常に遅くなります。

では、どのデータ構造を使用する必要がありますか?基本的に、4つの一般的なケースがあります。

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

多数のランダムアクセスとランダムな突然変異を伴う状況では、Vector(または docs say – a Seq)が適切な妥協案のようです。これも パフォーマンス特性 が示唆するものです。

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

2
Debilski

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

不変のデータ構造が不要な場合は、ArrayListと同等のScalaであるため、ArrayBufferを使用します。

0
Joshua Hartman