diff --git a/DataStructures.Tests/LinkedList/CircularLinkedListTests.cs b/DataStructures.Tests/LinkedList/CircularLinkedListTests.cs new file mode 100644 index 00000000..12b86b09 --- /dev/null +++ b/DataStructures.Tests/LinkedList/CircularLinkedListTests.cs @@ -0,0 +1,180 @@ +using System; +using DataStructures.LinkedList.CircularLinkedList; +using NUnit.Framework; + +namespace DataStructures.Tests.LinkedList; + +[TestFixture] +public static class CircularLinkedListTests +{ + [Test] + public static void TestInsertAtBeginning() + { + var cll = new CircularLinkedList(); + cll.InsertAtBeginning(10); + cll.InsertAtBeginning(20); + cll.InsertAtBeginning(30); + + Assert.That("30 20 10", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestInsertAtEnd() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + cll.InsertAtEnd(20); + cll.InsertAtEnd(30); + + Assert.That("10 20 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestInsertAfter() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + cll.InsertAtEnd(20); + cll.InsertAtEnd(30); + cll.InsertAfter(20, 25); + + Assert.That("10 20 25 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestInsertAtBeginningInEmptyList() + { + var cll = new CircularLinkedList(); + cll.InsertAtBeginning(10); + + Assert.That("10", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestInsertAtEndInEmptyList() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + + Assert.That("10", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestInsertAfterInEmptyList() + { + var cll = new CircularLinkedList(); + var ex = Assert.Throws(() => cll.InsertAfter(10, 20)); + + Assert.That(ex!.Message, Is.EqualTo("List is empty.")); + } + + [Test] + public static void TestInsertAfterSpecificNode() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + cll.InsertAtEnd(20); + cll.InsertAtEnd(30); + cll.InsertAfter(20, 25); // Insert after node with value 20 + + Assert.That("10 20 25 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestInsertAfterOnNonExistingValue() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + cll.InsertAfter(99, 25); // 99 does not exist + + Assert.That("10", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestDeleteNode() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + cll.InsertAtEnd(20); + cll.InsertAtEnd(30); + cll.DeleteNode(20); + + Assert.That("10 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestDeleteOnlyNode() + { + var cll = new CircularLinkedList(); + cll.InsertAtBeginning(10); + cll.DeleteNode(10); + + Assert.That(cll.IsEmpty(), Is.EqualTo(true)); + } + + [Test] + public static void TestDeleteHeadNode() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + cll.InsertAtEnd(20); + cll.InsertAtEnd(30); + cll.DeleteNode(10); + + Assert.That("20 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestDeleteTailNode() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + cll.InsertAtEnd(20); + cll.InsertAtEnd(30); + cll.DeleteNode(30); + + Assert.That("10 20", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + [Test] + public static void TestDeleteFromEmptyList() + { + var cll = new CircularLinkedList(); + var ex = Assert.Throws(() => cll.DeleteNode(10)); + + Assert.That(ex!.Message, Is.EqualTo("List is empty.")); + } + + [Test] + public static void TestDeleteNonExistentNode() + { + var cll = new CircularLinkedList(); + cll.InsertAtEnd(10); + cll.InsertAtEnd(20); + cll.InsertAtEnd(30); + cll.DeleteNode(40); // Attempting to delete a node that doesn't exist + + Assert.That("10 20 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); + } + + private static string GetDisplayOutput(CircularLinkedList list) + { + var head = list.GetHead(); + if (head == null) + { + return string.Empty; + } + + var current = head; + var result = new System.Text.StringBuilder(); + + do + { + result.Append(current!.Data + " "); + current = current.Next; + } + while (current != head); + + return result.ToString().Trim(); + } +} diff --git a/DataStructures/LinkedList/CircularLinkedList/CircularLinkedList.cs b/DataStructures/LinkedList/CircularLinkedList/CircularLinkedList.cs new file mode 100644 index 00000000..18c5148d --- /dev/null +++ b/DataStructures/LinkedList/CircularLinkedList/CircularLinkedList.cs @@ -0,0 +1,155 @@ +using System; + +namespace DataStructures.LinkedList.CircularLinkedList +{ + /// + /// CircularLinkedList. + /// @author Mohit Singh. mohit-gogitter + /// + /// The generic type parameter. + public class CircularLinkedList + { + /// + /// Points to the last node in the Circular Linked List. + /// + private CircularLinkedListNode? tail; + + /// + /// Initializes a new instance of the class. + /// + public CircularLinkedList() + { + tail = null; + } + + /// + /// Gets the head node (tail.Next) of the Circular Linked List. + /// + public CircularLinkedListNode? GetHead() + { + return tail?.Next; + } + + /// + /// Determines whether the Circular Linked List is empty. + /// + /// True if the list is empty; otherwise, false. + public bool IsEmpty() + { + return tail == null; + } + + /// + /// Inserts a new node at the beginning of the Circular Linked List. + /// + /// The data to insert into the new node. + public void InsertAtBeginning(T data) + { + var newNode = new CircularLinkedListNode(data); + if (IsEmpty()) + { + tail = newNode; + tail.Next = tail; + } + else + { + newNode.Next = tail!.Next; + tail.Next = newNode; + } + } + + /// + /// Inserts a new node at the end of the Circular Linked List. + /// + /// The data to insert into the new node. + public void InsertAtEnd(T data) + { + var newNode = new CircularLinkedListNode(data); + if (IsEmpty()) + { + tail = newNode; + tail.Next = tail; + } + else + { + newNode.Next = tail!.Next; + tail.Next = newNode; + tail = newNode; + } + } + + /// + /// Inserts a new node after a specific value in the list. + /// + /// The value to insert the node after. + /// The data to insert into the new node. + public void InsertAfter(T value, T data) + { + if (IsEmpty()) + { + throw new InvalidOperationException("List is empty."); + } + + var current = tail!.Next; + do + { + if (current!.Data!.Equals(value)) + { + var newNode = new CircularLinkedListNode(data); + newNode.Next = current.Next; + current.Next = newNode; + + return; + } + + current = current.Next; + } + while (current != tail.Next); + } + + /// + /// Deletes a node with a specific value from the list. + /// + /// The value of the node to delete. + public void DeleteNode(T value) + { + if (IsEmpty()) + { + throw new InvalidOperationException("List is empty."); + } + + var current = tail!.Next; + var previous = tail; + + do + { + if (current!.Data!.Equals(value)) + { + if (current == tail && current.Next == tail) + { + tail = null; + } + else if (current == tail) + { + previous!.Next = tail.Next; + tail = previous; + } + else if (current == tail.Next) + { + tail.Next = current.Next; + } + else + { + previous!.Next = current.Next; + } + + return; + } + + previous = current; + current = current.Next; + } + while (current != tail!.Next); + } + } +} diff --git a/DataStructures/LinkedList/CircularLinkedList/CircularLinkedListNode.cs b/DataStructures/LinkedList/CircularLinkedList/CircularLinkedListNode.cs new file mode 100644 index 00000000..40f416f7 --- /dev/null +++ b/DataStructures/LinkedList/CircularLinkedList/CircularLinkedListNode.cs @@ -0,0 +1,24 @@ +namespace DataStructures.LinkedList.CircularLinkedList +{ + /// + /// Represents a node in the Circular Linked List. + /// Each node contains generic data and a reference to the next node. + /// + /// The type of the data stored in the node. + /// + /// Initializes a new instance of the class. + /// + /// The data to be stored in the node. + public class CircularLinkedListNode(T data) + { + /// + /// Gets or sets the data for the node. + /// + public T Data { get; set; } = data; + + /// + /// Gets or sets the reference to the next node in the list. + /// + public CircularLinkedListNode? Next { get; set; } + } +} diff --git a/README.md b/README.md index 8085ccab..8a1308e2 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,7 @@ find more than one implementation for the same objective but using different alg * [Singly Linked List](./DataStructures/LinkedList/SinglyLinkedList/SinglyLinkedList.cs) * [Doubly Linked List](./DataStructures/LinkedList/DoublyLinkedList/DoublyLinkedList.cs) * [Skip List](./DataStructures/LinkedList/SkipList/SkipList.cs) + * [Circular Linked List](./DataStructures/LinkedList/CircularLinkedList/CircularLinkedList.cs) * [Graph](./DataStructures/Graph) * [Directed Weighted Graph Via Adjacency Matrix](./DataStructures/Graph/DirectedWeightedGraph.cs) * [Disjoint Set](./DataStructures/DisjointSet)