Queue with unique entries in c++

后端 未结 4 877
生来不讨喜
生来不讨喜 2021-02-05 07:25

I need to implement a queue containing unique entries(no duplicates) in C or C++. I am thinking of maintaining a reference of elements already available in queue but that seems

相关标签:
4条回答
  • 2021-02-05 07:56

    std::queue is a container adaptor and uses relatively few members of the underlying Container. You can easily implement a custom container that contains both: an unordered_map of reference_wrapper<T> and a deque<T>. It needs at least members front and push_back. Check inside that hash_map when push_back of your container is called and reject accordingly (possibly throw). To give the complete example:

    #include <iostream>
    #include <set>
    #include <deque>
    #include <queue>
    #include <unordered_set>
    #include <functional>
    
    namespace std {
    
    // partial specialization for reference_wrapper
    // is this really necessary?
    template<typename T>
    class hash<std::reference_wrapper<T>> {
    public:
      std::size_t operator()(std::reference_wrapper<T> x) const 
      { return std::hash<T>()(x.get()); }
    };
    
    }
    
    template <typename T>
    class my_container {
      // important: this really needs to be a deque and only front
      // insertion/deletion is allowed to not get dangling references
      typedef std::deque<T> storage;
      typedef std::reference_wrapper<const T> c_ref_w;
      typedef std::reference_wrapper<T> ref_w;
    public:  
      typedef typename storage::value_type value_type;
      typedef typename storage::reference reference; 
      typedef typename storage::const_reference const_reference; 
      typedef typename storage::size_type size_type;
    
      // no move semantics
      void push_back(const T& t) {
        auto it = lookup_.find(std::cref(t));
        if(it != end(lookup_)) {
          // is already inserted report error
          return;
        }
        store_.push_back(t);
        // this is important to not have dangling references
        lookup_.insert(store_.back());
      }
    
      // trivial functions
    
      bool empty() const { return store_.empty(); }
      const T& front() const { return store_.front(); }
      T& front() { return store_.front(); }
    
      void pop_front() { lookup_.erase(store_.front()); store_.pop_front();  }
    private:
      // look-up mechanism
      std::unordered_set<c_ref_w> lookup_;
      // underlying storage
      storage store_;
    };
    
    int main()
    {
      // reference wrapper for int ends up being silly 
      // but good for larger objects
      std::queue<int, my_container<int>> q;
      q.push(2);
      q.push(3);
      q.push(2);
      q.push(4);
      while(!q.empty()) {
        std::cout << q.front() << std::endl;
        q.pop();
      }
    
      return 0;
    }
    

    EDIT: You will want to make my_container a proper model of container (maybe also allocators), but this is another full question. Thanks to Christian Rau for pointing out bugs.

    0 讨论(0)
  • 2021-02-05 07:59

    There is one very important point you've not mentioned in your question, and that is whether your queue of items is sorted or have some kind of ordering (called a Priority queue), or unsorted (called a plain FIFO). The solution you choose will depend only on the answer to this question.

    1. If your queue is unsorted, then maintaining an extra data structure in addition to your queue will be more efficient. Using a second structure which is ordered in some way to maintain the contents of your queue will allow you check if an item already exists in your queue or not much quicker that scanning the queue itself. Adding to the end of an unsorted queue takes constant time and can be done very efficiently.

    2. If your queue must be sorted, then placing the item into the queue requires you to know the item's position in the queue, which requires the queue to be scanned anyway. Once you know an item's position, you know if the item is a duplicate because if it's a duplicate then an item will already exist at that position in the queue. In this case, all work can be performed optimally on the queue itself and maintaining any secondary data structure is unnecessary.

    The choice of data structures is up to you. However, for (1) the secondary data structure should not be any kind of list or array, otherwise it will be no more efficient to scan your secondary index as to scan the original queue itself.

    0 讨论(0)
  • 2021-02-05 08:06

    How about an auxiliary data structure to track uniqueness:

    std::queue<Foo> q;
    std::set<std::reference_wrapper<Foo>> s;
    
    // to add:
    
    void add(Foo const & x)
    {
        if (s.find(x) == s.end())
        {
            q.push_back(x);
            s.insert(std::ref(q.back()));  // or "s.emplace(q.back());"
        }
    }
    

    Or, alternatively, reverse the roles of the queue and the set:

    std::set<Foo> s;
    std::queue<std::reference_wrapper<Foo>> q;
    
    void add(Foo const & x)
    {
        auto p = s.insert(x);       // std::pair<std::set<Foo>::iterator, bool>
        if (s.second)
        {
            q.push_back(std::ref(*s.first));  // or "q.emplace_back(*s.first);"
        }
    }
    
    0 讨论(0)
  • 2021-02-05 08:18

    queuing:

    • use std::set to maintain your set of unique elements
    • add any element that you were able to add to the std::set to the std::queue

    dequeueing:

    • remove element from std::queue and std::set
    0 讨论(0)
提交回复
热议问题