first commit
This commit is contained in:
433
NitroxModel/DataStructures/PriorityQueue.cs
Normal file
433
NitroxModel/DataStructures/PriorityQueue.cs
Normal file
@@ -0,0 +1,433 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace NitroxModel.DataStructures
|
||||
{
|
||||
public class PriorityQueue<T>
|
||||
{
|
||||
public PriorityQueue()
|
||||
{
|
||||
// Build the collection of priority chains.
|
||||
priorityChains = new SortedList<int, PriorityChain<T>>(); // NOTE: should be Priority
|
||||
cacheReusableChains = new Stack<PriorityChain<T>>(10);
|
||||
|
||||
head = tail = null;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
// NOTE: not used
|
||||
// public int Count {get{return _count;}}
|
||||
|
||||
public int MaxPriority // NOTE: should be Priority
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = priorityChains.Count;
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
return priorityChains.Keys[count - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return int.MaxValue; // NOTE: should be Priority.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => priorityChains.Count;
|
||||
|
||||
public PriorityItem<T> Enqueue(int priority, T data) // NOTE: should be Priority
|
||||
{
|
||||
// Find the existing chain for this priority, or create a new one
|
||||
// if one does not exist.
|
||||
PriorityChain<T> chain = GetChain(priority);
|
||||
|
||||
// Wrap the item in a PriorityItem so we can put it in our
|
||||
// linked list.
|
||||
PriorityItem<T> priorityItem = new PriorityItem<T>(data);
|
||||
|
||||
// Step 1: Append this to the end of the "sequential" linked list.
|
||||
InsertItemInSequentialChain(priorityItem, tail);
|
||||
|
||||
// Step 2: Append the item into the priority chain.
|
||||
InsertItemInPriorityChain(priorityItem, chain, chain.Tail);
|
||||
|
||||
return priorityItem;
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
// Get the max-priority chain.
|
||||
int count = priorityChains.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
PriorityChain<T> chain = priorityChains.Values[count - 1];
|
||||
Debug.Assert(chain != null, "PriorityQueue.Dequeue: a chain should exist.");
|
||||
|
||||
PriorityItem<T> item = chain.Head;
|
||||
Debug.Assert(item != null, "PriorityQueue.Dequeue: a priority item should exist.");
|
||||
|
||||
RemoveItem(item);
|
||||
|
||||
return item.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
T data = default(T);
|
||||
|
||||
// Get the max-priority chain.
|
||||
int count = priorityChains.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
PriorityChain<T> chain = priorityChains.Values[count - 1];
|
||||
Debug.Assert(chain != null, "PriorityQueue.Peek: a chain should exist.");
|
||||
|
||||
PriorityItem<T> item = chain.Head;
|
||||
Debug.Assert(item != null, "PriorityQueue.Peek: a priority item should exist.");
|
||||
|
||||
data = item.Data;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void RemoveItem(PriorityItem<T> item)
|
||||
{
|
||||
Debug.Assert(item != null, "PriorityQueue.RemoveItem: invalid item.");
|
||||
Debug.Assert(item.Chain != null, "PriorityQueue.RemoveItem: a chain should exist.");
|
||||
|
||||
PriorityChain<T> chain = item.Chain;
|
||||
|
||||
// Step 1: Remove the item from its priority chain.
|
||||
RemoveItemFromPriorityChain(item);
|
||||
|
||||
// Step 2: Remove the item from the sequential chain.
|
||||
RemoveItemFromSequentialChain(item);
|
||||
|
||||
// Note: we do not clean up empty chains on purpose to reduce churn.
|
||||
}
|
||||
|
||||
public void ChangeItemPriority(PriorityItem<T> item, int priority) // NOTE: should be Priority
|
||||
{
|
||||
// Remove the item from its current priority and insert it into
|
||||
// the new priority chain. Note that this does not change the
|
||||
// sequential ordering.
|
||||
|
||||
// Step 1: Remove the item from the priority chain.
|
||||
RemoveItemFromPriorityChain(item);
|
||||
|
||||
// Step 2: Insert the item into the new priority chain.
|
||||
// Find the existing chain for this priority, or create a new one
|
||||
// if one does not exist.
|
||||
PriorityChain<T> chain = GetChain(priority);
|
||||
InsertItemInPriorityChain(item, chain);
|
||||
}
|
||||
|
||||
private PriorityChain<T> GetChain(int priority) // NOTE: should be Priority
|
||||
{
|
||||
PriorityChain<T> chain = null;
|
||||
|
||||
int count = priorityChains.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
if (priority == priorityChains.Keys[0])
|
||||
{
|
||||
chain = priorityChains.Values[0];
|
||||
}
|
||||
else if (priority == priorityChains.Keys[count - 1])
|
||||
{
|
||||
chain = priorityChains.Values[count - 1];
|
||||
}
|
||||
else if (priority > priorityChains.Keys[0] &&
|
||||
priority < priorityChains.Keys[count - 1])
|
||||
{
|
||||
priorityChains.TryGetValue(priority, out chain);
|
||||
}
|
||||
}
|
||||
|
||||
if (chain == null)
|
||||
{
|
||||
if (cacheReusableChains.Count > 0)
|
||||
{
|
||||
chain = cacheReusableChains.Pop();
|
||||
chain.Priority = priority;
|
||||
}
|
||||
else
|
||||
{
|
||||
chain = new PriorityChain<T>(priority);
|
||||
}
|
||||
|
||||
priorityChains.Add(priority, chain);
|
||||
}
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
private void InsertItemInPriorityChain(PriorityItem<T> item, PriorityChain<T> chain)
|
||||
{
|
||||
// Scan along the sequential chain, in the previous direction,
|
||||
// looking for an item that is already in the new chain. We will
|
||||
// insert ourselves after the item we found. We can short-circuit
|
||||
// this search if the new chain is empty.
|
||||
if (chain.Head == null)
|
||||
{
|
||||
Debug.Assert(chain.Tail == null, "PriorityQueue.InsertItemInPriorityChain: both the head and the tail should be null.");
|
||||
InsertItemInPriorityChain(item, chain, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(chain.Tail != null, "PriorityQueue.InsertItemInPriorityChain: both the head and the tail should not be null.");
|
||||
|
||||
PriorityItem<T> after = null;
|
||||
|
||||
// Search backwards along the sequential chain looking for an
|
||||
// item already in this list.
|
||||
for (after = item.SequentialPrev; after != null; after = after.SequentialPrev)
|
||||
{
|
||||
if (after.Chain == chain)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
InsertItemInPriorityChain(item, chain, after);
|
||||
}
|
||||
}
|
||||
|
||||
internal void InsertItemInPriorityChain(PriorityItem<T> item, PriorityChain<T> chain, PriorityItem<T> after)
|
||||
{
|
||||
Debug.Assert(chain != null, "PriorityQueue.InsertItemInPriorityChain: a chain must be provided.");
|
||||
Debug.Assert(item.Chain == null && item.PriorityPrev == null && item.PriorityNext == null, "PriorityQueue.InsertItemInPriorityChain: item must not already be in a priority chain.");
|
||||
|
||||
item.Chain = chain;
|
||||
|
||||
if (after == null)
|
||||
{
|
||||
// Note: passing null for after means insert at the head.
|
||||
|
||||
if (chain.Head != null)
|
||||
{
|
||||
Debug.Assert(chain.Tail != null, "PriorityQueue.InsertItemInPriorityChain: both the head and the tail should not be null.");
|
||||
|
||||
chain.Head.PriorityPrev = item;
|
||||
item.PriorityNext = chain.Head;
|
||||
chain.Head = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(chain.Tail == null, "PriorityQueue.InsertItemInPriorityChain: both the head and the tail should be null.");
|
||||
|
||||
chain.Head = chain.Tail = item;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.PriorityPrev = after;
|
||||
|
||||
if (after.PriorityNext != null)
|
||||
{
|
||||
item.PriorityNext = after.PriorityNext;
|
||||
after.PriorityNext.PriorityPrev = item;
|
||||
after.PriorityNext = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(item.Chain.Tail == after, "PriorityQueue.InsertItemInPriorityChain: the chain's tail should be the item we are inserting after.");
|
||||
after.PriorityNext = item;
|
||||
chain.Tail = item;
|
||||
}
|
||||
}
|
||||
|
||||
chain.Count++;
|
||||
}
|
||||
|
||||
private void RemoveItemFromPriorityChain(PriorityItem<T> item)
|
||||
{
|
||||
Debug.Assert(item != null, "PriorityQueue.RemoveItemFromPriorityChain: invalid item.");
|
||||
Debug.Assert(item.Chain != null, "PriorityQueue.RemoveItemFromPriorityChain: a chain should exist.");
|
||||
|
||||
// Step 1: Fix up the previous link
|
||||
if (item.PriorityPrev != null)
|
||||
{
|
||||
Debug.Assert(item.Chain.Head != item, "PriorityQueue.RemoveItemFromPriorityChain: the head should not point to this item.");
|
||||
|
||||
item.PriorityPrev.PriorityNext = item.PriorityNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(item.Chain.Head == item, "PriorityQueue.RemoveItemFromPriorityChain: the head should point to this item.");
|
||||
|
||||
item.Chain.Head = item.PriorityNext;
|
||||
}
|
||||
|
||||
// Step 2: Fix up the next link
|
||||
if (item.PriorityNext != null)
|
||||
{
|
||||
Debug.Assert(item.Chain.Tail != item, "PriorityQueue.RemoveItemFromPriorityChain: the tail should not point to this item.");
|
||||
|
||||
item.PriorityNext.PriorityPrev = item.PriorityPrev;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(item.Chain.Tail == item, "PriorityQueue.RemoveItemFromPriorityChain: the tail should point to this item.");
|
||||
|
||||
item.Chain.Tail = item.PriorityPrev;
|
||||
}
|
||||
|
||||
// Step 3: cleanup
|
||||
item.PriorityPrev = item.PriorityNext = null;
|
||||
item.Chain.Count--;
|
||||
if (item.Chain.Count == 0)
|
||||
{
|
||||
if (item.Chain.Priority == priorityChains.Keys[priorityChains.Count - 1])
|
||||
{
|
||||
priorityChains.RemoveAt(priorityChains.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
priorityChains.Remove(item.Chain.Priority);
|
||||
}
|
||||
|
||||
if (cacheReusableChains.Count < 10)
|
||||
{
|
||||
item.Chain.Priority = int.MaxValue; // NOTE: should be Priority.Invalid
|
||||
cacheReusableChains.Push(item.Chain);
|
||||
}
|
||||
}
|
||||
|
||||
item.Chain = null;
|
||||
}
|
||||
|
||||
internal void InsertItemInSequentialChain(PriorityItem<T> item, PriorityItem<T> after)
|
||||
{
|
||||
Debug.Assert(item.SequentialPrev == null && item.SequentialNext == null, "PriorityQueue.InsertItemInSequentialChain: item must not already be in the sequential chain.");
|
||||
|
||||
if (after == null)
|
||||
{
|
||||
// Note: passing null for after means insert at the head.
|
||||
|
||||
if (head != null)
|
||||
{
|
||||
Debug.Assert(tail != null, "PriorityQueue.InsertItemInSequentialChain: both the head and the tail should not be null.");
|
||||
|
||||
head.SequentialPrev = item;
|
||||
item.SequentialNext = head;
|
||||
head = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(tail == null, "PriorityQueue.InsertItemInSequentialChain: both the head and the tail should be null.");
|
||||
|
||||
head = tail = item;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.SequentialPrev = after;
|
||||
|
||||
if (after.SequentialNext != null)
|
||||
{
|
||||
item.SequentialNext = after.SequentialNext;
|
||||
after.SequentialNext.SequentialPrev = item;
|
||||
after.SequentialNext = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(tail == after, "PriorityQueue.InsertItemInSequentialChain: the tail should be the item we are inserting after.");
|
||||
after.SequentialNext = item;
|
||||
tail = item;
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
private void RemoveItemFromSequentialChain(PriorityItem<T> item)
|
||||
{
|
||||
Debug.Assert(item != null, "PriorityQueue.RemoveItemFromSequentialChain: invalid item.");
|
||||
|
||||
// Step 1: Fix up the previous link
|
||||
if (item.SequentialPrev != null)
|
||||
{
|
||||
Debug.Assert(head != item, "PriorityQueue.RemoveItemFromSequentialChain: the head should not point to this item.");
|
||||
|
||||
item.SequentialPrev.SequentialNext = item.SequentialNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(head == item, "PriorityQueue.RemoveItemFromSequentialChain: the head should point to this item.");
|
||||
|
||||
head = item.SequentialNext;
|
||||
}
|
||||
|
||||
// Step 2: Fix up the next link
|
||||
if (item.SequentialNext != null)
|
||||
{
|
||||
Debug.Assert(tail != item, "PriorityQueue.RemoveItemFromSequentialChain: the tail should not point to this item.");
|
||||
|
||||
item.SequentialNext.SequentialPrev = item.SequentialPrev;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(tail == item, "PriorityQueue.RemoveItemFromSequentialChain: the tail should point to this item.");
|
||||
|
||||
tail = item.SequentialPrev;
|
||||
}
|
||||
|
||||
// Step 3: cleanup
|
||||
item.SequentialPrev = item.SequentialNext = null;
|
||||
count--;
|
||||
}
|
||||
|
||||
// Priority chains...
|
||||
private SortedList<int, PriorityChain<T>> priorityChains; // NOTE: should be Priority
|
||||
private Stack<PriorityChain<T>> cacheReusableChains;
|
||||
|
||||
// Sequential chain...
|
||||
private PriorityItem<T> head;
|
||||
private PriorityItem<T> tail;
|
||||
private int count;
|
||||
}
|
||||
|
||||
public class PriorityChain<T>
|
||||
{
|
||||
public PriorityChain(int priority) // NOTE: should be Priority
|
||||
{
|
||||
Priority = priority;
|
||||
}
|
||||
|
||||
public int Priority { get; set; } // NOTE: should be Priority
|
||||
public int Count { get; set; }
|
||||
public PriorityItem<T> Head { get; set; }
|
||||
public PriorityItem<T> Tail { get; set; }
|
||||
}
|
||||
|
||||
public class PriorityItem<T>
|
||||
{
|
||||
public PriorityItem(T data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public T Data { get; }
|
||||
public bool IsQueued => Chain != null;
|
||||
|
||||
// Note: not used
|
||||
// public int Priority { get { return _chain.Priority; } } // NOTE: should be Priority
|
||||
|
||||
internal PriorityItem<T> SequentialPrev { get; set; }
|
||||
internal PriorityItem<T> SequentialNext { get; set; }
|
||||
|
||||
internal PriorityChain<T> Chain { get; set; }
|
||||
internal PriorityItem<T> PriorityPrev { get; set; }
|
||||
internal PriorityItem<T> PriorityNext { get; set; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user