85

Can I traverse a standard priority_queue or standard queue in c++ with an iterator (like a vector)? I don't want to use pop because it cause my queue to be dequeued.

Thanks for any help

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
mina70
  • 851
  • 1
  • 6
  • 3

16 Answers16

40

priority_queue doesn't allow iteration through all the members, presumably because it would be too easy in invalidate the priority ordering of the queue (by modifying the elements you traverse) or maybe it's a "not my job" rationale.

The official work-around is to use a vector instead and manage the priority-ness yourself with make_heap, push_heap and pop_heap. Another work-around, in @Richard's answer, is to use a class derived from priority_queue and access the underlying storage which has protected visibility.

xan
  • 7,511
  • 2
  • 32
  • 45
  • 10
    This should be in the documentation, to let us know how limited priority_queue's usefulness is... sigh. – Dronz Jul 01 '18 at 23:42
  • 5
    "because it would be too easy in invalidate the priority ordering of the queue (by modifying the elements you traverse) " returning const iterator is possible. Just like map returns iterator where key is constant. – Kavar Rushikesh Feb 14 '22 at 08:37
28

You can do it like this - bam! Notice that items are not necessarily in "sorted" order while they are in the queue, at least with regards to a straight-forward iteration of the container.

#include <queue>
#include <cstdlib>
#include <iostream>
using namespace std;

template <class T, class S, class C>
S& Container(priority_queue<T, S, C>& q) {
    struct HackedQueue : private priority_queue<T, S, C> {
        static S& Container(priority_queue<T, S, C>& q) {
            return q.*&HackedQueue::c;
        }
    };
    return HackedQueue::Container(q);
}

int main()
{
    priority_queue<int> pq;
    vector<int> &tasks = Container(pq);

    cout<<"Putting numbers into the queue"<<endl;
    for(int i=0;i<20;i++){
        int temp=rand();
        cout<<temp<<endl;
        pq.push(temp);
    }

    cout<<endl<<"Reading numbers in the queue"<<endl;
    for(vector<int>::iterator i=tasks.begin();i!=tasks.end();i++)
        cout<<*i<<endl;

    cout<<endl<<"Taking numbers out of the queue"<<endl;
    while(!pq.empty()){
        int temp=pq.top();
        pq.pop();
        cout<<temp<<endl;
    }

    return 0;
}
Rapptz
  • 20,807
  • 5
  • 72
  • 86
Richard
  • 56,349
  • 34
  • 180
  • 251
  • 5
    Subclassing std:: containers is dangerous/wrong because they lack virtual destructors. – geometrian Sep 25 '14 at 05:10
  • 1
    So this example does have a memory leaking because subclassing `std:: containers` is dangerous/wrong because they lack virtual destructor? – Kenenbek Arzymatov Nov 29 '17 at 16:36
  • 1
    I don't see how this could leak. We define HackedQueue subclassing the container and we add a static method. we then use the static method to extract the protected parameter. We didn't even instantiate one object of this subclass... – Tic Jun 19 '19 at 15:08
  • What is ".*&"? Calling a member of q with a pointer of a member of a different class? – Zohar Levi Sep 01 '21 at 01:55
  • https://stackoverflow.com/questions/69012795/accessing-a-base-class-member-with-accessing-priority-queue-container – Zohar Levi Sep 01 '21 at 11:23
  • Elegant solution! – larboyer May 25 '22 at 03:19
12

A queue purposefully provides a limited interface, which excludes iteration. But since a queue uses a deque as the underlying container, why not use a deque directly?

#include <iostream>
#include <queue>
using namespace std;

int main() {
  deque<int> q;
  q.push_back(1);
  q.push_back(2);
  q.push_back(3);
  for(deque<int>::iterator it = q.begin(); it != q.end(); ++it)
    cout << *it << endl;
}

Similar answer for a priority queue: no, you cannot. In this case though, a vector is used by default. In neither case can you access the underlying container to iterate over them. See this question for further reading.

Community
  • 1
  • 1
moinudin
  • 134,091
  • 45
  • 190
  • 216
  • 3
    I thought I would add an answer to your question of "why not use a deque directly?" In my scenario, I would like to log the contents of the priority_queue without affecting the implementation (by changing the type). This is a logging effort, where performance is not important, so making a copy would work fine. – D. A. Feb 24 '15 at 21:58
  • 6
    Can you promote the "no" part of the answer to the top. Frankly I do not understand why deque, it has no relation to the question. – Peter K Sep 20 '18 at 14:18
11
#include <queue>
#include <iostream>

