Scala 列表连接,::: vs ++

Posted

技术标签:

【中文标题】Scala 列表连接,::: vs ++【英文标题】:Scala list concatenation, ::: vs ++ 【发布时间】:2011-09-27 10:46:27 【问题描述】:

:::++ 在 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

从the documentation 看来,++ 更通用,而:::List 特定的。提供后者是因为它用于其他函数式语言吗?

【问题讨论】:

另外,::: 是一个前缀运算符,就像所有以 : 开头的方法一样 答案几乎描述了 Scala 围绕列表和 Scala 中的运算符一致性(或缺乏后者)演变的方式。有点不幸的是,这么简单的事情有这么长的细节,以混淆和浪费任何 Scala 学习者的时间。我希望它会在 2.12 中趋于平稳。 【参考方案1】:

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

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 所说,您可以使用 ++ 将任何集合的内容附加到列表中,而使用 ::: 您只能连接列表。

【讨论】:

请张贴您不太简单的微基准测试,我会投票赞成。【参考方案2】:

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

效率

x ::: y ::: zx ++ 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

【讨论】:

仅连接 2 个列表时,没有区别,但在 3 个或更多列表的情况下,您有一个好点,我通过快速基准确认了这一点。但是,如果您担心效率,则应将x ::: y ::: z 替换为List(x, y, z).flatten。 pastebin.com/gkx7Hpad 请解释一下,为什么左关联连接需要更多的 O(x) 步骤。我认为它们都适用于 O(1)。 @pacman 列表是单独链接的,要将一个列表附加到另一个列表,您需要复制第一个列表,并在末尾附加第二个列表。因此,相对于第一个列表中的元素数量,串联是 O(n)。第二个列表的长度不会影响运行时间,因此最好将长列表附加到短列表而不是将短列表附加到长列表。 @pacman Scala 的列表是不可变的。这就是为什么我们在进行连接时不能只替换最后一个链接。我们必须从头开始创建一个新列表。 @pacman 复杂度始终与xy 的长度成线性关系(z 在任何情况下都不会迭代,因此对运行时间没有影响,这就是为什么它更好的原因将一个长列表附加到一个短列表,而不是相反),但渐近复杂性并不能说明整个故事。 x ::: (y ::: z) 迭代 y 并附加 z,然后迭代 x 并附加 y ::: z 的结果。 xy 都被迭代一次。 (x ::: y) ::: z迭代x并追加y,然后迭代x ::: y的结果并追加zy 仍然迭代一次,但 x 在这种情况下被迭代两次。【参考方案3】:

遗产。 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 必须保留其原始运算符,除了一两个已弃用的运算符。

【讨论】:

那么现在最好避免::: 支持++ 吗?也使用+: 而不是::? :: 很有用,因为模式匹配(见丹尼尔第二个例子)。你不能用+: 我觉得最好同时拥有 List 惯用操作(如 :::::)和其他集合共有的更一般的操作。我不会从语言中删除任何一个操作。 @paradigmatic Sc​​ala 2.10 具有 :++: 对象提取器。 @samthebest 您可以在模式匹配中使用+:。没有理由在模式匹配或其他地方使用::,除非你有审美偏好。【参考方案4】:

::: 仅适用于列表,而 ++ 可用于任何可遍历的对象。在当前实现 (2.9.0) 中,如果参数也是 List++ 将回退到 :::

【讨论】:

因此很容易同时使用 ::: 和 ++ 处理列表。这可能会给代码/样式带来混乱。

以上是关于Scala 列表连接,::: vs ++的主要内容,如果未能解决你的问题,请参考以下文章

从连接表列表中选择唯一的列名

Mysql 使用逗号分隔列表 - 连接表

sql99 连接查询

如何在 C 中将链接列表解连接为 3 个单独的链接列表?

使用连接创建动态数据透视表列表

Mysql连表查询