9

I followed the directions given in this question (the answer by Jason) in order to write my PriorityQueue<T> using a SortedList. I understand that the count field within this class is used to ensure unique priorities and to preserve the enqueue order among the same priority.

However, when count reaches its maximum value and I sum 1 to it, the latter will starts again from 0, so the priority of the subsequent items would be higher than the priority of the previous items. Using this approach I could need for a way to "securely" reset the counter count... In fact, suppose to have the following queue state (in the format priority | count | item):

0 | 123 | A
0 | 345 | B
1 | 234 | C
2 | 200 | D

Now suppose the counter limit has reached, so I have to reset it to 0: as a consequence, the next inserted item will have counter 0: for example, if I insert an element with priority equal to 1, it will be wrongly inserted before 1 | 234 | D

0 | 123 | A
0 | 345 | B
1 | 000 | new element
1 | 234 | C
2 | 200 | D

The problem of the priority can be solved by implementing an heap: I created an Heap class, then I used Heap<KeyValuePair<TPriority, TElement> and a custom PriorityComparer in order to sort elements by TPriority. Given TPriority as an int and TElement as a string, the PriorityComparer is as follows:

public class MyComparer : IComparer<KeyValuePair<int, string>>
{
    public int Compare(KeyValuePair<int, string> x, KeyValuePair<int, string> y)
    {
        return x.Key.CompareTo(y.Key);
    }
}

...

int capacity = 10;
Heap<KeyValuePair<int, string>> queue;
queue = new Heap<KeyValuePair<int, string>>(capacity, new PriorityComparer());

...

UPDATE In this way (using the PriorityComparer), I have succeeded to implement a priority queue. Now I'd like to add support to modify its behavior at runtime, ie switch from FIFO to priority sorting and vice-versa. Since my implementation of priority queue has an IComparer field, I think it is sufficient to add a Comparer property to edit this field, like as follows:

public IComparer
{
    set
    {
        this._comparer = value;
    }
}

In the meantime I thought I'd take a different approach: instead of using a binary heap to manage priorities, I could wrap different queues (each queue refers to a given priority) as follows.

public class PriorityQueue<T, int>
{
    private Queue<T> _defaultQueue;
    private bool _priority;
    private SortedList<int, Queue<T>> _priorityQueues;

    public PriorityQueue(int capacity)
    {
        this._defaultQueue = new Queue<T>(capacity);
        this._priority = false;
        this._priorityQueues = new SortedList<int, Queue<T>>(0);
    }

    public void PriorityEnable()
    {
        this._priority = true;
    }

    public void PriorityDisable()
    {

        this._priority = false;
    }

    public void Enqueue(T item)
    {
        if (this._priority)
        {
            // enqueue to one of the queues
            // with associated priority
            // ...
        }
        else this._defaultQueue.Enqueue(item);
    }

    public T Dequeue()
    {
        if (this._priority)
        {
            // dequeue from one of the queues
            // with associated priority and
            // return
            // ...
        }
        return this._defaultQueue.Dequeue();
    }
}
  1. How to manage the transition from FIFO mode to priority mode when there are still elements in the default queue? I could copy them in the priority queues based on the item priority... Other better solutions?
  2. How to manage the transition from priority mode to FIFO mode? In this case, I would have several priority queues, which may contain elements, but no longer have to manage them according to priority and not even know the original order of arrival...
  3. How can I manage the capacity of the different queues?
  4. What about the performances of the above two solutions? Which does use more memory?
Community
  • 1
  • 1
enzom83
  • 8,080
  • 10
  • 68
  • 114

3 Answers3

1

EDIT: You have kind of changed what you are asking with your edits. You went from asking one question to doing a new approach and asking a new question. Should probably open a new question for your new approach, as this one is now confusing as to what answer/response is to what question/comment. I believe your original question about sorting equal priorities has been answered.

You could use a long to allow for more values. You will always reach an end eventually, so you would need to use a new pattern for unique values or 'recount' the items when the max is reached (loop through each and reset the unique count value).

Maybe use a GUID for each item instead?

Guid.NewGuid()

EDIT:

To add after your edit: If you want the new 1 to be placed after the existing, In the Compare override, return a greater than result (1) when the values are equal. That way the following will happen:

1 > 0, return greater (1), continue
1 > 0, return greater (1), continue
1 == 1, return greater (1), continue
1 < 2, return less than (-1), insert

EDIT 2:

If the second parameter is only meant to be a unique value, you could always use a string and set the value as numeric strings instead. That way you will never reach a cap, would just have to parse the string accordingly. You can use leading alpha values that represent a new set.

I have not tested this code, just an idea as to what you could do.

static string leadingStr = "";
static char currentChar = 'a';
static Int32 currentId = Int32.MinValue;

