【问题标题】:In C#, is there a queue which can only hold an object once in its lifetime?在 C# 中,是否有一个队列在其生命周期中只能保存一次对象?
【发布时间】:2013-11-26 04:46:41
【问题描述】:

我需要一个数据结构,它是一种特殊类型的队列。我想要这样,如果我的队列的一个实例曾经包含一个对象 X,那么在这个实例中应该不可能再次将 X 加入队列。如果用 X 调用,入队方法应该什么都不做,比如尝试向 HashSet 添加重复值。

示例用法:

MyQueue<int> queue = new MyQueue<int>(); 
queue.Enqueue(5); 
queue.Enqueue(17); 
queue.Enqueue(28); 
queue.Enqueue(17); 
int firstNumber = queue.Dequeue(); 
queue.Enqueue(5); 
queue.Enqueue(3); 

List<int> queueContents = queue.ToList(); //this list should contain {17, 28, 3}

我在 MSDN 上四处寻找,但找不到这样的课程。它是否存在,或者我必须自己实现它?

我想我也可以使用不同的数据结构,但访问总是先进先出,所以我认为队列将是最有效的。此外,我不知道有任何其他结构提供这种“实例生命周期的唯一性”功能。

【问题讨论】:

  • 我很确定你必须自己推出。
  • @Eugene OP 已经在问题中提到了 HashSet。
  • @Eugene 如果我从 HashSet 中删除一个元素,我可以稍后再添加它。我想要一个不允许我这样做的结构。就像具有内存和元素顺序的 HashSet。
  • 请注意,要使其正常工作,您传入队列的任何类型都必须支持正确的相等比较(.Equals(object).GetHashCode()

标签: c# data-structures queue


【解决方案1】:

你必须自己实现。

一个想法是在您入队时将元素添加到HashSet

然后,当你想入队时,只需检查 HashSet 的项目,如果存在,请不要入队。

由于您想在队列的剩余生命周期内阻止排队,您可能永远不想从HashSet 中删除。

【讨论】:

    【解决方案2】:

    我会做类似的事情:

    class UniqueQueue<T>
    {
        private readonly Queue<T> queue = new Queue<T>();
        private HashSet<T> alreadyAdded = new HashSet<T>();
    
        public virtual void Enqueue(T item)
        {
            if (alreadyAdded.Add(item)) { queue.Enqueue(item); }
        }
        public int Count { get { return queue.Count; } }
    
        public virtual T Dequeue()
        {
            T item = queue.Dequeue();
            return item;
        }
    }
    

    注意,这段代码大部分是从This Thread借来的。

    【讨论】:

    • 您应该在这里使用HashSet 而不是List。列表的表现会差很多。
    • 在阅读其他答案后,我会同意。不幸的是,以前从未使用过 HashSet,所以我不知道。事实上,我希望我能投票赞成其他一些答案。
    • 当然,只是在我公开同意 HashSet 更好之前,确保我了解它们的好处。
    • 而不是if (alreadyAdded.Contains(item)) return;if (alreadyAdded.Add(item)) { queue.Enqueue(item); }。如果添加了项目,Add 返回 true。如果项目已存在,则返回 false。基本上,您正在保存查找并简化代码。
    • Dequeue 应该从alreadyAdded 中清除出列项,否则后续对Enqueue 的调用将不起作用。
    【解决方案3】:

    您可以使用基本队列,但修改 Enqueue 方法以验证先前输入的值。在这里,我使用了一个哈希集来包含那些以前的值:

    public class UniqueValueQueue<T> : Queue<T>
    {
        private readonly HashSet<T> pastValues = new HashSet<T>();
    
        public new void Enqueue(T item)
        {
            if (!pastValues.Contains(item))
            {
                pastValues.Add(item);
    
                base.Enqueue(item);
            }
        }
    }
    

    用你的测试用例

    UniqueValueQueue<int> queue = new UniqueValueQueue<int>();
    queue.Enqueue(5);
    queue.Enqueue(17);
    queue.Enqueue(28);
    queue.Enqueue(17);
    int firstNumber = queue.Dequeue();
    queue.Enqueue(5);
    queue.Enqueue(3);
    
    List<int> queueContents = queue.ToList();
    

    queueContents 包含 17、28 和 3。

    【讨论】:

    • 你不需要 HashSet。你可以从 Queue 调用 Contains(item) 来检查项目是否已经存在。
    • 问题在于,由于Enqueue 不是虚拟的,您有可能将对象投射到Queue 并添加重复项。最好不要从队列继承而是封装它。
    • @Kabbalah 这会执行得非常糟糕,因为通过哈希集搜索比通过队列搜索要快得多。最重要的是。要求是您不能添加曾经在队列中的项目,而不仅仅是当前在队列中的项目。
    • @Kabbalah 不,用 OP 的测试用例试试,你会发现它失败了。不能添加任何曾经被排队的项目,不仅是当前排队的项目。
    • @Pierre-LucPineault 对不起,我错过了。我已经删除了我的答案。
    【解决方案4】:

    这只是wayne's answer 的扩展版本,只是更加充实并支持更多接口。 (模仿Queue&lt;T&gt;的界面)

    sealed class UniqueQueue<T> : IEnumerable<T>, ICollection, IEnumerable
    {
        private readonly Queue<T> queue;
        private readonly HashSet<T> alreadyAdded;
    
        public UniqueQueue(IEqualityComparer<T> comparer)
        {
            queue = new Queue<T>();
            alreadyAdded = new HashSet<T>(comparer);
        }
    
        public UniqueQueue(IEnumerable<T> collection, IEqualityComparer<T> comparer)
        {
            //Do this so the enumeration does not happen twice in case the enumerator behaves differently each enumeration.
            var localCopy = collection.ToList();
    
            queue = new Queue<T>(localCopy);
            alreadyAdded = new HashSet<T>(localCopy, comparer);
        }
    
        public UniqueQueue(int capacity, IEqualityComparer<T> comparer)
        {
            queue = new Queue<T>(capacity);
            alreadyAdded = new HashSet<T>(comparer);
        }
    
        //Here are the constructors that use the default comparer. By passing null in for the comparer it will just use the default one for the type.
        public UniqueQueue() : this((IEqualityComparer<T>) null) { }
        public UniqueQueue(IEnumerable<T> collection) : this(collection, null) { }
        public UniqueQueue(int capacity) : this(capacity, null) { }
    
        /// <summary>
        /// Attempts to enqueue a object, returns false if the object was ever added to the queue in the past.
        /// </summary>
        /// <param name="item">The item to enqueue</param>
        /// <returns>True if the object was successfully added, false if it was not</returns>
        public bool Enqueue(T item)
        {
            if (!alreadyAdded.Add(item))
                return false;
    
            queue.Enqueue(item);
            return true;
        }
    
        public int Count
        {
            get { return queue.Count; }
        }
    
        public T Dequeue()
        {
            return queue.Dequeue();
        }
    
        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return ((IEnumerable<T>)queue).GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)queue).GetEnumerator();
        }
    
        void ICollection.CopyTo(Array array, int index)
        {
            ((ICollection)queue).CopyTo(array, index);
        }
    
        bool ICollection.IsSynchronized
        {
            get { return ((ICollection)queue).IsSynchronized; }
        }
    
        object ICollection.SyncRoot
        {
            get { return ((ICollection)queue).SyncRoot; }
        }
    }
    

    【讨论】:

    • 这和我最终实现的很接近。我忘记包含所有构造函数和 ICollection 接口的实现(虽然我想到了 IEnumerable),但实现了您不包含的特定于队列的方法,例如 Peek。但我希望你不介意我把接受的答案给韦恩,因为他是第一个,也因为他可以利用声誉,这是他的第一个帖子。
    • @RumiP。相信韦恩,我只是想为未来的访问者提供更全面的实施。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-27
    • 2011-01-05
    • 1970-01-01
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多