【发布时间】:2010-11-17 20:39:22
【问题描述】:
我正在探索HashSet<T> 类型,但我不明白它在集合中的位置。
可以用它来代替List<T>吗?我认为HashSet<T> 的性能会更好,但我看不到个人对其元素的访问。
只用于枚举吗?
【问题讨论】:
标签: c# .net data-structures hashset
我正在探索HashSet<T> 类型,但我不明白它在集合中的位置。
可以用它来代替List<T>吗?我认为HashSet<T> 的性能会更好,但我看不到个人对其元素的访问。
只用于枚举吗?
【问题讨论】:
标签: c# .net data-structures hashset
HashSet 是一个通过散列实现的set。集合是不包含重复元素的值的集合。集合中的值通常也是无序的。所以不,不能使用集合来替换列表(除非您首先应该使用集合)。
如果您想知道集合可能有什么用处:显然,您想在任何地方摆脱重复。作为一个稍微人为的例子,假设您有一个软件项目的 10.000 个修订的列表,并且您想找出有多少人为该项目做出了贡献。您可以使用Set<string> 并遍历修订列表并将每个修订的作者添加到集合中。完成迭代后,集合的大小就是您要寻找的答案。
【讨论】:
性能将是选择 HashSet 而不是 List 的一个不好的理由。相反,有什么能更好地捕捉您的意图?如果顺序很重要,那么 Set(或 HashSet)就出局了。如果允许重复,同样如此。但是在很多情况下,我们不关心顺序,我们宁愿没有重复 - 这就是你想要一个 Set 的时候。
【讨论】:
Performance would be a bad reason to choose HashSet over List:我只是不同意你的观点。这就是说选择一个字典而不是两个列表对性能没有帮助。看看the following article
string[].Contains 和 HashSet<string>.Contains 可以很好地表达您的意图;选择 HashSet 的原因是它会运行得更快。
HashSet<T> 是 .NET 框架中的数据结构,能够将 mathematical set 表示为对象。在这种情况下,它使用哈希码(每个项目的GetHashCode 结果)来比较集合元素的相等性。
集合与列表的不同之处在于它只允许其中包含的相同元素出现一次。如果您尝试添加第二个相同的元素,HashSet<T> 只会返回 false。实际上,元素的查找非常快(O(1) 时间),因为内部数据结构只是一个哈希表。
如果您想知道使用哪个,请注意,使用 List<T>(其中 HashSet<T> 是合适的)并不是最大的错误,尽管它可能会在您的集合中有不受欢迎的重复项目时出现问题。更重要的是,查找(项目检索)效率更高 - 理想情况下是 O(1)(用于完美分桶)而不是 O(n) 时间 - 这在许多情况下都非常重要。
【讨论】:
List<T> 用于存储有序的信息集。如果您知道列表元素的相对顺序,则可以在恒定时间内访问它们。但是,要确定元素在列表中的位置或检查它是否存在于列表中,查找时间是线性的。另一方面,HashedSet<T> 不保证存储数据的顺序,因此为其元素提供恒定的访问时间。
顾名思义,HashedSet<T> 是实现set semantics 的数据结构。数据结构经过优化以实现集合操作(即 Union、Difference、Intersect),这是传统 List 实现无法高效完成的。
因此,选择使用哪种数据类型实际上取决于您尝试对应用程序执行的操作。如果您不关心元素在集合中的排序方式,而只想枚举或检查是否存在,请使用HashSet<T>。否则,请考虑使用List<T> 或其他合适的数据结构。
【讨论】:
hashsets 最常见的用途可能是查看它们是否包含某个元素,这对它们来说接近 O(1) 操作(假设一个足够强的散列函数),而不是检查包含的列表是 O(n) (以及它是 O(log n) 的排序集)。因此,如果您进行大量检查,某个项目是否包含在某个列表中,hahssets 可能会提高性能。如果你只对它们进行迭代,则不会有太大区别(迭代整个集合是 O(n),与列表相同,哈希集在添加项目时会有更多开销)。
不,你不能索引一个集合,这无论如何都是没有意义的,因为集合不是有序的。如果你添加一些物品,套装将不记得哪个是第一个,哪个是第二个等等。
【讨论】:
简而言之 - 任何时候你想使用字典(或字典,其中 S 是 T 的属性),那么你应该考虑一个 HashSet(或 HashSet + 在 T 上实现 IEquatable,它等于 S)
【讨论】:
HashSet<T> 的重要之处就在名称中:它是一个集合。您可以对单个集合做的唯一事情是确定它的成员是什么,并检查一个项目是否是成员。
询问您是否可以检索单个元素(例如set[45])是对集合概念的误解。没有像集合的第 45 个元素这样的东西。集合中的项目没有排序。集合 {1, 2, 3} 和 {2, 3, 1} 在各个方面都是相同的,因为它们具有相同的成员资格,而成员资格才是最重要的。
迭代HashSet<T> 有点危险,因为这样做会对集合中的项目施加顺序。该顺序并不是该集合的真正属性。你不应该依赖它。如果集合中项目的排序对您很重要,那么该集合就不是集合。
集合非常有限,并且具有独特的成员。另一方面,它们真的很快。
【讨论】:
SortedSet 数据结构的事实与您所说的顺序不是集合的属性相矛盾 - 或者指出了开发团队的误解。
HashSet里面的item的顺序没有定义比较正确,所以不要依赖迭代器的顺序。如果您因为对集合中的项目做某事而对集合进行迭代,那不是危险的除非您依赖于与订单相关的任何事物。 SortedSet 具有 HashSet plus 顺序的所有属性,但是 SortedSet 不是从 HashSet 派生的;换个说法,SortedSet 是不同对象的有序集合。
这是我使用HashSet<string> 的真实示例:
我的 UnrealScript 文件语法高亮显示的一部分是 highlights Doxygen-style comments 的新功能。我需要能够判断 @ 或 \ 命令是否有效,以确定是显示为灰色(有效)还是红色(无效)。我有一个所有有效命令的HashSet<string>,所以每当我在词法分析器中点击@xxx 标记时,我都会使用validCommands.Contains(tokenText) 作为我的O(1) 有效性检查。除了有效命令的set 中命令的存在 之外,我真的不关心任何事情。让我们看看我面临的替代方案:
Dictionary<string, ?>: 我用什么类型的值?该值没有意义,因为我将使用ContainsKey。注意:在 .NET 3.0 之前,这是 O(1) 查找的唯一选择 - 为 3.0 添加了 HashSet<T>,并扩展为在 4.0 中实现 ISet<T>。List<string>:如果我保持列表排序,我可以使用BinarySearch,即 O(log n)(没有看到上面提到的这个事实)。但是,由于我的有效命令列表是一个永远不会改变的固定列表,所以这永远不会比简单地更合适......string[]:同样,Array.BinarySearch 提供 O(log n) 性能。如果列表很短,这可能是性能最佳的选择。它的空间开销总是比HashSet、Dictionary 或List 少。即使使用BinarySearch,对于大型系列来说也不会更快,但对于小型系列来说,它值得尝试。不过我的有几百件,所以我把它传递了。【讨论】:
HashSet<T> 实现了ICollection<T> 接口:
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
// Methods
void Add(T item);
void Clear();
bool Contains(T item);
void CopyTo(T[] array, int arrayIndex);
bool Remove(T item);
// Properties
int Count { get; }
bool IsReadOnly { get; }
}
List<T> 实现 IList<T>,它扩展了 ICollection<T>
public interface IList<T> : ICollection<T>
{
// Methods
int IndexOf(T item);
void Insert(int index, T item);
void RemoveAt(int index);
// Properties
T this[int index] { get; set; }
}
HashSet 具有设置语义,通过内部哈希表实现:
集合是一个集合,不包含 重复元素,以及其元素 没有特别的顺序。
如果 HashSet 失去索引/位置/列表行为,它会获得什么?
从 HashSet 添加和检索项目始终由对象本身进行,而不是通过索引器,并且接近 O(1) 操作(列表是 O(1) 添加,O(1) 通过索引检索,O( n) 查找/删除)。
可以将 HashSet 的行为与使用 Dictionary<TKey,TValue> 进行比较,只需添加/删除键作为值,而忽略字典值本身。您会希望字典中的键没有重复值,这就是“设置”部分的重点。
【讨论】:
HashSet 将用于删除 IEnumerable 集合中的重复元素。例如,
List<string> duplicatedEnumrableStrings = new List<string> {"abc", "ghjr", "abc", "abc", "yre", "obm", "ghir", "qwrt", "abc", "vyeu"};
HashSet<string> uniqueStrings = new HashSet(duplicatedEnumrableStrings);
在这些代码运行后,uniqueStrings 保存 {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"};
【讨论】:
在基本预期场景中,当您希望对两个集合进行比 LINQ 提供的更具体的集合操作时,应使用HashSet<T>。 Distinct、Union、Intersect 和 Except 等 LINQ 方法在大多数情况下就足够了,但有时您可能需要更细粒度的操作,HashSet<T> 提供:
UnionWithIntersectWithExceptWithSymmetricExceptWithOverlapsIsSubsetOfIsProperSubsetOfIsSupersetOfIsProperSubsetOfSetEqualsLINQ 和HashSet<T>“重叠”方法的另一个区别是LINQ 总是返回一个新的IEnumerable<T>,而HashSet<T> 方法修改源集合。
【讨论】: