mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-06 09:06:51 +08:00
Enhance docs, add tests in CircleLinkedList
(#5991)
This commit is contained in:
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user