From a52cf5a077d67b8108d9455c4d9dacafe1fa1ed5 Mon Sep 17 00:00:00 2001 From: Alex Macocian Date: Thu, 20 Jun 2019 03:52:06 +0200 Subject: [PATCH] Skip list implementation --- SystemExtensions/Collections/SkipList.cs | 241 ++++++++++++++++++ SystemExtensions/SystemExtensions.csproj | 1 + .../Collections/SkipListTests.cs | 128 ++++++++++ .../SystemExtensionsTests.csproj | 1 + 4 files changed, 371 insertions(+) create mode 100644 SystemExtensions/Collections/SkipList.cs create mode 100644 SystemExtensionsTests/Collections/SkipListTests.cs diff --git a/SystemExtensions/Collections/SkipList.cs b/SystemExtensions/Collections/SkipList.cs new file mode 100644 index 0000000..aa970e0 --- /dev/null +++ b/SystemExtensions/Collections/SkipList.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SystemExtensions.Collections +{ + /// + /// Skip list implementation. + /// + /// Provided type. + public class SkipList : ICollection where T : IComparable + { + #region Fields + private class NodeSet + { + public TKey Key; + public int Level; + public NodeSet[] Next; + + public NodeSet(TKey key, int level) + { + this.Key = key; + this.Level = level; + Next = new NodeSet[level + 1]; + } + } + private int count; + private Random random; + private NodeSet head; + private NodeSet end; + private int maxLevel = 10; + private int level; + #endregion + #region Properties + /// + /// Number of elements in the list. + /// + public int Count { get => count; } + /// + /// Specifies if the collection can be modified. + /// + public bool IsReadOnly { get; set; } + #endregion + #region Constructors + /// + /// Creates a new instance of SkipList collection. + /// + /// Maximum level of the skip list. + public SkipList(int maxLevel = 10) + { + this.maxLevel = maxLevel; + random = new Random(); + head = new NodeSet(default(T), maxLevel); + end = head; + for(int i = 0; i <= maxLevel; i++) + { + head.Next[i] = end; + } + } + + #endregion + #region Public Methods + /// + /// Adds an item to the collection. + /// + /// Item to be added. + public void Add(T item) + { + NodeSet curNode = head; + int newLevel = 0; + while (random.Next(0, 2) > 0 && newLevel < maxLevel) + { + newLevel++; + } + if (newLevel > level) + { + level = newLevel; + } + NodeSet newNode = new NodeSet(item, newLevel); + for (var i = 0; i <= newLevel; i++) + { + if(i > curNode.Level) + { + curNode = head; + } + while (curNode.Next[i] != end && curNode.Next[i].Key.CompareTo(item) < 0) + { + curNode = curNode.Next[i]; + } + newNode.Next[i] = curNode.Next[i]; + curNode.Next[i] = newNode; + } + count++; + } + /// + /// Removes provided item from the collection. + /// + /// Item to be removed. + /// True if removal was successful. + public bool Remove(T item) + { + bool removed = false; + NodeSet curNode = head; + for (var i = 0; i <= maxLevel; i++) + { + if(i > curNode.Level) + { + curNode = head; + } + while (curNode.Next[i] != end && curNode.Next[i].Key.CompareTo(item) < 0) + { + curNode = curNode.Next[i]; + } + if (curNode.Next[i].Key.CompareTo(item) == 0) //Item is present on this level + { + curNode.Next[i] = curNode.Next[i].Next[i]; + removed = true; + } + else + { + break; + } + } + if (removed) + { + count--; + return true; + } + else + { + return false; + } + } + /// + /// Clears the collection. + /// + public void Clear() + { + for(int i = 0; i < maxLevel; i++) + { + head.Next[i] = end; + } + count = 0; + } + /// + /// Checks if item is present in the collection. + /// + /// Item to be checked. + /// True if item is present in the collection. + public bool Contains(T item) + { + if(Find(item) != null) + { + return true; + } + return false; + } + /// + /// Copies the skip list contents onto the provided array. + /// + /// Array to hold the values from the list. + /// Index to start insertion in the array. + public void CopyTo(T[] array, int arrayIndex) + { + NodeSet node = head.Next[0]; + while(node != end) + { + array[arrayIndex] = node.Key; + arrayIndex++; + node = node.Next[0]; + } + } + /// + /// Copies the elements from the collection into an array. + /// + /// Array filled with elements from the collection. + public T[] ToArray() + { + T[] array = new T[count]; + int index = 0; + NodeSet curNode = head.Next[0]; + while(curNode != end) + { + array[index] = curNode.Key; + index++; + curNode = curNode.Next[0]; + } + return array; + } + /// + /// Enumerator that iterates over the collection. + /// + /// + public IEnumerator GetEnumerator() + { + NodeSet curNode = head.Next[0]; + while(curNode != end) + { + yield return curNode.Key; + curNode = curNode.Next[0]; + } + } + #endregion + #region Private Methods + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + private NodeSet Find(T key) + { + NodeSet curNode = head; + + for(int i = level; i >= 0; i--) + { + while(curNode.Next[i] != end) + { + if(curNode.Next[i].Key.CompareTo(key) > 0) + { + break; + } + else if(curNode.Next[i].Key.CompareTo(key) == 0) + { + return curNode.Next[i]; + } + curNode = curNode.Next[i]; + } + } + + curNode = curNode.Next[0]; + if(curNode != end && curNode.Key.CompareTo(key) == 0) + { + return curNode; + } + return null; + } + #endregion + } +} diff --git a/SystemExtensions/SystemExtensions.csproj b/SystemExtensions/SystemExtensions.csproj index 84b461e..45ce300 100644 --- a/SystemExtensions/SystemExtensions.csproj +++ b/SystemExtensions/SystemExtensions.csproj @@ -50,6 +50,7 @@ + diff --git a/SystemExtensionsTests/Collections/SkipListTests.cs b/SystemExtensionsTests/Collections/SkipListTests.cs new file mode 100644 index 0000000..7c6c427 --- /dev/null +++ b/SystemExtensionsTests/Collections/SkipListTests.cs @@ -0,0 +1,128 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SystemExtensions.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SystemExtensions.Collections.Tests +{ + [TestClass()] + public class SkipListTests + { + SkipList skipList = new SkipList(); + [TestMethod()] + public void SkipListTest() + { + SkipList skipList = new SkipList(); + } + + [TestMethod()] + public void SkipListTest2() + { + SkipList skipList = new SkipList(30); + } + + [TestMethod()] + public void AddTest() + { + for(int i = 0; i < 200; i++) + { + skipList.Add(i); + } + } + + [TestMethod()] + public void ClearTest() + { + for (int i = 0; i < 200; i++) + { + skipList.Add(i); + } + skipList.Clear(); + if(skipList.Count > 0) + { + Assert.Fail(); + } + } + + [TestMethod()] + public void ContainsTest() + { + for (int i = 0; i < 200; i++) + { + skipList.Add(i); + } + + if (!skipList.Contains(50)) + { + Assert.Fail(); + } + } + + [TestMethod()] + public void CopyToTest() + { + for (int i = 0; i < 200; i++) + { + skipList.Add(i); + } + int[] array = new int[skipList.Count]; + skipList.CopyTo(array, 0); + for (int i = 0; i < 200; i++) + { + if(array[i] != i) + { + Assert.Fail(); + } + } + } + + [TestMethod()] + public void ToArrayTest() + { + for (int i = 0; i < 200; i++) + { + skipList.Add(i); + } + int[] array = skipList.ToArray(); + + for (int i = 0; i < 200; i++) + { + if(array[i] != i) + { + Assert.Fail(); + } + } + } + + [TestMethod()] + public void GetEnumeratorTest() + { + for (int i = 0; i < 200; i++) + { + skipList.Add(i); + } + + foreach(int i in skipList) + { + System.Diagnostics.Debug.WriteLine(i); + } + } + + [TestMethod()] + public void RemoveTest() + { + for (int i = 0; i < 200; i++) + { + skipList.Add(i); + } + skipList.Remove(50); + if (skipList.Contains(50)) + { + Assert.Fail(); + } + } + } +} \ No newline at end of file diff --git a/SystemExtensionsTests/SystemExtensionsTests.csproj b/SystemExtensionsTests/SystemExtensionsTests.csproj index 4ba94d6..be24684 100644 --- a/SystemExtensionsTests/SystemExtensionsTests.csproj +++ b/SystemExtensionsTests/SystemExtensionsTests.csproj @@ -60,6 +60,7 @@ +