Scalaリストの連結、::: vs ++


362

Scalaのリストの連結:::との違いはあります++か?

scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> res0 == res1
res2: Boolean = true

以下からのドキュメントのように見える++のに対し、より一般的で:::あるList固有。後者は他の関数型言語で使用されているため提供されていますか?


4
また:::始まるすべてのメソッドのような接頭辞演算子である:
ベン・ジャクソン

3
回答は、ScalaがリストとScalaの演算子の均一性を中心に進化した方法(または後者の欠如)をかなり詳しく示しています。Scala学習者の時間を混乱させ、時間を浪費するほど細かい特徴が非常に単純なものにあることは少し残念です。2.12で横ばいになることを願っています。
Matanster 2015年

回答:


321

レガシー。リストはもともと機能的な言語に見えるように定義されていました:

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists

list match {
  case head :: tail => "non-empty"
  case Nil          => "empty"
}

もちろん、Scalaはアドホックな方法で他のコレクションを進化させました。2.8がリリースされたとき、コレクションは最大限のコード再利用と一貫したAPIのために再設計されたので、を使用++して、任意の 2つのコレクション(さらにはイテレータ)を連結することができます。ただし、リストは、廃止された1つまたは2つを除いて、元の演算子を保持する必要があります。


19
それで:::++今を支持して避けるのがベストプラクティスですか?の+:代わりにも使用し::ますか?
Luigi Plinge 2011

37
::パターンマッチングのために便利です(ダニエルの2番目の例を参照)。あなたはそれを行うことはできません+:
パラダイム的な

1
@LuigiのList代わりにを使用している場合はSeq、慣用的Listなメソッドを使用することもできます。一方、別のタイプに変更したい場合は、変更するのが難しくなります。
ダニエルC.ソブラル2011

2
Listの慣用的な操作(::およびのような:::)と、他のコレクションに共通のより一般的な操作の両方があることは良いことです。言語からどちらの操作も削除しません。
ジョルジオ

21
@paradigmatic Sc​​ala 2.10には:++:オブジェクト抽出機能があります。
0__ 2013年

97

常に使用してください:::。効率とタイプセーフティの2つの理由があります。

効率

x ::: y ::: zは正しい結合x ++ y ++ zであるため、よりも高速です:::x ::: y ::: zはとして解析されますx ::: (y ::: z)。これはアルゴリズム的に高速です(x ::: y) ::: z(後者はO(| x |)より多くのステップが必要です)。

タイプセーフ

では、:::連結できるのは2つListのだけです。では++、あなたの任意のコレクションに追加することができListひどいです、:

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)

++と混同するのも簡単+です:

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab

9
2つだけリストを連結しても差はありませんが、3つ以上の場合は良い点があり、簡単なベンチマークで確認しました。ただし、効率が心配な場合x ::: y ::: zは、に置き換えてくださいList(x, y, z).flattenpastebin.com/gkx7Hpad
ルイジPlinge

3
左連想連結でより多くのO(x)ステップが必要な理由を説明してください。どちらもO(1)で機能すると思いました。
pacman 2017年

6
@pacmanリストは単独でリンクされます。あるリストを別のリストに追加するには、最後に2番目のリストが添付されている最初のリストのコピーを作成する必要があります。したがって、連結は最初のリストの要素数に関してO(n)です。2番目のリストの長さはランタイムに影響しないため、短いリストを長いリストに追加するよりも、長いリストを短いリストに追加することをお勧めします。
プーレン

1
@pacman Scalaのリストは不変です。そのため、連結を行うときに最後のリンクを単に置き換えることはできません。新しいリストを最初から作成する必要があります。
ZhekaKozlov 2017

4
複雑@pacmanいつもの長さの直線WRTであるxyzどのような場合に反復されることはありませんと、それは他の方法で回避よりも、短いものに長いリストを追加する方が良いでしょう、なぜこれは、実行時には影響しません)が、漸近的な複雑さは、全体の話をしません。 x ::: (y ::: z)を反復yして追加しz、次にxの結果を反復して追加しy ::: zます。 xそして、yの両方が一度反復されます。 (x ::: y) ::: z反復xして追加しy、次に結果x ::: yと追加を反復しzます。 yはまだ1回繰り返されますがx、この場合は2回繰り返されます。
プーレン

84

:::リストでのみ機能しますが、++すべての走査可能オブジェクトで使用できます。現在の実装(2.9.0)では、引数もの場合は++フォールバックします。:::List


4
したがって、:::と++の両方を使用してリストを操作するのは非常に簡単です。それは潜在的にコード/スタイルに混乱をもたらす可能性があります。
2013年

24

異なる点は、最初の文が次のように解析されることです。

scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)

一方、2番目の例は次のように解析されます。

scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)

したがって、マクロを使用している場合は注意が必要です。

さらに、++2つのリストが呼び出されますが、リスト:::からリストへのビルダーを持つように暗黙の値を要求しているため、オーバーヘッドが大きくなります。しかし、マイクロベンチマークはその意味で有用なものを何も証明していませんでした。コンパイラがそのような呼び出しを最適化していると思います。

ウォームアップ後のマイクロベンチマーク。

scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100

scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46

ダニエル・C. Sobraiが言ったように、あなたが使用してリストに任意のコレクションのコンテンツを追加することができます++とのに対し、:::あなただけのCONCATENATEリストすることができます。


20
過度に単純化していないマイクロベンチマークを投稿してください。投票します。
ミカエル・メイヤー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.