static string getNextId()
{
    if (currentId >= Int32.MaxValue)
    {
        currentId = Int32.MinValue;
        if (currentChar >= 'z')
        {
            currentChar = 'a';
            leadingStr = leadingStr.Insert(0, "X");
        }
        else
            currentChar++;
    }
    else
        currentId++;

    return String.Format("{0}{1}-{2}", leadingStr, currentChar, currentId);
}

EDIT 3: Reset Values

static Int64 currentValue = Int64.MinValue;
static void AddItem(object item)
{
    if (currentValue == Int64.MaxValue)
        RecountItems();

    item.counter = currentValue++;
    SortedList.Add(item);
}

static void RecountItems()
{
    currentValue = 0;
    foreach (var item in SortedList)
    {
        item.counter = currentValue++;
    }
}

Edit 4: For your second question:

You could use a FIFO stack as you normally would, but also have a priority List that only stores the unique ID of the items. However you would then need to remove the item from the list every time you remove from the FIFO stack.

static Object RemoveNextFIFO()
{
    if (fifoList.Count > 0)
    {
        var removedItem = fifoList[0];
        fifoList.RemoveAt(0);
        RemoveItemFromPriority(removedItem);
        return removedItem;
    }
}

static void RemoveItemFromPriority(Object itemToRemove)
{
    foreach (var counter in priorityQueue)
    {
        if (counter == itemToRemove.counter)
        {
            priorityQueue.remove(item);
            break;
        }
    }
}

static Object RemoveFromFIFO(int itemCounter)
{
    foreach (var item in fifoList)
    {
        if (item.counter == itemCounter)
        {
            fifoList.Remove(item);
            return item;   
        }
    }
}

