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


13

List(:+)の追加操作の実行時間は、のサイズに比例して増加することを読みましたList

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

私の観点からは、先頭と末尾の両方がO(1)である必要があります。

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


2
「正当」の定義に依存します。Scalaは不変のデータ構造、ユビキタスな匿名リスト、機能構成などに深く関わっています。デフォルトのリスト実装(リスト末尾への余分な可変ポインターなし)は、そのスタイルに適しています。より強力なリストが必要な場合は、少なくとも標準のコンテナとほとんど区別できない独自のコンテナを作成するのは非常に簡単です。
キリアンフォス

1
クロスサイト関連-Scalaのリストの最後に要素を追加します - 要素の性質について少し説明します。scalaのリストは不変であるように見えるため、コピーする必要があります。これはO(N)です。

多くの利用可能な可変データ構造の1つ、またはScalaが提供するO(1)追加時間(ベクトル)を使用した不変データ構造を使用できます。List[T]純粋な関数型言語で使用するのと同じ方法で使用していることを前提としています-一般に、解体と前置を使用して頭から作業します。
KChaloux

3
先頭に追加すると、新しいヘッドの次のノードポインターが既存の不変リストに配置されます。これは変更できません。Thats O(1)。

1
純粋なFPでのデータ構造の複雑性測定の一般的なトピックに関する独創的な研究と分析については、後に本として出版された岡崎の論文を読んでください。FPを学習している人にとって、FPでのデータの整理について考える方法を理解することは高く評価され、非常に良い読み物です。また、よく書かれており、読みやすくて読みやすいので、品質の高いテキストです。
ジミー・ホッファ

回答:


24

コメントを少し拡大します。からのList[T]データ構造scala.collection.immutableは、より純粋に機能的なプログラミング言語の不変リストが機能するように最適化されています。プリペンド時間は非常に高速であり、アクセスのほとんどすべてについて頭で作業することを想定しています。

不変リストは、リンクリストを一連の「コンスセル」としてモデル化するという事実により、非常に高速のプリペンド時間を持ちます。セルは、単一の値と、次のセルへのポインターを定義します(古典的な単一リンクリストスタイル):

Cell [Value| -> Nil]

リストの先頭に追加する場合、実際には1つの新しいセルを作成するだけで、既存のリストの残りの部分は次のように示されます。

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

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

これは、リストの再帰的な作業に非常に役立ちます。独自のバージョンを定義したとしましょう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。または、Vectorfromのscala.collection.immutable追加時間は高速です。


今分かります!それは完全に理にかなっています。
DPM

私はScalaを知りませんが、それはelse無限ループではありませんか?私はそれが次のようなものであるべきだと思うx::deleteIf(xs)(f)
svick

@svick Uh ...はい。はい、そうです。私はすぐにそれを書いて、コードを検証しませんでした。なぜなら、私は:p(今修正されるべきです)に行く会議があったからです
-KChaloux

@Jubbat この種類のリストheadtail使用したアクセスは非常に高速であるため、ハッシュベースのマップや配列を使用するよりも高速であるため、再帰関数に最適なタイプです。これは、リストは、ほとんどの関数型言語(例えばハスケルまたはスキーム)でコアタイプある理由の一つである
itsbruce

素晴らしい答え。おそらく、「追加するのではなく、先頭に追加する必要がある」という単純なTL; DRを追加します(ほとんどの開発者がListsと追加/先頭に追加する基本的な前提を明確にするのに役立つかもしれません)。
ダニエルB
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.