int main() {
    std::priority_queue<int> pq;

    pq.push_back(1);
    pq.push_back(2);
    pq.push_back(3);

    std::priority_queue<int> temp = pq;

    while (!temp.empty()) {
        std::cout << temp.top() << std::endl;
        temp.pop();
    }

    return 0;
}
Snooze
  • 499
  • 1
  • 5
  • 14
5

Yes, make a copy of the priority_queue and iterate over that.

Lie Ryan
  • 62,238
  • 13
  • 100
  • 144
  • 13
    Sounds like a reasonable idea if performance is not a concern. You will get my upvote if you provide a code snippet. – D. A. Feb 24 '15 at 21:56
3

I found this after stumbling across your question. There is a very simple way of doing this by writing an implementation inheriting from std::priority_queue. It is all of 14 lines.

http://www.linuxtopia.org/online_books/programming_books/c++_practical_programming/c++_practical_programming_189.html

Jonathan Henson
  • 8,076
  • 3
  • 28
  • 52
3

This is not possible. You would have to use a different container, probably a deque would serve you best.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
2

Queues are totally different from vectors and are used for different purposes. Priority queues are simply sorted deques with no direct access to the back. However, if you desperately want to do this for whatever method, what you can do is pop off the top/front element, add it to a list/array/vector, and then push the element back into your queue for(size_t i = 0; i < q.size(); i++). I took a class in java data structures, and this was the answer to an exam question. Plus it is the only method i can think of.

sj755
  • 3,944
  • 14
  • 59
  • 79
2

Many of these answers rely on coding/using many of C++ arcane features. That's ok, fun and funds expensive programmers. A direct solution that is quick, cheap to program but more expensive to run, is:

// 
// Only use this routine when developing code, NOT for production use!!
//
// Note. _pq is in private for a class that uses the priority queue
// and listQueue is a public method in that same class.
//
void listQueue() {

    // allocate pointer to a NEW container
    priority_queue<int>* new_pq = new  priority_queue<int>;

    while (!_pq->empty()) {

        int el = _pq->top();

        cout << "(" << el << ")" << endl;

        new_pq->push(el);

        _pq->pop();

    } // end while;

    // remove container storage
    delete(_pq);

    // viola, new container same as the old
    _pq = new_pq;

} // end of listQueue;

By the way, it seems perfectly non-sensible to NOT provide an iterator for a priority_queue, especially when it is a container class for a or structure.

JackCColeman
  • 3,777
  • 1
  • 15
  • 21
2

Most existing answers either suggest using a different data structure or performing a sequence of O(log n) operations (n being the size of the priority queue). Some even advise to make a copy of the structure.

The operation can be performed in linear time with respect to the number of elements, provided that :

  • the underlying container is a vector, and
  • it is not necessary to go through the elements in sorted order.

If these conditions are met, the following code can be used :

#include <iostream>
#include <queue>

int main()
{
    using T = int;
    std::priority_queue<T, std::vector<T>, std::less<T>> q;
    
    q.emplace(1); q.emplace(16); q.emplace(5); q.emplace(42); q.emplace(3);
    
    const T* base = &q.top(); // root of the binary heap
    for(size_t i = 0; i<q.size(); i++)
        std::cout << *(base+i)<<" ";
    std::cout << std::endl; // outputs "42 16 5 1 3"

    return 0;
}

This approach has the advantage of not changing the data structure used : if a binary heap is suited for your application, there is no point in using a multiset. Also it is compact, trivial to implement, does not change the original object and does not require additional memory. Finally, it runs in O(n) time instead of the commonly seen O(n log n).

Romain
  • 829
  • 6
  • 8
  • why is no one commenting on this? Could you please share your opinions on this as it seems to be the "optimal" solution :) – Vero Apr 30 '23 at 18:18
  • 1
    @Vero The main drawback is that it does not work when the elements of the heap are not stored contiguously (e.g. when the underlying Container is a std::dequeue, instead of a std::vector). The syntax "*(base+i)" seems scary but could be replaced by "base[i]" for clarity. Regarding performance, this method is indeed optimal (and much faster than the other approaches), since it runs in linear time wrt. the number of elements, which happens to be the lower bound for this problem (OP needs to go through each of these elements). Also, its memory requirements do not depend on the size of the queue. – Romain May 03 '23 at 08:05
1

C++ priority_queue does not offer a .begin() pointer (like vector would do) that you can use to iterate over it.

If you want to iterate over the priority queue to search for whether it contains a value then maybe create a wrapper priority queue and use a hash set to keep track of what you have in the queue.

