【问题标题】:Scala: Can I rely on the order of items in a Set?Scala:我可以依赖 Set 中项目的顺序吗?
【发布时间】:2011-03-09 12:23:13
【问题描述】:

这是一个非常令人不快的惊喜:

scala> Set(1, 2, 3, 4, 5)       
res18: scala.collection.immutable.Set[Int] = Set(4, 5, 1, 2, 3)
scala> Set(1, 2, 3, 4, 5).toList
res25: List[Int] = List(5, 1, 2, 3, 4)

这个例子本身对我的问题提出了一个“否”的答案。那么ListSet呢?

scala> import scala.collection.immutable.ListSet
scala> ListSet(1, 2, 3, 4, 5)
res21: scala.collection.immutable.ListSet[Int] = Set(1, 2, 3, 4, 5)

这似乎有效,但我应该依赖这种行为吗? 还有哪些其他数据结构适用于唯一项的不可变集合,其中必须保留原始顺序?

顺便说一句,我确实知道List 中的distict 方法。问题是,我想在界面级别强制项目的唯一性(同时保留顺序),所以使用 distinct 会弄乱我的整洁设计..

编辑

ListSet 似乎也不太可靠:

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

EDIT2

在寻找完美设计时,我尝试了这个:

scala> class MyList[A](list: List[A]) { val values = list.distinct }
scala> implicit def toMyList[A](l: List[A]) = new MyList(l)
scala> implicit def fromMyList[A](l: MyList[A]) = l.values     

实际可行:

scala> val l1: MyList[Int] = List(1, 2, 3)
scala> l1.values
res0: List[Int] = List(1, 2, 3)

scala> val l2: List[Int] = new MyList(List(1, 2, 3))
l2: List[Int] = List(1, 2, 3)

然而,问题是我不想在库之外公开MyList。有没有办法在覆盖时进行隐式转换?例如:

trait T { def l: MyList[_] }
object O extends T { val l: MyList[_] = List(1, 2, 3) }
scala> O.l mkString(" ")  // Let's test the implicit conversion
res7: String = 1 2 3      

我想这样做:

object O extends T { val l = List(1, 2, 3) }  // Doesn't work

【问题讨论】:

  • 在哪里可以阅读比“Scala 编程”和 Scaladocs 更深入的有关 scala 集合的信息?
  • 这是开始学习 Scala 集合的好地方:scala-lang.org/docu/files/collections-api/collections.html
  • 集合按(数学)定义没有顺序,大多数语言都遵循该约定。好奇,你为什么希望他们有?除了唯一性之外,顺序是SeqSet 之间唯一的实质性区别!
  • 在这种情况下,我不太关心我的数据结构的数学属性。我只需要保留原始顺序的不同项目的不可变集合。称它为列表、集合、向量、表格或“卡车负载”:)

标签: scala set


【解决方案1】:

这取决于您使用的套装。如果你不知道你有哪个 Set 实现,那么答案很简单,不,你不能确定。在实践中我通常会遇到以下三种情况:

  1. 我需要订购套装中的物品。为此,我使用在 SortedSet 特征中混合的类,当您仅使用标准 Scala API 时,它始终是 TreeSet。它保证元素按照它们的compareTo 方法排序(参见Ordered trat)。由于插入/检索的运行时间现在是对数的,而不是像 HashSet 那样(几乎)恒定(假设一个良好的散列函数),因此排序会受到(非常)小的性能损失。

  2. 您需要保留插入项目的顺序。然后你使用LinkedHashSet。几乎和普通的HashSet 一样快,需要更多的存储空间来存储元素之间的额外链接。

  3. 你不关心集合中的顺序。所以你使用HashSet。 (在第一个示例中使用 Set.apply 方法时,这是默认设置)

这一切也适用于 Java,Java 有 TreeSetLinkedHashSetHashSet 以及相应的接口 SortedSetComparable 和普通 Set

【讨论】:

  • 如果您查看the source for LinkedHashSet,您会发现它所做的只是混合基本的 Set 特征并添加一个存储元素顺序的列表成员。因此,按照该模式,您可以使用不可变包中的相应基本特征和类简单地编写自己的不可变 LinkedHashSet。
  • 插入顺序不是事先定义好的:如果相同的元素被添加两次会发生什么,即一个代表将被放在哪里?
  • 另外,依赖给定类型的实现也不是一个好主意。您需要通过其接口定义(合同)确保保持秩序的东西。我想不出一个可以做到这一点的现有不可变集合的简单包装器。您可以使用TreeMap[(Int,T)](跟踪第一个组件中的插入顺序),但这意味着显式委派所有 set 方法。
  • Scala API 文档确实(遗憾地)没有提及它,但根据代码,再次插入元素不会改变它的位置(这也是 Java LinkedHashMap 的行为方式):@ 987654339@。 Java API 更精确:“请注意,如果将键重新插入到地图中,插入顺序不会受到影响”
  • 有时必须依赖给定类型的实现。当我需要一个带有固定时间前置的列表时,我需要使用链表,而不是数组支持的列表。这就是为什么在这种情况下我明确使用实现的类型,而不是接口或特征。例如,我是否允许将一个支持数组的列表交给我的代码,我可以获得二次运行时。所以,我的代码依赖于列表的实现。如果我想明确插入顺序对我的代码行为有影响,我使用 LinkedHashSet 作为类型。如果我不在乎,我会使用 Set。
【解决方案2】:

我相信你永远不应该依赖一组中的顺序。没有语言。

除此之外,请查看this question,其中深入讨论了这一点。

【讨论】:

  • 谢谢,我没有找到其他问题.. 我不好
  • 在 C++ 中,std::set 保证被订购。 -1.
  • @larsmans...举个例子?一个提供OP想要的顺序(即插入顺序)的例子?真的吗?
  • 同意@larsmans。在 C++ 中,std::set 是有序的,而 std::unordered_set 是无序的
  • Christoph Henkelmann 的答案是正确的答案。唯一性和排序性是正交属性,在集合类(任何语言)中都需要这两者是完全合法的。
【解决方案3】:

ListSet 将始终以与插入相反的顺序返回元素,因为它由 List 支持,而将元素添加到 List 的最佳方法是预先添加元素。

如果您想要先进先出(队列),则不可变数据结构是有问题的。您可以获得O(logn) 或摊销O(1)。鉴于显然需要构建集合,然后从中生成一个迭代器(即,您将首先放置所有元素,然后您将删除所有元素),我看不到任何摊销它的方法。

可以相信ListSet 将始终以后进先出的顺序(堆栈)返回元素。如果够了,那就去吧。

【讨论】:

  • 我考虑了 ListSet 有一段时间,但有一个问题——我不得不在某个地方反转列表以恢复原始顺序。不幸的是,你不能在不可变特征中做到这一点。如果我将我的 trait 转换为一个类(我最终必须这样做),我可以很容易地做到 list = providedList.distinct 或者只是在重复项上抛出异常......
猜你喜欢
  • 2020-07-22
  • 2016-04-07
  • 1970-01-01
  • 1970-01-01
  • 2017-06-07
  • 2015-11-22
  • 1970-01-01
  • 2014-03-19
  • 1970-01-01
相关资源
最近更新 更多