web-dev-qa-db-ja.com

Scalaのリストに追加するのはなぜO(n)時間の複雑さがあるのですか?

List(:+)の追加操作の実行時間は、Listのサイズに比例して増加することを私は読んだだけです。

Listへの追加はかなり一般的な操作のようです。これを行う慣用的な方法は、なぜコンポーネントを前に付けてからリストを逆にする必要があるのですか?また、実装はいつでも変更される可能性があるため、設計の失敗になることはありません。

私の観点からは、前置と追加の両方がO(1)である必要があります。

これには正当な理由がありますか?

13
DPM

コメントを少し広げます。 List[T]データ構造、scala.collection.immutableは、より純粋な関数型プログラミング言語の不変リストが機能するように最適化されています。 prepend回と非常に高速で、ほとんどすべてのアクセスで頭を操作することが想定されています。

不変リストは、リンクリストを一連の「コンスセル」としてモデル化するため、プリペンド時間が非常に速くなります。セルは単一の値と次のセルへのポインター(クラシックシングルリンクリストスタイル)を定義します。

Cell [Value| -> Nil]

リストの先頭に追加すると、実際には1つの新しいセルが作成され、残りの既存のリストがポイントされます。

Cell [NewValue| -> [Cell[Value| -> Nil]]

リストは不変なので、実際にコピーせずにこれを実行しても安全です。古いリストが変更され、新しいリストのすべての値が無効になる危険はありません。ただし、妥協案として、リストのendへの可変ポインタを使用する機能を失います。

これは、リストで再帰的に作業するのに非常に適しています。 filterの独自のバージョンを定義したとしましょう:

def deleteIf[T](list : List[T])(f : T => Boolean): List[T] = list match {
  case Nil => Nil
  case (x::xs) => f(x) match {
    case true => deleteIf(xs)(f)
    case false => x :: deleteIf(xs)(f)
  }
}

これは、リストの先頭から排他的に機能する再帰関数であり、::抽出によるパターンマッチングを利用します。これは、Haskellのような言語でよく目にするものです。

本当に高速なアペンドが必要な場合は、Scalaから、選択可能な多数の変更可能で不変のデータ構造が提供されます。変更可能な側では、ListBufferを調べることもできます。または、Vector from scala.collection.immutableの追加時間が速い。

24
KChaloux