6

I need a datastructure, which is a special type of queue. I want that, if an instance of my queue ever contained an object X, it shouldn't be possible to enqueue X again in this instance. The enqueuing method should just do nothing if called with X, like the attempt to add a duplicate value to a HashSet.

Example usage:

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}

I looked around on MSDN, but couldn't find such a class. Does it exist, or do I have to implement it myself?

I guess I could use a different data structure too, but access will always be FIFO, so I thought a queue will be most efficient. Also, I don't know of any other structure which provides such "uniqueness over instance lifetime" feature.

Rumi P.
  • 1,688
  • 3
  • 23
  • 31

4 Answers4

5

You'd have to implement that yourself.

One idea is just to add the element to a HashSet when you enqueue it.

Then, when you want to enqueue, just check the HashSet for the item, if it exists, don't enqueue.

Since you want to prevent enqueuing for the rest of the queue's lifetime, you probably won't want to ever remove from the HashSet.

Bernhard Barker
  • 54,589
  • 14
  • 104
  • 138
5

I would do something similar to this:

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;
    }
}

Note, most of this code was borrowed from This Thread.

Community
  • 1
  • 1
  • 1
    You should be using a `HashSet` instead of a `List` here. A list will perform much worse. – Servy Nov 13 '13 at 14:46
  • I would agree, after reading the other answers. Unfortunately never used a HashSet before so I wasn't aware of it. Infact I wish I could upvote some of the other answers. – Wayne Pincence Nov 13 '13 at 14:53
  • Of course, was just making sure I understood the benefits of HashSet's before I publicly agreed they were better. – Wayne Pincence Nov 13 '13 at 14:59
  • 1
    Rather than `if (alreadyAdded.Contains(item)) return;` do `if (alreadyAdded.Add(item)) { queue.Enqueue(item); }`. `Add` returns `true` if the item was added. It returns `false` if the item already exists. Basically, you're saving a lookup and simplifying your code. – Jim Mischel Nov 13 '13 at 15:05
  • 3
    `Dequeue` should clear the dequeued item out of `alreadyAdded`, otherwise subsequent calls to `Enqueue` won't work. – Paul Ruane Apr 09 '14 at 10:17
4

This is just a extended version of wayne's answer, it is just a little more fleshed out and having a few more interfaces supported. (To mimic Queue<T>'s interfaces)

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; }
    }
}
Community
  • 1
  • 1
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • This is close to what I ended up implementing. I forgot to include the implementation of all the constructors and the ICollection interface (although I thought of IEnumerable), but implemented queue-specific methods you don't include, such as Peek. But I hope you don't mind me giving the accepted answer to wayne, because he was first and also because he can use the reputation, this being his first post. – Rumi P. Nov 14 '13 at 12:20
  • @RumiP. Give Wayne the credit, I just wanted to give a fuller implementation for future visitors. – Scott Chamberlain Nov 14 '13 at 14:44
0

You can use a basic queue, but modify the Enqueue method to verify the previous values entered. Here, I used a hashset to contain those previous values :

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);
        }
    }
}

With your test case

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 contains 17, 28 and 3.

Pierre-Luc Pineault
  • 8,993
  • 6
  • 40
  • 55
  • 1
    You don't need a HashSet. You could just call Contains(item) from Queue to check whether the item already exists. – Kabbalah Nov 13 '13 at 14:47
  • 2
    The problem is that since `Enqueue` isn't virtual you have the possibility of someone casting the object to `Queue` and adding duplicates. It's likely best to not inherit from queue but encapsulate it. – Servy Nov 13 '13 at 14:48
  • 2
    @Kabbalah That would perform much, much worse, as searching through a hashset is much faster than searching through a queue. On top of that. the requirements are that you shouldn't be able to add an item that was ever in the queue, not just items currently in the queue. – Servy Nov 13 '13 at 14:48
  • @Kabbalah No, try it with OP's test case and you'll see it fails. Can't add any item that ever been enqueued, not only currently queued items. – Pierre-Luc Pineault Nov 13 '13 at 14:49
  • @Pierre-LucPineault Sorry I missed that. I've deleted my answer. – Kabbalah Nov 13 '13 at 14:50