Enhance docs, add tests in CircleLinkedList (#5991)

This commit is contained in:
Hardik Pawar
2024-10-25 20:46:34 +05:30
committed by GitHub
parent c766c5e812
commit 202879aa58
2 changed files with 114 additions and 46 deletions

View File

@ -1,8 +1,23 @@
package com.thealgorithms.datastructures.lists; package com.thealgorithms.datastructures.lists;
/**
* This class is a circular singly linked list implementation. In a circular linked list,
* the last node points back to the first node, creating a circular chain.
*
* <p>This implementation includes basic operations such as appending elements
* to the end, removing elements from a specified position, and converting
* the list to a string representation.
*
* @param <E> the type of elements held in this list
*/
public class CircleLinkedList<E> { public class CircleLinkedList<E> {
private static final class Node<E> { /**
* A static nested class representing a node in the circular linked list.
*
* @param <E> the type of element stored in the node
*/
static final class Node<E> {
Node<E> next; Node<E> next;
E value; E value;
@ -13,44 +28,56 @@ public class CircleLinkedList<E> {
} }
} }
// For better O.O design this should be private allows for better black box design
private int size; private int size;
// this will point to dummy node; Node<E> head = null;
private Node<E> head = null; private Node<E> tail;
private Node<E> tail = null; // keeping a tail pointer to keep track of the end of list
// constructor for class.. here we will make a dummy node for circly linked list implementation /**
// with reduced error catching as our list will never be empty; * Initializes a new circular linked list. A dummy head node is used for simplicity,
* pointing initially to itself to ensure the list is never empty.
*/
public CircleLinkedList() { public CircleLinkedList() {
// creation of the dummy node head = new Node<>(null, head);
head = new Node<E>(null, head);
tail = head; tail = head;
size = 0; size = 0;
} }
// getter for the size... needed because size is private. /**
* Returns the current size of the list.
*
* @return the number of elements in the list
*/
public int getSize() { public int getSize() {
return size; return size;
} }
// for the sake of simplistiy this class will only contain the append function or addLast other /**
// add functions can be implemented however this is the basses of them all really. * Appends a new element to the end of the list. Throws a NullPointerException if
* a null value is provided.
*
* @param value the value to append to the list
* @throws NullPointerException if the value is null
*/
public void append(E value) { public void append(E value) {
if (value == null) { if (value == null) {
// we do not want to add null elements to the list.
throw new NullPointerException("Cannot add null element to the list"); throw new NullPointerException("Cannot add null element to the list");
} }
// head.next points to the last element;
if (tail == null) { if (tail == null) {
tail = new Node<E>(value, head); tail = new Node<>(value, head);
head.next = tail; head.next = tail;
} else { } else {
tail.next = new Node<E>(value, head); tail.next = new Node<>(value, head);
tail = tail.next; tail = tail.next;
} }
size++; size++;
} }
/**
* Returns a string representation of the list in the format "[ element1, element2, ... ]".
* An empty list is represented as "[]".
*
* @return the string representation of the list
*/
public String toString() { public String toString() {
if (size == 0) { if (size == 0) {
return "[]"; return "[]";
@ -68,23 +95,27 @@ public class CircleLinkedList<E> {
return sb.toString(); return sb.toString();
} }
/**
* Removes and returns the element at the specified position in the list.
* Throws an IndexOutOfBoundsException if the position is invalid.
*
* @param pos the position of the element to remove
* @return the value of the removed element
* @throws IndexOutOfBoundsException if the position is out of range
*/
public E remove(int pos) { public E remove(int pos) {
if (pos >= size || pos < 0) { if (pos >= size || pos < 0) {
// catching errors throw new IndexOutOfBoundsException("Position out of bounds");
throw new IndexOutOfBoundsException("position cannot be greater than size or negative");
} }
// we need to keep track of the element before the element we want to remove we can see why
// bellow.
Node<E> before = head; Node<E> before = head;
for (int i = 1; i <= pos; i++) { for (int i = 1; i <= pos; i++) {
before = before.next; before = before.next;
} }
Node<E> destroy = before.next; Node<E> destroy = before.next;
E saved = destroy.value; E saved = destroy.value;
// assigning the next reference to the element following the element we want to remove... before.next = destroy.next;
// the last element will be assigned to the head.
before.next = before.next.next;
// scrubbing
if (destroy == tail) { if (destroy == tail) {
tail = before; tail = before;
} }

View File

@ -1,78 +1,115 @@
package com.thealgorithms.datastructures.lists; package com.thealgorithms.datastructures.lists;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class CircleLinkedListTest { public class CircleLinkedListTest {
private CircleLinkedList<Integer> list;
@BeforeEach
public void setUp() {
list = new CircleLinkedList<>();
}
@Test
public void testInitialSize() {
assertEquals(0, list.getSize(), "Initial size should be 0.");
}
@Test @Test
public void testAppendAndSize() { public void testAppendAndSize() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1); list.append(1);
list.append(2); list.append(2);
list.append(3); list.append(3);
assertEquals(3, list.getSize()); assertEquals(3, list.getSize(), "Size after three appends should be 3.");
assertEquals("[ 1, 2, 3 ]", list.toString()); assertEquals("[ 1, 2, 3 ]", list.toString(), "List content should match appended values.");
} }
@Test @Test
public void testRemove() { public void testRemove() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1); list.append(1);
list.append(2); list.append(2);
list.append(3); list.append(3);
list.append(4); list.append(4);
assertEquals(2, list.remove(1)); assertEquals(2, list.remove(1), "Removed element at index 1 should be 2.");
assertEquals(3, list.remove(1)); assertEquals(3, list.remove(1), "Removed element at index 1 after update should be 3.");
assertEquals("[ 1, 4 ]", list.toString()); assertEquals("[ 1, 4 ]", list.toString(), "List content should reflect removals.");
assertEquals(2, list.getSize()); assertEquals(2, list.getSize(), "Size after two removals should be 2.");
} }
@Test @Test
public void testRemoveInvalidIndex() { public void testRemoveInvalidIndex() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1); list.append(1);
list.append(2); list.append(2);
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2)); assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2), "Should throw on out-of-bounds index.");
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1), "Should throw on negative index.");
} }
@Test @Test
public void testToStringEmpty() { public void testToStringEmpty() {
CircleLinkedList<Integer> list = new CircleLinkedList<>(); assertEquals("[]", list.toString(), "Empty list should be represented by '[]'.");
assertEquals("[]", list.toString());
} }
@Test @Test
public void testToStringAfterRemoval() { public void testToStringAfterRemoval() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1); list.append(1);
list.append(2); list.append(2);
list.append(3); list.append(3);
list.remove(1); list.remove(1);
assertEquals("[ 1, 3 ]", list.toString()); assertEquals("[ 1, 3 ]", list.toString(), "List content should match remaining elements after removal.");
} }
@Test @Test
public void testSingleElement() { public void testSingleElement() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1); list.append(1);
assertEquals(1, list.getSize()); assertEquals(1, list.getSize(), "Size after single append should be 1.");
assertEquals("[ 1 ]", list.toString()); assertEquals("[ 1 ]", list.toString(), "Single element list should display properly.");
assertEquals(1, list.remove(0)); assertEquals(1, list.remove(0), "Single element removed should match appended value.");
assertEquals("[]", list.toString()); assertEquals("[]", list.toString(), "List should be empty after removing the single element.");
} }
@Test @Test
public void testNullElement() { public void testNullElement() {
CircleLinkedList<String> list = new CircleLinkedList<>(); assertThrows(NullPointerException.class, () -> list.append(null), "Appending null should throw exception.");
assertThrows(NullPointerException.class, () -> list.append(null)); }
@Test
public void testCircularReference() {
list.append(1);
list.append(2);
list.append(3);
CircleLinkedList.Node<Integer> current = list.head;
// Traverse one full cycle and verify the circular reference
for (int i = 0; i <= list.getSize(); i++) {
current = current.next;
}
assertEquals(list.head, current, "End of list should point back to the head (circular structure).");
}
@Test
public void testClear() {
list.append(1);
list.append(2);
list.append(3);
// Remove all elements to simulate clearing the list
for (int i = list.getSize() - 1; i >= 0; i--) {
list.remove(i);
}
assertEquals(0, list.getSize(), "Size after clearing should be 0.");
assertEquals("[]", list.toString(), "Empty list should be represented by '[]' after clear.");
assertSame(list.head.next, list.head, "Head's next should point to itself after clearing.");
} }
} }