//******************************************************************************************************
// PriorityQueue.cs - Gbtc
//
// Copyright © 2014, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 03/28/2014 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace GSF.Collections
{
///
/// Represents a queue of items to which priority can be assigned.
///
/// The type of the items stored in the queue.
public class PriorityQueue : IList
{
#region [ Members ]
// Nested Types
private class PriorityQueueNode : IComparable
{
public int Priority;
public int InsertionOrder;
public T Item;
public int CompareTo(PriorityQueueNode other)
{
// Higher priority than other
if (Priority > other.Priority)
return 1;
// Lower priority than other
if (Priority < other.Priority)
return -1;
// Same priority, but inserted after other
if (InsertionOrder - other.InsertionOrder > 0)
return -1;
// Assuming nodes are never compared to themselves:
// Same priority, but inserted before other
return 1;
}
public static bool operator >(PriorityQueueNode node1, PriorityQueueNode node2)
{
return node1.CompareTo(node2) > 0;
}
public static bool operator <(PriorityQueueNode node1, PriorityQueueNode node2)
{
return node1.CompareTo(node2) < 0;
}
}
// Fields
private PriorityQueueNode[] m_heap;
private int m_count;
private int m_insertionOrder;
private int m_version;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
public PriorityQueue()
: this(0)
{
}
///
/// Creates a new instance of the class.
///
/// The initial size of the underlying array.
public PriorityQueue(int capacity)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException("capacity");
if (capacity == 0)
m_heap = EmptyHeap;
else
m_heap = new PriorityQueueNode[capacity];
}
#endregion
#region [ Properties ]
///
/// Gets the head of the queue. The value returned
/// is the same as the method.
///
public T Head
{
get
{
return Peek();
}
}
///
/// Gets or sets the element at the specified index.
///
///
/// The element at the specified index.
///
/// The zero-based index of the element to get or set.
/// is not a valid index in the .
public T this[int index]
{
get
{
if (index < 0 || index >= m_count)
throw new ArgumentOutOfRangeException("index");
return m_heap[index].Item;
}
set
{
if (index < 0 || index >= m_count)
throw new ArgumentOutOfRangeException("index");
m_heap[index].Item = value;
m_version++;
}
}
///
/// Gets the number of elements contained in the .
///
///
/// The number of elements contained in the .
///
public int Count
{
get
{
return m_count;
}
}
///
/// Gets a value indicating whether the is read-only.
///
///
/// False
///
public bool IsReadOnly
{
get
{
return false;
}
}
#endregion
#region [ Methods ]
///
/// Enqueues an item into the queue.
///
/// The priority of the item.
/// The item to be enqueued.
///
/// After adding an item to the queue, the heap will need to be fixed,
/// therefore this is an O(log n) operation.
///
public void Enqueue(int priority, T item)
{
int newIndex;
if (m_heap.Length == m_count)
IncreaseCapacity();
newIndex = m_count;
m_count++;
m_heap[newIndex] = new PriorityQueueNode()
{
Priority = priority,
InsertionOrder = m_insertionOrder,
Item = item
};
FixHeapUp(newIndex);
m_insertionOrder++;
m_version++;
}
///
/// Gets the item with the highest priority and removes it from the queue.
///
/// The item with the highest priority.
///
/// After removing an item from the queue, the heap will need to be fixed,
/// therefore this is an O(log n) operation.
///
/// The queue is empty.
public T Dequeue()
{
T head;
if (m_count == 0)
throw new InvalidOperationException("Cannot dequeue from an empty queue.");
head = m_heap[0].Item;
RemoveAt(0);
return head;
}
///
/// Gets the item with the highest priority.
///
/// The item with the highest priority.
/// The queue is empty.
public T Peek()
{
if (m_count == 0)
throw new InvalidOperationException("Cannot peek at an empty queue.");
return m_heap[0].Item;
}
///
/// Gets the priority of the item at the given index.
///
/// The index of the item.
/// The priority of the item.
/// Index does not fall within the bounds of the queue.
public int GetPriority(int index)
{
if (index < 0 || index >= m_count)
throw new ArgumentOutOfRangeException("index");
return m_heap[index].Priority;
}
///
/// Sets the priority of the item at the given index.
///
/// The index of the item.
/// The new priority of the item.
///
/// This method can be used to change the priority of the item
/// at the given index. After setting the priority, the queue
/// will have to fix the heap so this is an O(log n) operation.
///
/// Index does not fall within the bounds of the queue.
public void SetPriority(int index, int priority)
{
int oldPriority;
if (index < 0 || index >= m_count)
throw new ArgumentOutOfRangeException("index");
oldPriority = m_heap[index].Priority;
m_heap[index].Priority = priority;
if (priority > oldPriority)
FixHeapUp(index);
else if(priority < oldPriority)
FixHeapDown(index);
m_version++;
}
///
/// Adds the given value to the priority of all values in the priority queue.
///
/// The amount by which to adjust priorities.
///
/// This allows for adjusting the priorities of all items in the heap without
/// having to fix the heap each time a priority is changed. This can be useful
/// to increase the priorities of items that have been in the queue for significant
/// periods of time to prevent starvation. This is an O(n) operation.
///
public void AdjustPriority(int delta)
{
foreach (PriorityQueueNode node in m_heap)
node.Priority += delta;
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through the collection.
///
///
/// The enumerator performs an in-order traversal of the heap, so the caller should
/// expect that items will be iterated in no particular order. Any operations that
/// would modify the collection during iteration will cause the enumerator to throw
/// an .
///
public IEnumerator GetEnumerator()
{
int version = m_version;
int count = 0;
foreach (PriorityQueueNode node in m_heap)
{
if (version != m_version)
throw new InvalidOperationException("Collection was modified; enumeration operation may not execute");
if (count >= m_count)
break;
yield return node.Item;
count++;
}
}
///
/// Determines whether the contains a specific value.
///
/// The object to locate in the .
///
/// true if is found in the ; otherwise, false.
///
public bool Contains(T item)
{
Func comparer;
if ((object)item != null)
comparer = heapItem => item.Equals(heapItem);
else
comparer = heapItem => (object)heapItem == null;
return this.Any(comparer);
}
///
/// Determines the index of a specific item in the .
///
/// The object to locate in the .
///
/// The index of if found in the list; otherwise, -1.
///
public int IndexOf(T item)
{
Func comparer;
int index;
if ((object)item != null)
comparer = heapItem => !item.Equals(heapItem);
else
comparer = heapItem => (object)heapItem != null;
index = this.TakeWhile(comparer).Count();
return (index < m_count) ? index : -1;
}
///
/// Copies the elements of the to an
/// , starting at a particular index.
///
/// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.
/// The zero-based index in at which copying begins.
/// is null.
/// is less than 0.
/// The number of elements in the source is greater than the available space from to the end of the destination .
public void CopyTo(T[] array, int arrayIndex)
{
if ((object)array == null)
throw new ArgumentNullException("array");
if (arrayIndex < 0)
throw new ArgumentOutOfRangeException("arrayIndex");
if (array.Length - arrayIndex < m_count)
throw new ArgumentException("Not enough space in destination.", "array");
foreach (T item in this)
array[arrayIndex++] = item;
}
///
/// Removes the item at the specified index.
///
/// The zero-based index of the item to remove.
///
/// After removing an item from the queue, the heap will need to be fixed,
/// therefore this is an O(log n) operation.
///
/// is not a valid index in the .
public void RemoveAt(int index)
{
PriorityQueueNode removedNode;
PriorityQueueNode swappedNode;
if (index < 0 || index >= m_count)
throw new ArgumentOutOfRangeException("index");
m_count--;
m_version++;
if (index == m_count)
{
m_heap[m_count] = null;
}
else
{
removedNode = m_heap[index];
swappedNode = m_heap[m_count];
m_heap[index] = swappedNode;
m_heap[m_count] = null;
if (swappedNode > removedNode)
FixHeapUp(index);
else if (swappedNode < removedNode)
FixHeapDown(index);
}
}
///
/// Removes the first occurrence of a specific object from the .
///
/// The object to remove from the .
///
/// true if was successfully removed from the ; otherwise, false.
/// This method also returns false if is not found in the original .
///
///
/// After removing an item from the queue, the heap will need to be fixed,
/// therefore this is an O(log n) operation.
///
public bool Remove(T item)
{
int index = IndexOf(item);
if (index < 0)
return false;
RemoveAt(index);
return true;
}
///
/// Removes all items from the .
///
public void Clear()
{
Array.Clear(m_heap, 0, m_count);
m_count = 0;
m_version++;
}
private void FixHeapUp(int child)
{
int parent;
int left;
int right;
int highestChild;
// If the child has no parent,
// there's nothing to do
if (child == 0)
return;
// Get references to the child's
// parent and both its children
parent = GetParent(child);
left = GetLeftChild(parent);
right = GetRightChild(parent);
// Determine which of the children has greater priority
highestChild = left;
if (right < m_count && m_heap[right] > m_heap[left])
highestChild = right;
// If the higher-priority child has a greater priority than its parent,
// swap the parent and child nodes. Since the priority of the parent has
// changed, we'll need to continue up the heap to get it to the right place
if (m_heap[highestChild] > m_heap[parent])
{
Swap(parent, highestChild);
FixHeapUp(parent);
}
}
private void FixHeapDown(int parent)
{
int left;
int right;
int highestChild;
// Get a reference to the left child
left = GetLeftChild(parent);
// If the parent has no children,
// then there's nothing to do
if (left >= m_count)
return;
// Get a reference tothe right child and
// determine which child has greater priority
right = GetRightChild(parent);
highestChild = left;
if (right < m_count && m_heap[right] > m_heap[left])
highestChild = right;
// If the higher priority child has a greater priority than its parent,
// swap the parent and child nodes. Since the priority of the child has
// changed, we'll need to continue down the heap to get it to the right place
if (m_heap[highestChild] > m_heap[parent])
{
Swap(parent, highestChild);
FixHeapDown(highestChild);
}
}
// The indexes represent the positions of the nodes when performing
// an in-order traversal of the heap. Parent and child nodes can be
// reached using simple mathematical operations.
private int GetParent(int index)
{
return (index - 1) / 2;
}
private int GetLeftChild(int index)
{
return (index * 2) + 1;
}
private int GetRightChild(int index)
{
return (index * 2) + 2;
}
// Swaps the nodes at the given indexes.
private void Swap(int index1, int index2)
{
PriorityQueueNode temp = m_heap[index1];
m_heap[index1] = m_heap[index2];
m_heap[index2] = temp;
}
// Increases the capacity of the array representing the heap.
private void IncreaseCapacity()
{
if (m_heap == EmptyHeap)
m_heap = new PriorityQueueNode[4];
else
Array.Resize(ref m_heap, m_heap.Length * 2);
}
// Explicit implementation because items should
// be enqueued with a specified priority.
void ICollection.Add(T item)
{
Enqueue(int.MinValue, item);
}
// Not supported. Allowing insertion would break heap ordering.
// Fixing the heap would change the index of the inserted item anyway.
void IList.Insert(int index, T item)
{
throw new NotSupportedException("Allowing insertion into the heap would break heap ordering.");
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region [ Static ]
// Static Fields
private static readonly PriorityQueueNode[] EmptyHeap = new PriorityQueueNode[0];
#endregion
}
}