diff --git a/HW4/HW4.sln b/HW4/HW4.sln
new file mode 100644
index 0000000..7b6430a
--- /dev/null
+++ b/HW4/HW4.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkipList", "SkipList\SkipList.csproj", "{CE9E0C67-54C7-4201-A3DC-23973756923D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkipListTests", "SkipListTests\SkipListTests.csproj", "{25793A21-4C06-4F61-902A-E9F69D44B820}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CE9E0C67-54C7-4201-A3DC-23973756923D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CE9E0C67-54C7-4201-A3DC-23973756923D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CE9E0C67-54C7-4201-A3DC-23973756923D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CE9E0C67-54C7-4201-A3DC-23973756923D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {25793A21-4C06-4F61-902A-E9F69D44B820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {25793A21-4C06-4F61-902A-E9F69D44B820}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {25793A21-4C06-4F61-902A-E9F69D44B820}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {25793A21-4C06-4F61-902A-E9F69D44B820}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/HW4/SkipList/SkipList.cs b/HW4/SkipList/SkipList.cs
new file mode 100644
index 0000000..edca589
--- /dev/null
+++ b/HW4/SkipList/SkipList.cs
@@ -0,0 +1,292 @@
+//
+// Copyright (c) khusainovilas. All rights reserved.
+//
+
+namespace SkipList;
+
+using System.Collections;
+
+///
+/// Skip list implementation.
+///
+/// Type of elements in the list.
+public class SkipList : IList
+ where T : IComparable
+{
+ private const int MaxLevel = 16;
+ private readonly Random random = new();
+ private readonly SkipListNode sentinel = new(default, null, null);
+ private SkipListNode header;
+ private SkipListNode baseHeader;
+ private int currentVersion;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SkipList()
+ {
+ this.baseHeader = new SkipListNode(default, this.sentinel, this.sentinel);
+ var curr = this.baseHeader;
+ for (var level = 0; level < MaxLevel; level++)
+ {
+ curr = new SkipListNode(default, this.sentinel, curr);
+ }
+
+ this.header = curr;
+ }
+
+ ///
+ /// Initializes a new instance of the class with elements from a collection.
+ ///
+ /// The collection to initialize from.
+ public SkipList(IEnumerable collection)
+ : this()
+ {
+ ArgumentNullException.ThrowIfNull(collection);
+ foreach (var element in collection)
+ {
+ this.Add(element);
+ }
+ }
+
+ ///
+ public int Count { get; private set; }
+
+ ///
+ public bool IsReadOnly => false;
+
+ ///
+ public T this[int index]
+ {
+ get
+ {
+ if (index < 0 || index >= this.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ var curr = this.baseHeader.Next ?? throw new SkipListException("Cannot access item in empty skip list.");
+ for (var i = 0; i < index; i++)
+ {
+ curr = curr.Next ?? throw new InvalidOperationException("Index exceeds list bounds.");
+ }
+
+ return curr.Value ?? throw new InvalidOperationException("Null value encountered at index.");
+ }
+ set => throw new NotImplementedException();
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ var curr = this.baseHeader.Next;
+ var startVersion = this.currentVersion;
+ while (curr != this.sentinel && curr != null)
+ {
+ if (startVersion != this.currentVersion)
+ {
+ throw new InvalidOperationException("Collection modified during enumeration.");
+ }
+
+ yield return curr.Value ?? throw new NullReferenceException("Element value is null.");
+ curr = curr.Next;
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+
+ ///
+ public void Add(T item)
+ {
+ ArgumentNullException.ThrowIfNull(item);
+ var level = this.GenerateRandomLevel();
+ var curr = this.header;
+ var updateNodes = new SkipListNode[level];
+ for (var i = 0; i <= MaxLevel - level; i++)
+ {
+ curr = curr.Down ?? curr;
+ }
+
+ for (var lvl = level - 1; lvl >= 0; lvl--)
+ {
+ while (curr != null && curr.Next != null && curr.Next != this.sentinel && curr.Next.Value != null &&
+ curr.Next.Value.CompareTo(item) < 0)
+ {
+ curr = curr.Next;
+ }
+
+ updateNodes[lvl] = curr ?? throw new InvalidOperationException("Cannot add null to skip list.");
+ curr = curr.Down;
+ }
+
+ SkipListNode? lower = null;
+ for (var lvl = 0; lvl < level; lvl++)
+ {
+ var newNode = new SkipListNode(item, updateNodes[lvl].Next, lower);
+ updateNodes[lvl].Next = newNode;
+ lower = newNode;
+ }
+
+ this.Count++;
+ this.currentVersion++;
+ }
+
+ ///
+ public void Clear()
+ {
+ this.baseHeader = new SkipListNode(default, this.sentinel, this.sentinel);
+ var curr = this.baseHeader;
+ for (var level = 1; level < MaxLevel; level++)
+ {
+ curr = new SkipListNode(default, this.sentinel, curr);
+ }
+
+ this.header = curr;
+ this.Count = 0;
+ this.currentVersion++;
+ }
+
+ ///
+ public bool Contains(T item)
+ {
+ ArgumentNullException.ThrowIfNull(item);
+ var curr = this.header;
+ while (curr != null)
+ {
+ while (curr.Next != null && curr.Next != this.sentinel &&
+ curr.Next.Value != null &&
+ curr.Next.Value.CompareTo(item) < 0)
+ {
+ curr = curr.Next;
+ }
+
+ if (curr.Next != null && curr.Next != this.sentinel &&
+ curr.Next.Value != null &&
+ curr.Next.Value.CompareTo(item) == 0)
+ {
+ return true;
+ }
+
+ curr = curr.Down;
+ }
+
+ return false;
+ }
+
+ ///
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ ArgumentNullException.ThrowIfNull(array);
+ if (arrayIndex < 0 || arrayIndex >= array.Length || arrayIndex + this.Count > array.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex));
+ }
+
+ var curr = this.baseHeader.Next;
+ var idx = arrayIndex;
+ while (curr != this.sentinel && curr != null)
+ {
+ array[idx++] = curr.Value ?? throw new InvalidOperationException("Null element in list.");
+ curr = curr.Next;
+ }
+ }
+
+ ///
+ public bool Remove(T item)
+ {
+ ArgumentNullException.ThrowIfNull(item);
+ var curr = this.header;
+ var removed = false;
+ while (curr != null)
+ {
+ while (curr.Next != null && curr.Next != this.sentinel &&
+ curr.Next.Value != null &&
+ curr.Next.Value.CompareTo(item) < 0)
+ {
+ curr = curr.Next;
+ }
+
+ if (curr.Next != null && curr.Next != this.sentinel &&
+ curr.Next.Value != null &&
+ curr.Next.Value.CompareTo(item) == 0)
+ {
+ curr.Next = curr.Next.Next;
+ removed = true;
+ }
+
+ curr = curr.Down;
+ }
+
+ if (removed)
+ {
+ this.Count--;
+ this.currentVersion++;
+ }
+
+ return removed;
+ }
+
+ ///
+ public int IndexOf(T item)
+ {
+ ArgumentNullException.ThrowIfNull(item);
+ var idx = 0;
+ var curr = this.baseHeader.Next;
+ while (curr != null && curr != this.sentinel)
+ {
+ if (curr.Value != null && curr.Value.CompareTo(item) == 0)
+ {
+ return idx;
+ }
+
+ if (curr.Value != null && curr.Value.CompareTo(item) > 0)
+ {
+ return -1;
+ }
+
+ curr = curr.Next;
+ idx++;
+ }
+
+ return -1;
+ }
+
+ ///
+ public void Insert(int index, T item)
+ {
+ throw new NotSupportedException("Insertion at specific index not supported in sorted skip list.");
+ }
+
+ ///
+ public void RemoveAt(int index)
+ {
+ if (index < 0 || index >= this.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ var item = this[index];
+ this.Remove(item);
+ }
+
+ private int GenerateRandomLevel()
+ {
+ var lvl = 1;
+ while (this.random.NextDouble() < 0.5 && lvl < MaxLevel)
+ {
+ lvl++;
+ }
+
+ return lvl;
+ }
+
+ private class SkipListNode(T? value, SkipListNode? next, SkipListNode? down)
+ {
+ public T? Value { get; set; } = value;
+
+ public SkipListNode? Next { get; set; } = next;
+
+ public SkipListNode? Down { get; set; } = down;
+ }
+}
\ No newline at end of file
diff --git a/HW4/SkipList/SkipList.csproj b/HW4/SkipList/SkipList.csproj
new file mode 100644
index 0000000..7b2dade
--- /dev/null
+++ b/HW4/SkipList/SkipList.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Library
+ net9.0
+ enable
+ enable
+ true
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/HW4/SkipList/SkipListException.cs b/HW4/SkipList/SkipListException.cs
new file mode 100644
index 0000000..bce9e73
--- /dev/null
+++ b/HW4/SkipList/SkipListException.cs
@@ -0,0 +1,19 @@
+//
+// Copyright (c) khusainovilas. All rights reserved.
+//
+namespace SkipList;
+
+///
+/// Exception for empty skip list operations.
+///
+public class SkipListException : Exception
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error message.
+ public SkipListException(string message)
+ : base(message)
+ {
+ }
+}
\ No newline at end of file
diff --git a/HW4/SkipList/stylecop.json b/HW4/SkipList/stylecop.json
new file mode 100644
index 0000000..76c8e76
--- /dev/null
+++ b/HW4/SkipList/stylecop.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
+ "settings": {
+ "documentationRules": {
+ "companyName": "khusainovilas",
+ "copyrightText": "Copyright (c) {companyName}. All rights reserved."
+ }
+ }
+}
\ No newline at end of file
diff --git a/HW4/SkipListTests/SkipListTests.cs b/HW4/SkipListTests/SkipListTests.cs
new file mode 100644
index 0000000..9b66a8b
--- /dev/null
+++ b/HW4/SkipListTests/SkipListTests.cs
@@ -0,0 +1,282 @@
+//
+// Copyright (c) khusainovilas. All rights reserved.
+//
+
+namespace SkipList.Tests;
+
+///
+/// Tests for SkipList.
+///
+[TestFixture]
+public class SkipListTests
+{
+ ///
+ /// Checks that the constructor accepting the collection creates a sorted list.
+ ///
+ [Test]
+ public void SkipList_Constructor_WithUnsortedArray_CreatesSortedList()
+ {
+ var list = new SkipList([73, 12, 99, 33, 5]);
+
+ Assert.That(list, Is.EqualTo(new[] { 5, 12, 33, 73, 99 }).AsCollection);
+ }
+
+ ///
+ /// Checks that consecutive Add calls preserve the sorting order.
+ ///
+ [Test]
+ public void SkipList_Add_MultipleStrings_KeepsSortedOrder()
+ {
+ var list = new SkipList
+ {
+ "python",
+ "java",
+ "rust",
+ "csharp",
+ "go",
+ };
+
+ Assert.That(list, Is.EqualTo(new[] { "csharp", "go", "java", "python", "rust" }).AsCollection);
+ }
+
+ ///
+ /// Checks the operation of Contains after adding 2000 elements.
+ ///
+ [Test]
+ public void SkipList_Add_TwoThousandIntegers_ContainsReturnsCorrectResult()
+ {
+ var list = new SkipList();
+
+ for (var i = 2000; i >= 1; i--)
+ {
+ list.Add(i);
+ }
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(list.Contains(42), Is.True);
+ Assert.That(list.Contains(1337), Is.True);
+ Assert.That(list.Contains(2000), Is.True);
+ Assert.That(list.Contains(2001), Is.False);
+ });
+ }
+
+ ///
+ /// Checks that adding null throws an ArgumentNullException.
+ ///
+ [Test]
+ public void SkipList_Add_NullReference_ThrowsArgumentNullException()
+ {
+ var list = new SkipList();
+
+ Assert.Throws(() => list.Add(null!));
+ }
+
+ ///
+ /// Checks the correctness of the Count property after several inserts.
+ ///
+ [Test]
+ public void SkipList_Count_AfterFourAdds_ReturnsFour()
+ {
+ var list = new SkipList
+ {
+ 9.81,
+ 2.718,
+ 3.14159,
+ 1.414,
+ };
+
+ Assert.That(list.Count, Is.EqualTo(4));
+ }
+
+ ///
+ /// Checks that foreach returns the items in sorted order.
+ ///
+ [Test]
+ public void SkipList_GetEnumerator_UnsortedInput_ReturnsSortedSequence()
+ {
+ var list = new SkipList([500, 100, 400, 200, 300]);
+
+ Assert.That(list, Is.EqualTo(new[] { 100, 200, 300, 400, 500 }).AsCollection);
+ }
+
+ ///
+ /// Checks that changing the list during enumeration throws an InvalidOperationException.
+ ///
+ [Test]
+ public void SkipList_GetEnumerator_ModifyDuringEnumeration_ThrowsInvalidOperationException()
+ {
+ var list = new SkipList([10, 20, 30]);
+ using var enumerator = list.GetEnumerator();
+
+ enumerator.MoveNext();
+ list.Add(999);
+
+ Assert.Throws(() => enumerator.MoveNext());
+ }
+
+ ///
+ /// Checks that Clear resets Count.
+ ///
+ [Test]
+ public void SkipList_Clear_NonEmptyList_SetsCountToZero()
+ {
+ var list = new SkipList([111, 222, 333]);
+ list.Clear();
+
+ Assert.That(list.Count, Is.Zero);
+ }
+
+ ///
+ /// Checks that the collection is really empty after Clear.
+ ///
+ [Test]
+ public void SkipList_Clear_NonEmptyList_BecomesEmptyCollection()
+ {
+ var list = new SkipList(["alpha", "beta", "gamma"]);
+ list.Clear();
+
+ Assert.That(list, Is.Empty);
+ Assert.That(list.Any(), Is.False);
+ }
+
+ ///
+ /// Checks the operation of Contains for an existing element.
+ ///
+ [Test]
+ public void SkipList_Contains_ExistingGuid_ReturnsTrue()
+ {
+ var guid = Guid.NewGuid();
+ var list = new SkipList([Guid.NewGuid(), Guid.NewGuid()]) { guid };
+
+ Assert.That(list.Contains(guid), Is.True);
+ }
+
+ ///
+ /// Checks the correctness of the CopyTo if the array size is sufficient.
+ ///
+ [Test]
+ public void SkipList_CopyTo_SufficientArray_CopiesElementsCorrectly()
+ {
+ var list = new SkipList([7, 14, 21, 28]);
+ var array = new int[7];
+
+ list.CopyTo(array, 2);
+
+ Assert.That(array, Is.EqualTo(new[] { 0, 0, 7, 14, 21, 28, 0 }).AsCollection);
+ }
+
+ ///
+ /// Checks that CopyTo throws an exception if the array size is insufficient.
+ ///
+ [Test]
+ public void SkipList_CopyTo_SmallArray_ThrowsArgumentOutOfRangeException()
+ {
+ var list = new SkipList([1, 2, 3]);
+ var small = new int[2];
+
+ Assert.Throws(() => list.CopyTo(small, 0));
+ }
+
+ ///
+ /// Checks that the Remove of an existing element returns true and deletes it.
+ ///
+ [Test]
+ public void SkipList_Remove_ExistingString_ReturnsTrueAndRemovesElement()
+ {
+ var list = new SkipList(["wolf", "bear", "fox", "lynx"]);
+
+ var removed = list.Remove("bear");
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(removed, Is.True);
+ Assert.That(list.Contains("bear"), Is.False);
+ Assert.That(list.Count, Is.EqualTo(3));
+ });
+ }
+
+ ///
+ /// Checks that the Remove of a non-existent element returns false.
+ ///
+ [Test]
+ public void SkipList_Remove_NonExistingInteger_ReturnsFalse()
+ {
+ var list = new SkipList([100, 200, 300]);
+
+ var removed = list.Remove(999);
+
+ Assert.That(removed, Is.False);
+ Assert.That(list.Count, Is.EqualTo(3));
+ }
+
+ ///
+ /// Checks whether indexOf is valid for an existing element.
+ ///
+ [Test]
+ public void SkipList_IndexOf_ExistingInteger_ReturnsCorrectIndex()
+ {
+ var list = new SkipList([150, 250, 350, 450]);
+
+ Assert.That(list.IndexOf(350), Is.EqualTo(2));
+ }
+
+ ///
+ /// Checks that indexOf returns -1 for the missing element.
+ ///
+ [Test]
+ public void SkipList_IndexOf_NonExistingInteger_ReturnsMinusOne()
+ {
+ var list = new SkipList([11, 22, 33]);
+
+ Assert.That(list.IndexOf(44), Is.EqualTo(-1));
+ }
+
+ ///
+ /// Checks that Insert always throws a NotSupportedException.
+ ///
+ [Test]
+ public void SkipList_Insert_AnyIndex_ThrowsNotSupportedException()
+ {
+ var list = new SkipList();
+
+ Assert.Throws(() => list.Insert(0, 123));
+ }
+
+ ///
+ /// Checks the deletion of an element by index via RemoveAt.
+ ///
+ [Test]
+ public void SkipList_RemoveAt_MiddleIndex_RemovesCorrectElement()
+ {
+ var list = new SkipList([5, 10, 15, 20, 25]);
+
+ list.RemoveAt(2);
+
+ Assert.That(list, Is.EqualTo(new[] { 5, 10, 20, 25 }).AsCollection);
+ Assert.That(list, Has.No.Member(15));
+ }
+
+ ///
+ /// Checks the receipt of an element by a valid index.
+ ///
+ [Test]
+ public void SkipList_Indexer_GetValidIndex_ReturnsCorrectValue()
+ {
+ var list = new SkipList([8, 6, 7, 5, 9]);
+
+ Assert.That(list[2], Is.EqualTo(7));
+ }
+
+ ///
+ /// Checks that accessing an index outside the list throws an ArgumentOutOfRangeException.
+ ///
+ [Test]
+ public void SkipList_Indexer_GetOutOfRangeIndex_ThrowsArgumentOutOfRangeException()
+ {
+ var list = new SkipList([1, 2, 3]);
+
+ Assert.Throws(() => _ = list[-1]);
+ Assert.Throws(() => _ = list[4]);
+ }
+}
\ No newline at end of file
diff --git a/HW4/SkipListTests/SkipListTests.csproj b/HW4/SkipListTests/SkipListTests.csproj
new file mode 100644
index 0000000..019920c
--- /dev/null
+++ b/HW4/SkipListTests/SkipListTests.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net9.0
+ latest
+ enable
+ enable
+ false
+ SkipList.Tests
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/HW4/SkipListTests/stylecop.json b/HW4/SkipListTests/stylecop.json
new file mode 100644
index 0000000..76c8e76
--- /dev/null
+++ b/HW4/SkipListTests/stylecop.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
+ "settings": {
+ "documentationRules": {
+ "companyName": "khusainovilas",
+ "copyrightText": "Copyright (c) {companyName}. All rights reserved."
+ }
+ }
+}
\ No newline at end of file