#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 } }