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年

Answers:


321

遗产。List最初被定义为具有功能性语言的外观:

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,以便您可以++用来连接任意两个集合-甚至迭代器。但是,除了一两个不推荐使用的列表之外,List必须保留其原始运算符。


19
因此,它是为了避免最佳实践:::赞成++呢?还用+:代替::
路易吉·普林格

37
::由于模式匹配而非常有用(请参见Daniel的第二个示例)。您无法做到这一点+:
范式

1
@Luigi如果使用List代替Seq,则最好使用惯用List方法。另一方面,如果您愿意的话,将很难更改为其他类型。
Daniel C. Sobral

2
我发现同时具有List惯用操作(如:::::)和其他集合所共有的更通用的操作很好。我不会从该语言中删除任何一项操作。
乔治

21
@paradigmatic Sc​​ala 2.10具有:++:对象提取器。
0__

97

始终使用:::。有两个原因:效率和类型安全。

效率

x ::: y ::: z比更快x ++ y ++ z,因为:::是正确的关联。x ::: y ::: z被解析为x ::: (y ::: z),在算法上比快(x ::: y) ::: z(后者需要O(| x |)多步)。

类型安全

:::您只能串联两个Lists。使用,++您可以将任何集合追加到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
Luigi Plinge 2015年

3
请解释一下,为什么左联想级联需要更多的O(x)步骤。我认为他们俩都为O(1)工作。
pacman

6
@pacman列表是单链接的,要将一个列表附加到另一个列表,您需要制作第一个列表的副本,并在其末尾附加第二个列表。因此,相对于第一列表中元素的数量,串联为O(n)。第二个列表的长度不会影响运行时,因此最好将一个长列表附加到一个短列表上,而不是将一个短列表附加到一个长列表上。
puhlen

1
@pacman Scala的列表是不可变的。这就是为什么我们不能在进行串联时仅替换最后一个链接。我们必须从头开始创建一个新列表。
ZhekaKozlov '02

4
@pacman的复杂度始终与x和的长度成线性关系yz在任何情况下都不会迭代,因此对运行时间没有影响,这就是为什么最好将长列表附加到短列表上,而不是反过来)渐进复杂性并不能说明全部问题。 x ::: (y ::: z)迭代y并追加z,然后迭代x并追加的结果y ::: zx并且y都被迭代一次。 (x ::: y) ::: z迭代x和追加y,然后迭代x ::: y和追加的结果zy仍然被迭代一次,但是x在这种情况下被迭代了两次。
puhlen

84

:::仅适用于列表,而++可与任何遍历一起使用。在当前实现(2.9.0)中,如果参数也是a ++:::则回退List


4
因此,使用:::和++与list一起使用非常容易。这可能会使代码/样式混乱。
ses 2013年

24

另一个不同点是第一句被解析为:

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

而第二个示例解析为:

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

因此,如果您使用宏,则应多加注意。

此外,++对于两个列表,正在调用:::但具有更多开销,因为它要求隐式值在列表之间创建一个生成器。但是从这个意义上说,微基准测试并没有提供任何有用的功能,我想编译器会优化此类调用。

预热后的微基准测试。

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

正如Daniel C. Sobrai所说,您可以使用++,将任何集合的内容附加到列表中,而使用:::只能将列表连接起来。


20
请发布您的过于简单化的微基准测试,我会对其进行投票。
米凯尔·梅耶
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.