#region License
/* Copyright (c) 2006 Leslie Sanford
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Contact
/*
* Leslie Sanford
* Email: jabberdabber@hotmail.com
*/
#endregion
using System;
using System.Collections;
namespace LSCollections
{
///
/// Represents a collection of key-and-value pairs.
///
///
/// The SkipList class is an implementation of the IDictionary interface. It
/// is based on the data structure created by William Pugh.
///
public class SkipList : IDictionary
{
#region SkipList Members
#region Constants
// Maximum level any node in a skip list can have
private const int MaxLevel = 32;
// Probability factor used to determine the node level
private const double Probability = 0.5;
#endregion
#region Fields
// The skip list header. It also serves as the NIL node.
private Node header = new Node(MaxLevel);
// Comparer for comparing keys.
private IComparer comparer;
// Random number generator for generating random node levels.
private Random random = new Random();
// Current maximum list level.
private int listLevel;
// Current number of elements in the skip list.
private int count;
// Version of the skip list. Used for validation checks with
// enumerators.
private long version = 0;
#endregion
///
/// Initializes a new instance of the SkipList class that is empty and
/// is sorted according to the IComparable interface implemented by
/// each key added to the SkipList.
///
///
/// Each key must implement the IComparable interface to be capable of
/// comparisons with every other key in the SortedList. The elements
/// are sorted according to the IComparable implementation of each key
/// added to the SkipList.
///
public SkipList()
{
// Initialize the skip list.
Initialize();
}
///
/// Initializes a new instance of the SkipList class that is empty and
/// is sorted according to the specified IComparer interface.
///
///
/// The IComparer implementation to use when comparing keys.
///
///
/// The elements are sorted according to the specified IComparer
/// implementation. If comparer is a null reference, the IComparable
/// implementation of each key is used; therefore, each key must
/// implement the IComparable interface to be capable of comparisons
/// with every other key in the SkipList.
///
public SkipList(IComparer comparer)
{
// Initialize comparer with the client provided comparer.
this.comparer = comparer;
// Initialize the skip list.
Initialize();
}
///
/// Destructor.
///
~SkipList()
{
Clear();
}
#region Private Helper Methods
///
/// Initializes the SkipList.
///
private void Initialize()
{
listLevel = 1;
count = 0;
// When the list is empty, make sure all forward references in the
// header point back to the header. This is important because the
// header is used as the sentinel to mark the end of the skip list.
for(int i = 0; i < MaxLevel; i++)
{
header.forward[i] = header;
}
}
///
/// Returns a level value for a new SkipList node.
///
///
/// The level value for a new SkipList node.
///
private int GetNewLevel()
{
int level = 1;
// Determines the next node level.
while(random.NextDouble() < Probability && level < MaxLevel &&
level <= listLevel)
{
level++;
}
return level;
}
///
/// Searches for the specified key.
///
///
/// The key to search for.
///
///
/// Returns true if the specified key is in the SkipList.
///
private bool Search(object key)
{
Node curr;
Node[] dummy = new Node[MaxLevel];
return Search(key, out curr, dummy);
}
///
/// Searches for the specified key.
///
///
/// The key to search for.
///
///
/// A SkipList node to hold the results of the search.
///
///
/// Returns true if the specified key is in the SkipList.
///
private bool Search(object key, out Node curr)
{
Node[] dummy = new Node[MaxLevel];
return Search(key, out curr, dummy);
}
///
/// Searches for the specified key.
///
///
/// The key to search for.
///
///
/// An array of nodes holding references to the places in the SkipList
/// search in which the search dropped down one level.
///
///
/// Returns true if the specified key is in the SkipList.
///
private bool Search(object key, Node[] update)
{
Node curr;
return Search(key, out curr, update);
}
///
/// Searches for the specified key.
///
///
/// The key to search for.
///
///
/// A SkipList node to hold the results of the search.
///
///
/// An array of nodes holding references to the places in the SkipList
/// search in which the search dropped down one level.
///
///
/// Returns true if the specified key is in the SkipList.
///
private bool Search(object key, out Node curr, Node[] update)
{
// Make sure key isn't null.
if(key == null)
{
throw new ArgumentNullException("An attempt was made to pass a null key to a SkipList.");
}
bool result;
// Check to see if we will search with a comparer.
if(comparer != null)
{
result = SearchWithComparer(key, out curr, update);
}
// Else we're using the IComparable interface.
else
{
result = SearchWithComparable(key, out curr, update);
}
return result;
}
///
/// Search for the specified key using a comparer.
///
///
/// The key to search for.
///
///
/// A SkipList node to hold the results of the search.
///
///
/// An array of nodes holding references to the places in the SkipList
/// search in which the search dropped down one level.
///
///
/// Returns true if the specified key is in the SkipList.
///
private bool SearchWithComparer(object key, out Node curr,
Node[] update)
{
bool found = false;
// Start from the beginning of the skip list.
curr = header;
// Work our way down from the top of the skip list to the bottom.
for(int i = listLevel - 1; i >= 0; i--)
{
// While we haven't reached the end of the skip list and the
// current key is less than the search key.
while(curr.forward[i] != header &&
comparer.Compare(curr.forward[i].Entry.Key, key) < 0)
{
// Move forward in the skip list.
curr = curr.forward[i];
}
// Keep track of each node where we move down a level. This
// will be used later to rearrange node references when
// inserting or deleting a new element.
update[i] = curr;
}
// Move ahead in the skip list. If the new key doesn't already
// exist in the skip list, this should put us at either the end of
// the skip list or at a node with a key greater than the search key.
// If the new key already exists in the skip list, this should put
// us at a node with a key equal to the search key.
curr = curr.forward[0];
// If we haven't reached the end of the skip list and the
// current key is equal to the search key.
if(curr != header && comparer.Compare(key, curr.Entry.Key) == 0)
{
// Indicate that we've found the search key.
found = true;
}
return found;
}
///
/// Search for the specified key using the IComparable interface
/// implemented by each key.
///
///
/// The key to search for.
///
///
/// A SkipList node to hold the results of the search.
///
///
/// An array of nodes holding references to the places in the SkipList
/// search in which the search dropped down one level.
///
///
/// Returns true if the specified key is in the SkipList.
///
///
/// Assumes each key inserted into the SkipList implements the
/// IComparable interface.
///
/// If the specified key is in the SkipList, the curr parameter will
/// reference the node with the key. If the specified key is not in the
/// SkipList, the curr paramater will either hold the node with the
/// first key value greater than the specified key or it will have the
/// same value as the header indicating that the search reached the end
/// of the SkipList.
///
private bool SearchWithComparable(object key, out Node curr,
Node[] update)
{
// Make sure key is comparable.
if(!(key is IComparable))
{
throw new ArgumentException("The SkipList was set to use the IComparable interface and an attempt was made to add a key that does not support this interface.");
}
bool found = false;
IComparable comp;
// Begin at the start of the skip list.
curr = header;
// Work our way down from the top of the skip list to the bottom.
for(int i = listLevel - 1; i >= 0; i--)
{
// Get the comparable interface for the current key.
comp = (IComparable)curr.forward[i].Key;
// While we haven't reached the end of the skip list and the
// current key is less than the search key.
while(curr.forward[i] != header && comp.CompareTo(key) < 0)
{
// Move forward in the skip list.
curr = curr.forward[i];
// Get the comparable interface for the current key.
comp = (IComparable)curr.forward[i].Key;
}
// Keep track of each node where we move down a level. This
// will be used later to rearrange node references when
// inserting a new element.
update[i] = curr;
}
// Move ahead in the skip list. If the new key doesn't already
// exist in the skip list, this should put us at either the end of
// the skip list or at a node with a key greater than the search key.
// If the new key already exists in the skip list, this should put
// us at a node with a key equal to the search key.
curr = curr.forward[0];
// Get the comparable interface for the current key.
comp = (IComparable)curr.Key;
// If we haven't reached the end of the skip list and the
// current key is equal to the search key.
if(curr != header && comp.CompareTo(key) == 0)
{
// Indicate that we've found the search key.
found = true;
}
return found;
}
///
/// Inserts a key/value pair into the SkipList.
///
///
/// The key to insert into the SkipList.
///
///
/// The value to insert into the SkipList.
///
///
/// An array of nodes holding references to places in the SkipList in
/// which the search for the place to insert the new key/value pair
/// dropped down one level.
///
private void Insert(object key, object val, Node[] update)
{
// Get the level for the new node.
int newLevel = GetNewLevel();
// If the level for the new node is greater than the skip list
// level.
if(newLevel > listLevel)
{
// Make sure our update references above the current skip list
// level point to the header.
for(int i = listLevel; i < newLevel; i++)
{
update[i] = header;
}
// The current skip list level is now the new node level.
listLevel = newLevel;
}
// Create the new node.
Node newNode = new Node(newLevel, key, val);
// Insert the new node into the skip list.
for(int i = 0; i < newLevel; i++)
{
// The new node forward references are initialized to point to
// our update forward references which point to nodes further
// along in the skip list.
newNode.forward[i] = update[i].forward[i];
// Take our update forward references and point them towards
// the new node.
update[i].forward[i] = newNode;
}
// Keep track of the number of nodes in the skip list.
count++;
// Indicate that the skip list has changed.
version++;
}
#endregion
#endregion
#region Node Class
///
/// Represents a node in the SkipList.
///
private class Node : IDisposable
{
#region Fields
// References to nodes further along in the skip list.
public Node[] forward;
// Node key.
private Object key;
// Node value.
private Object val;
#endregion
///
/// Initializes an instant of a Node with its node level.
///
///
/// The node level.
///
public Node(int level)
{
forward = new Node[level];
}
///
/// Initializes an instant of a Node with its node level and
/// key/value pair.
///
///
/// The node level.
///
///
/// The key for the node.
///
///
/// The value for the node.
///
public Node(int level, object key, object val)
{
forward = new Node[level];
Key = key;
Value = val;
}
///
/// Key property.
///
public Object Key
{
get
{
return key;
}
set
{
key = value;
}
}
///
/// Value property.
///
public Object Value
{
get
{
return val;
}
set
{
val = value;
}
}
///
/// Node dictionary Entry property - contains key/value pair.
///
public DictionaryEntry Entry
{
get
{
return new DictionaryEntry(Key, Value);
}
}
#region IDisposable Members
///
/// Disposes the Node.
///
public void Dispose()
{
for(int i = 0; i < forward.Length; i++)
{
forward[i] = null;
}
}
#endregion
}
#endregion
#region SkipListEnumerator Class
///
/// Enumerates the elements of a skip list.
///
private class SkipListEnumerator : IDictionaryEnumerator
{
#region SkipListEnumerator Members
#region Fields
// The skip list to enumerate.
private SkipList list;
// The current node.
private Node current;
// The version of the skip list we are enumerating.
private long version;
// Keeps track of previous move result so that we can know
// whether or not we are at the end of the skip list.
private bool moveResult = true;
#endregion
///
/// Initializes an instance of a SkipListEnumerator.
///
///
public SkipListEnumerator(SkipList list)
{
this.list = list;
version = list.version;
current = list.header;
}
#endregion
#region IDictionaryEnumerator Members
///
/// Gets both the key and the value of the current dictionary
/// entry.
///
public DictionaryEntry Entry
{
get
{
DictionaryEntry entry;
// Make sure the skip list hasn't been modified since the
// enumerator was created.
if(version != list.version)
{
throw new InvalidOperationException("SkipListEnumerator is no longer valid. The SkipList has been modified since the creation of this enumerator.");
}
// Make sure we are not before the beginning or beyond the
// end of the skip list.
else if(current == list.header)
{
throw new InvalidOperationException("SkipListEnumerator is no longer valid. The SkipList has been modified since the creation of this enumerator.");
}
// Finally, all checks have passed. Get the current entry.
else
{
entry = current.Entry;
}
return entry;
}
}
///
/// Gets the key of the current dictionary entry.
///
public object Key
{
get
{
object key = Entry.Key;
return key;
}
}
///
/// Gets the value of the current dictionary entry.
///
public object Value
{
get
{
object val = Entry.Value;
return val;
}
}
#endregion
#region IEnumerator Members
///
/// Advances the enumerator to the next element of the skip list.
///
///
/// true if the enumerator was successfully advanced to the next
/// element; false if the enumerator has passed the end of the
/// skip list.
///
public bool MoveNext()
{
// Make sure the skip list hasn't been modified since the
// enumerator was created.
if(version == list.version)
{
// If the result of the previous move operation was true
// we can still move forward in the skip list.
if(moveResult)
{
// Move forward in the skip list.
current = current.forward[0];
// If we are at the end of the skip list.
if(current == list.header)
{
// Indicate that we've reached the end of the skip
// list.
moveResult = false;
}
}
}
// Else this version of the enumerator doesn't match that of
// the skip list. The skip list has been modified since the
// creation of the enumerator.
else
{
throw new InvalidOperationException("SkipListEnumerator is no longer valid. The SkipList has been modified since the creation of this enumerator.");
}
return moveResult;
}
///
/// Sets the enumerator to its initial position, which is before
/// the first element in the skip list.
///
public void Reset()
{
// Make sure the skip list hasn't been modified since the
// enumerator was created.
if(version == list.version)
{
current = list.header;
moveResult = true;
}
// Else this version of the enumerator doesn't match that of
// the skip list. The skip list has been modified since the
// creation of the enumerator.
else
{
throw new InvalidOperationException("SkipListEnumerator is no longer valid. The SkipList has been modified since the creation of this enumerator.");
}
}
///
/// Gets the current element in the skip list.
///
public object Current
{
get
{
return Entry;
}
}
#endregion
}
#endregion
#region IDictionary Members
///
/// Adds an element with the provided key and value to the SkipList.
///
///
/// The Object to use as the key of the element to add.
///
///
/// The Object to use as the value of the element to add.
///
public void Add(object key, object value)
{
Node[] update = new Node[MaxLevel];
// If key does not already exist in the skip list.
if(!Search(key, update))
{
// Inseart key/value pair into the skip list.
Insert(key, value, update);
}
// Else throw an exception. The IDictionary Add method throws an
// exception if an attempt is made to add a key that already
// exists in the skip list.
else
{
throw new ArgumentException("An attempt was made to add an element in which the key of the element already exists in the SkipList.");
}
}
///
/// Removes all elements from the SkipList.
///
public void Clear()
{
// Start at the beginning of the skip list.
Node curr = header.forward[0];
Node prev;
// While we haven't reached the end of the skip list.
while(curr != header)
{
// Keep track of the previous node.
prev = curr;
// Move forward in the skip list.
curr = curr.forward[0];
// Dispose of the previous node.
prev.Dispose();
}
// Initialize skip list and indicate that it has been changed.
Initialize();
version++;
}
///
/// Determines whether the SkipList contains an element with the
/// specified key.
///
///
/// The key to locate in the SkipList.
///
///
/// true if the SkipList contains an element with the key; otherwise,
/// false.
///
public bool Contains(object key)
{
return Search(key);
}
///
/// Returns an IDictionaryEnumerator for the SkipList.
///
///
/// An IDictionaryEnumerator for the SkipList.
///
public IDictionaryEnumerator GetEnumerator()
{
return new SkipListEnumerator(this);
}
///
/// Removes the element with the specified key from the SkipList.
///
///
/// The key of the element to remove.
///
public void Remove(object key)
{
Node[] update = new Node[MaxLevel];
Node curr;
if(Search(key, out curr, update))
{
// Take the forward references that point to the node to be
// removed and reassign them to the nodes that come after it.
for(int i = 0; i < listLevel &&
update[i].forward[i] == curr; i++)
{
update[i].forward[i] = curr.forward[i];
}
curr.Dispose();
// After removing the node, we may need to lower the current
// skip list level if the node had the highest level of all of
// the nodes.
while(listLevel > 1 && header.forward[listLevel - 1] == header)
{
listLevel--;
}
// Keep track of the number of nodes.
count--;
// Indicate that the skip list has changed.
version++;
}
}
///
/// Gets a value indicating whether the SkipList has a fixed size.
///
public bool IsFixedSize
{
get
{
return false;
}
}
///
/// Gets a value indicating whether the IDictionary is read-only.
///
public bool IsReadOnly
{
get
{
return false;
}
}
///
/// Gets or sets the element with the specified key. This is the
/// indexer for the SkipList.
///
public object this[object key]
{
get
{
object val = null;
Node curr;
if(Search(key, out curr))
{
val = curr.Entry.Value;
}
return val;
}
set
{
Node[] update = new Node[MaxLevel];
Node curr;
// If the search key already exists in the skip list.
if(Search(key, out curr, update))
{
// Replace the current value with the new value.
curr.Value = value;
// Indicate that the skip list has changed.
version++;
}
// Else the key doesn't exist in the skip list.
else
{
// Insert the key and value into the skip list.
Insert(key, value, update);
}
}
}
///
/// Gets an ICollection containing the keys of the SkipList.
///
public ICollection Keys
{
get
{
// Start at the beginning of the skip list.
Node curr = header.forward[0];
// Create a collection to hold the keys.
ArrayList collection = new ArrayList();
// While we haven't reached the end of the skip list.
while(curr != header)
{
// Add the key to the collection.
collection.Add(curr.Entry.Key);
// Move forward in the skip list.
curr = curr.forward[0];
}
return collection;
}
}
///
/// Gets an ICollection containing the values of the SkipList.
///
public ICollection Values
{
get
{
// Start at the beginning of the skip list.
Node curr = header.forward[0];
// Create a collection to hold the values.
ArrayList collection = new ArrayList();
// While we haven't reached the end of the skip list.
while(curr != header)
{
// Add the value to the collection.
collection.Add(curr.Entry.Value);
// Move forward in the skip list.
curr = curr.forward[0];
}
return collection;
}
}
#endregion
#region ICollection Members
///
/// Copies the elements of the SkipList to an Array, starting at a
/// particular Array index.
///
///
/// The one-dimensional Array that is the destination of the elements
/// copied from SkipList.
///
///
/// The zero-based index in array at which copying begins.
///
public void CopyTo(Array array, int index)
{
// Make sure array isn't null.
if(array == null)
{
throw new ArgumentNullException("An attempt was made to pass a null array to the CopyTo method of a SkipList.");
}
// Make sure index is not negative.
else if(index < 0)
{
throw new ArgumentOutOfRangeException("An attempt was made to pass an out of range index to the CopyTo method of a SkipList.");
}
// Array bounds checking.
else if(index >= array.Length)
{
throw new ArgumentException("An attempt was made to pass an out of range index to the CopyTo method of a SkipList.");
}
// Make sure that the number of elements in the skip list is not
// greater than the available space from index to the end of the
// array.
else if((array.Length - index) < Count)
{
throw new ArgumentException("An attempt was made to pass an out of range index to the CopyTo method of a SkipList.");
}
// Else copy elements from skip list into array.
else
{
// Start at the beginning of the skip list.
Node curr = header.forward[0];
// While we haven't reached the end of the skip list.
while(curr != header)
{
// Copy current value into array.
array.SetValue(curr.Entry.Value, index);
// Move forward in the skip list and array.
curr = curr.forward[0];
index++;
}
}
}
///
/// Gets the number of elements contained in the SkipList.
///
public int Count
{
get
{
return count;
}
}
///
/// Gets a value indicating whether access to the SkipList is
/// synchronized (thread-safe).
///
public bool IsSynchronized
{
get
{
return false;
}
}
///
/// Gets an object that can be used to synchronize access to the
/// SkipList.
///
public object SyncRoot
{
get
{
return this;
}
}
#endregion
#region IEnumerable Members
///
/// Returns an enumerator that can iterate through the SkipList.
///
///
/// An IEnumerator that can be used to iterate through the collection.
///
IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return new SkipListEnumerator(this);
}
#endregion
}
}