保留顺序的 HashSet

发布于 2024-08-07 15:22:26 字数 39 浏览 3 评论 0原文

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

I need a HashSet that preserves insertion ordering, are there any implementations of this in the framework?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

看透却不说透 2024-08-14 15:22:27

You can use OrderedDictionary to preserve the order of insertion. But beware of the cost of Removing items (O(n)).

烟雨扶苏 2024-08-14 15:22:26

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

有关详细信息,请参阅此问题:HashSet 保留插入顺序吗?

我已经简单地实现了一个保证插入顺序的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);
    }
}

Standard .NET HashSet do not preserve the insertion order.
For simple tests the insertion order may be preserved due to an accident, but it's not guaranteed and would not always work that way. To prove that it is enough to do some removals in between.

See this question for more information on that: Does HashSet preserve insertion order?

I have briefly implemented a HashSet which guarantees insertion order. It uses the Dictionary to look up items and the LinkedList to preserve order. All three insertion, removal and lookup work still in 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);
    }
}
梦与时光遇 2024-08-14 15:22:26

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

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

You can get this functionality easily using KeyedCollection<TKey,TItem> specifying the same type argument for TKey and TItem:

public class OrderedHashSet<T> : KeyedCollection<T, T>
{
    protected override T GetKeyForItem(T item)
    {
        return item;
    }
}
浴红衣 2024-08-14 15:22:26

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

如果您同意第 3 方代码,请查看我的存储库(宽松的 MIT 许可证):
https://github.com/OndrejPetrzilka/Rock.Collections

OrderedHashSet; 集合:

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

If you need constant complexity of Add, Remove, Contains and order preservation, then there's no such collection in .NET Framework 4.5.

If you're okay with 3rd party code, take a look at my repository (permissive MIT license):
https://github.com/OndrejPetrzilka/Rock.Collections

There's OrderedHashSet<T> collection:

  • based on classic HashSet<T> source code (from .NET Core)
  • preserves order of insertions and allows manual reordering
  • features reversed enumeration
  • has same operation complexities as HashSet<T>
  • Add and Remove operations are 20% slower compared to HashSet<T>
  • consumes 8 more bytes of memory per item
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文