【问题标题】:HashSet that preserves ordering保留排序的 HashSet
【发布时间】:2010-12-05 20:25:55
【问题描述】:

我需要一个保留插入顺序的 HashSet,框架中是否有任何实现?

【问题讨论】:

  • 我想你的意思就像 Java 的 LinkedHashSet
  • 是的,最简单的做法是将链接列表和哈希集包装在一起......这是我过去最终所做的。对 LRU 实现很有用
  • 根据 Set 的定义不应该保留任何顺序。
  • @vash 代替那句话,问题应该是“我需要一个保留插入顺序并具有哈希集访问特性的数据结构,即 O(1) 等。”

标签: c# .net hashset


【解决方案1】:

标准 .NET HashSet 不保留插入顺序。 对于简单的测试,插入顺序可能会由于意外而被保留,但不能保证并且不会总是以这种方式工作。证明中间做一些删除就足够了。

有关更多信息,请参阅此问题:Does HashSet preserve insertion order?

我已经简单地实现了一个HashSet,它保证了插入顺序。它使用Dictionary 查找项目并使用LinkedList 保留顺序。所有三个插入、删除和查找仍然在 O(1) 中工作。

public class OrderedSet<T> : ICollection<T>
{
    private readonly IDictionary<T, LinkedListNode<T>> m_Dictionary;
    private readonly LinkedList<T> m_LinkedList;

    public OrderedSet()
        : this(EqualityComparer<T>.Default)
    {
    }

    public OrderedSet(IEqualityComparer<T> comparer)
    {
        m_Dictionary = new Dictionary<T, LinkedListNode<T>>(comparer);
        m_LinkedList = new LinkedList<T>();
    }

    public int Count => m_Dictionary.Count;

    public virtual bool IsReadOnly => m_Dictionary.IsReadOnly;

    void ICollection<T>.Add(T item)
    {
        Add(item);
    }

    public bool Add(T item)
    {
        if (m_Dictionary.ContainsKey(item)) return false;
        var node = m_LinkedList.AddLast(item);
        m_Dictionary.Add(item, node);
        return true;
    }

    public void Clear()
    {
        m_LinkedList.Clear();
        m_Dictionary.Clear();
    }

    public bool Remove(T item)
    {
        if (item == null) return false;
        var found = m_Dictionary.TryGetValue(item, out var node);
        if (!found) return false;
        m_Dictionary.Remove(item);
        m_LinkedList.Remove(node);
        return true;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return m_LinkedList.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public bool Contains(T item)
    {
        return item != null && m_Dictionary.ContainsKey(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        m_LinkedList.CopyTo(array, arrayIndex);
    }
}

【讨论】:

  • 您确实应该提供一个重载,将IEqualityComparer&lt;T&gt; 传递给IDictionary&lt;T&gt; 的相同重载。开箱即用,您的示例通过对象引用检查相等性(至少对于类),并且没有提供更改该行为的选项。不过我知道这是一个简单的例子。
  • @SimonBelanger 同意。我刚刚在这里发布了一个实现所有常见构造函数和方法变体的版本:gmamaladze.wordpress.com/2013/07/25/…
  • m_LinkedList.Remove(node); 实际上不是 O(n) 吗?
  • @HimBromBeere - 好问题。是的,但实际上没有。如果你想删除一个特定的元素,因为你必须找到这个元素,删除本身就是 O(1)。如果你看一下我的实现,我们使用字典来查找它,它是 O(1),而不是有趣的列表。首先。stackoverflow.com/questions/33987542/…
  • @HimBromBeere:特别是O(1),因为@GeorgeMamaladze 存储了LinkedListNode&lt;T&gt; 引用,这使得在双向链表中的删除时间恒定。
【解决方案2】:

您可以使用KeyedCollection&lt;TKey,TItem&gt; 为 TKey 和 TItem 指定相同的类型参数轻松获得此功能:

public class OrderedHashSet<T> : KeyedCollection<T, T>
{
    protected override T GetKeyForItem(T item)
    {
        return item;
    }
}

【讨论】:

  • 当您调用Remove 时,它会调用Remove(T) 还是Remove(TKey)?第一个是 O(n),第二个是 O(1)。
  • 它调用Remove(TKey),因为它是派生最多的类。但是,当集合被转换为 Collection 或 ICollection 时调用 Remove() 将调用 Remove(T) 重载。
  • 另一个澄清:Remove(T)Remove(TKey) 都是 O(n),因为 KeyedCollection&lt;T, T&gt; 使用列表来存储项目。
  • 请注意,与常规的 System.Collections.Generic.HashSet&lt;T&gt; 不同,这将在重复键上抛出 System.ArgumentException 而不是返回 false
  • @kcnygaard - 为什么你认为 KeyedCollection 保持插入顺序?在内部它只是一个字典,即known to not maintain order under all conditions
【解决方案3】:

如果您需要AddRemoveContains 的恒定复杂性和订单保存,那么 .NET Framework 4.5 中没有这样的集合。

如果您对 3rd 方代码没问题,请查看我的存储库(许可 MIT 许可证): https://github.com/OndrejPetrzilka/Rock.Collections

OrderedHashSet&lt;T&gt;收藏:

  • 基于经典 HashSet&lt;T&gt; 源代码(来自 .NET Core)
  • 保留插入顺序并允许手动重新排序
  • 特性反向枚举
  • HashSet&lt;T&gt;具有相同的操作复杂性
  • AddRemove 操作比 HashSet&lt;T&gt; 慢 20%
  • 每个项目多消耗 8 个字节的内存

【讨论】:

    【解决方案4】:

    现在 .NET 和 .NET Core 中都有 SortedSet&lt;T&gt;

    https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.sortedset-1?view=net-5.0

    但是效率有点低,因为 Hashset 使用哈希表,而 SortedSet 使用 b-tree。

    【讨论】:

      猜你喜欢
      • 2010-10-14
      • 2018-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-02
      相关资源
      最近更新 更多