static Object RemoveNextPriority()
{
    if (priorityQueue.Count > 0)
    {
        var counter = priorityQueue.Pop();
        return RemoveFromFIFO(counter);
    }
}
JeremyK
  • 1,075
  • 1
  • 22
  • 45
  • Well, the `SortedList` automatically sorts the items using an `IComparer`: I already created this _comparer_ and it works sorting by priority and counter (the items with the same priority are sorted comparing their counters). The problem is that at some point the counter reaches its maximum value and begins again from 0: any items added before reset the counter would have a greater counter and so they would appear as if they had been inserted after the last ones. The `Guid` does not solve the problem, because it is random, so it could distort the order of insertion. – enzom83 Feb 11 '12 at 14:56
  • Would it be possible to attach an additional parameter to your data? That is, if you had an id from 0 to 255 (just an example) you could then have an idSecond from 0 to 255 where you first check the id then the idSecond? So you'd have everything from 0,0 to 0,1 to ... to 255,255 which would square your allowed numbers? Using TWO longs would surely exceed your requirements, right? -- From an apprentice programmer. – BlackVegetable Feb 20 '12 at 21:38
  • 1
    @BlackVegetable: _Would it be possible to attach an additional parameter to your data?_ If there is no alternative, I think the only right is to add another parameter, maybe the long containing the current date and time. – enzom83 Feb 21 '12 at 14:23
  • What is the point of the second parameter? I thought it was to maintain a unique id. using Guid would ensure this would it not? The priority is still sorted by the priority id and not the unique id. If you add new items they would always get placed after the last matching priority if you return 1 when equal. – JeremyK Feb 21 '12 at 17:19
  • I think he needs a specific ordering, not necessarily in the order they were added, right? – BlackVegetable Feb 21 '12 at 17:22
  • Using a `SortedSet`, the priority must be accompanied by a counter, just to allow for items with the same priority: compared to a `Guid`, I think it's better a `long`... Basically the queue should be able to switch from FIFO to priority-based and vice-versa. – enzom83 Feb 21 '12 at 18:05
  • Well in that case you can use a string based counter, see my update. It's probably slower and not an ideal solution, but best I can think of with my limited use of .net libraries out there – JeremyK Feb 21 '12 at 18:11
  • I don't see why resetting the values doesn't work either. Once you reach your max, loop through all items and set their counter to counter++ starting at 0. Then when new items are added they are added just like they would have been before – JeremyK Feb 21 '12 at 18:18
  • @JeremyK: I added new details about five or six days ago: the first part of the question was not changed, while the second part begins with the word **UPDATE**. Basically the target of the question remains the same, so there was no reason to open another question. – enzom83 Feb 21 '12 at 19:27
  • The original question asks about keeping a sorted priority list and how to handle new items with the same priority. The new question talks about being able to switch between FIFO and a priority queue. Not trying to be rude, just a different question. Your end goal is not the same as it was. There are now a mix of answers and responses for the two goals. – JeremyK Feb 21 '12 at 19:30
  • "Now I'd like to add support to modify its behavior at runtime" You basically stated, you got your answer, and now you want to do something new. – JeremyK Feb 21 '12 at 19:31
  • @JeremyK: The goal of the question is not **extremely** changed since the beginning. On the day following (11 Feb, see [revision 4](http://stackoverflow.com/posts/9232604/revisions)) I solved the problem of ordering the items _by priority and order of arrival_ using a simple binary heap, so I thought it appropriate to update this thread by writing a question related to the first. – enzom83 Feb 21 '12 at 19:58
  • It is your call. I am not trying to give you a hard time, but by creating a new question for what you are now trying to do you will get better support. Reusing this question and not closing it off just pollutes it. – JeremyK Feb 21 '12 at 20:23
  • There is another direction you could take by using a list to go with your priority and just removing from the one you want and then clearing it out of the second list by search. Doesnt sound fast though. – JeremyK Feb 21 '12 at 21:51
1

You could "cheat" and use BigInteger so you never "run out of numbers". This of course leads to gradual deterioration of performance over time, but probably not significant enough to matter.

Combine that with a heap-based priority queue and you are set!


Don't try to "switch from FIFO to priority sorting and vice-versa" - simply put elements in both data structures appropriate for the task (Queue and priority queue).

Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
  • I just need to "switch from FIFO to priority sorting and vice-versa". In this way, when the queue fills beyond a certain limit, it switch to priority queue. Then, when the filling level is reduced below a certain threshold, it swith to FIFO. – enzom83 Feb 18 '12 at 15:04
  • @enzom83 Why? What are you trying to accomplish? – Branko Dimitrijevic Feb 18 '12 at 15:12
  • I need to control the flow of outgoing messages to a connection, so I use a queue for each connection: if there are too many messages in the queue, probably the connection is slow then I could apply a priority to outgoing messages. – enzom83 Feb 18 '12 at 16:53
  • @enzom83 So why can't you _always_ use the priority queue? – Branko Dimitrijevic Feb 18 '12 at 16:55
  • Because under normal conditions (ie, without the need to control the flow) I need of a FIFO sorting. – enzom83 Feb 18 '12 at 17:03
  • Suppose you use multiple FIFO queues, one for each type of task: in this way each queue always contains tasks with the same priority, so in this case, each queue has a different priority. What rules should I follow to dequeue items from queues with different priorities? – enzom83 Feb 21 '12 at 13:40
  • @enzom83 The rule of "dequeuing from the queue with the highest priority first" perhaps? You can have a priority queue of FIFO queues if that's what you want. – Branko Dimitrijevic Feb 21 '12 at 14:24
  • Should I add a new queue? For example, if I have four queues (each with a certain priority), should I add an _additional queue_ to decide which queue to dequeue? – enzom83 Feb 21 '12 at 14:36
  • Why should you add a queue just to dequeue it? Form the priority queue, pick the element with the highest priority. That element is actually a FIFO queue, so you simply dequeue _it_. If the FIFO queue becomes empty after that, you remove it from the priority queue so the next dequeue operation can find the next priority. All that assuming I actually understand correctly what you want ;) – Branko Dimitrijevic Feb 21 '12 at 15:07
1

Using both Queue and Priority Queue is what I would do.
But if you must...

Instead of one key use 2 keys for an element.
The first key priority will be the priority.
The second key time will be a counter that will be like a timestamp.

For the regular behavior use the priority key.

When the heap is full, HEAPIFY it by the time key.
Then remove n needed elements.
Now HEAPIFY it again with the prioritykey to return to the regular behavior.

Avi Cohen
  • 3,102
  • 2
  • 25
  • 26
  • When the heap is full, maybe I should _re-heapify_ it by the _priority_ key (not by time)... – enzom83 Feb 21 '12 at 14:27
  • It is more of a playful idea, Queue+Priority Queue is the way to go. – Avi Cohen Feb 21 '12 at 14:44
  • I think I'll use a heap: it is sufficient that each element including the priority, since duplicates are not a problem for the heap (the items which have the same value are placed in the order of arrival, no need to add more fields). As soon as some elements (`N` elements) are removed from the queue, I re-heapify with new priorities equal to each other. – enzom83 Feb 21 '12 at 23:11
  • In OOD it said to prefer composition over inheritance. The same rule holds here. If you want the good of more than one data structure then use them in composition. In composition you don't change the data structure code and this is a great plus. – Avi Cohen Feb 22 '12 at 09:01
  • Should I use an `Heap` object as private field of the new `PriorityQueue` class? – enzom83 Feb 22 '12 at 13:22
  • Yes, since it is a part of the inner implementation of your new class. – Avi Cohen Feb 22 '12 at 13:46
  • Again, I still recommend that your `new class` will have the `priority queue` and the `queue` as its private members. – Avi Cohen Feb 22 '12 at 14:20