class MyPriorityQueue {

   MyPriorityQueue() {}

   void push(int item) {
     if (!contains(item)){
       pq_.push(item);
       set_.emplace(item);
     }
   }
   void pop() {
     if (!empty()) {
       int top = pq_.top();
       set_.erase(top);
       pq_.pop();
     }
   }
   int top() { return pq_.top(); }
   bool contains(int item) { return set_.find(item) != set_.end(); }
   bool empty() const { return set_.empty(); }

 private:
   std::priority_queue<int> pq_;
   std::unordered_set<int> set_;
};
mario
  • 11
  • 3
1

For basic purposes, a std::multiset will give you similar properties but with the ability to iterate:

  • Items sorted, custom Less can be defined
  • Keys can occur multiple times
  • Quick to access and remove first item
AndiDog
  • 68,631
  • 21
  • 159
  • 205
  • Just a hint: `std::priority_queue` by default orders by largest element first whereas sets order by lowest element first - so probably the ordering has to be inverted. `std::set` could also be used when the elements are different. – michael_s Oct 10 '19 at 11:17
0

I had the same question myself. I found it was very difficult, maybe impossible, to get to the data structure underlying the priority queue. In my case this was a vector of objects.

However, I ended up using a standard template library heap. It is almost as easy as a priority queue (it takes two instructions to push and pop, vs. 1 for the pq), otherwise the behavior seems to be identical and I can get at the underlying data structure if I don't modify it.

David E.
  • 89
  • 2
0

If you want to push items in an ordered manner with complexity (logN). But would like to iterate over the elements as well in increasing order, you can use set<int>. Sets are typically implemented as binary search trees.

Sets are iterable (begin, end, rbegin, rend etc)

Biboswan
  • 1,145
  • 12
  • 15
0

Making a copy and iterating over that - suggested by @lie-ryan

#include <iostream>
#include <queue>

using namespace std;
void make_copy(priority_queue<int, vector<int>> pq, vector<int> &arr)
{
    arr = {}; // if it was not empty , make it :)
    while (!pq.empty())
    {
        arr.push_back(pq.top());
        pq.pop();
    }
}
int main()
{
    priority_queue<int, vector<int>> q;
    q.push(1);
    q.push(2);
    q.push(3);
    vector<int> arr;
    make_copy(q, arr); // this will copy all elements of q to arr :)
    for (auto &x : arr)
    {
        cout << x << " ";
    }
}
-1

I had the same problem, where I wanted to iterate over a priority queue without dequeuing (hence destroying my queue). I made it work for me by recasting my priority_queue pointer to a pointer to a vector (as my priority_queue uses vector as its container). Here is how it looks like:

class PriorityQueue {
  private:
    class Element {
    int x; 
    //Other fields
    ...
    ..
    //Comparator function
    bool operator()(const Element* e1, const Element* e2) const {
        // Smallest deadline value has highest priority.
        return e1->x > e2->x;
    }
    };
    // Lock to make sure no other thread/function is modifying the queue
    // Ofcourse we can do our operation w/o lock. if we are sure what is happening in other functions
    pthread_mutex_t lock;   
    std::priority_queue<Element*, std::vector<Element*>, Element> pq;

  public:
    PriorityQueue();
    ~PriorityQueue();
    //print the all the elements of the queue
    void print_queue_elements() {
        std::vector<PriorityQueue::Element*> *queue_vector;
        //Acquire the lock
        pthread_mutex_lock(&lock);
        //recast the priority queue to vector
        queue_vector = reinterpret_cast<std::vector<PriorityQueue::Element*> *>(&pq);
        for(std::vector<PriorityQueue::Element*>::iterator it = (*queue_vector).begin(); it != (*queue_vector).end(); it++) {
            //Assuming Element class has string function
            printf("My Element %s", (*it)->string);
            //other processing with queue elements
        }
        //Release the lock
        pthread_mutex_unlock(&lock);    

    }
    //other functions possibly modifying the priority queue
    ...
    ..
    .
 };

Now since I am using reinterpret_cast, people can argue about type-safety. But in this case I am sure about all other functions accessing/changing the queue (all of which are safe).. and I feel this is a much better way than copying the content of whole queue to some other container.

I was actually expecting static_cast to work.. as priority_queue is adaptor over container (vector in our case), but it doesnt and I had to use reinterpret_cast.

Coding Mash
  • 3,338
  • 5
  • 24
  • 45
akshay singh
  • 71
  • 3
  • 5