Skip to content

refactor: Enhance docs, add tests in CircleLinkedList #5991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
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> {

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;
E value;
Expand All @@ -13,44 +28,56 @@ private Node(E value, Node<E> next) {
}
}

// For better O.O design this should be private allows for better black box design
private int size;
// this will point to dummy node;
private Node<E> head = null;
private Node<E> tail = null; // keeping a tail pointer to keep track of the end of list
Node<E> head = null;
private Node<E> tail;

// 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() {
// creation of the dummy node
head = new Node<E>(null, head);
head = new Node<>(null, head);
tail = head;
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() {
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) {
if (value == null) {
// we do not want to add null elements to the list.
throw new NullPointerException("Cannot add null element to the list");
}
// head.next points to the last element;
if (tail == null) {
tail = new Node<E>(value, head);
tail = new Node<>(value, head);
head.next = tail;
} else {
tail.next = new Node<E>(value, head);
tail.next = new Node<>(value, head);
tail = tail.next;
}
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() {
if (size == 0) {
return "[]";
Expand All @@ -68,23 +95,27 @@ public String 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) {
if (pos >= size || pos < 0) {
// catching errors
throw new IndexOutOfBoundsException("position cannot be greater than size or negative");
throw new IndexOutOfBoundsException("Position out of bounds");
}
// we need to keep track of the element before the element we want to remove we can see why
// bellow.

Node<E> before = head;
for (int i = 1; i <= pos; i++) {
before = before.next;
}
Node<E> destroy = before.next;
E saved = destroy.value;
// assigning the next reference to the element following the element we want to remove...
// the last element will be assigned to the head.
before.next = before.next.next;
// scrubbing
before.next = destroy.next;

if (destroy == tail) {
tail = before;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,78 +1,115 @@
package com.thealgorithms.datastructures.lists;

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 org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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
public void testAppendAndSize() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1);
list.append(2);
list.append(3);

assertEquals(3, list.getSize());
assertEquals("[ 1, 2, 3 ]", list.toString());
assertEquals(3, list.getSize(), "Size after three appends should be 3.");
assertEquals("[ 1, 2, 3 ]", list.toString(), "List content should match appended values.");
}

@Test
public void testRemove() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1);
list.append(2);
list.append(3);
list.append(4);

assertEquals(2, list.remove(1));
assertEquals(3, list.remove(1));
assertEquals("[ 1, 4 ]", list.toString());
assertEquals(2, list.getSize());
assertEquals(2, list.remove(1), "Removed element at index 1 should be 2.");
assertEquals(3, list.remove(1), "Removed element at index 1 after update should be 3.");
assertEquals("[ 1, 4 ]", list.toString(), "List content should reflect removals.");
assertEquals(2, list.getSize(), "Size after two removals should be 2.");
}

@Test
public void testRemoveInvalidIndex() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1);
list.append(2);

assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2));
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1));
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2), "Should throw on out-of-bounds index.");
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1), "Should throw on negative index.");
}

@Test
public void testToStringEmpty() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
assertEquals("[]", list.toString());
assertEquals("[]", list.toString(), "Empty list should be represented by '[]'.");
}

@Test
public void testToStringAfterRemoval() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1);
list.append(2);
list.append(3);
list.remove(1);

assertEquals("[ 1, 3 ]", list.toString());
assertEquals("[ 1, 3 ]", list.toString(), "List content should match remaining elements after removal.");
}

@Test
public void testSingleElement() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
list.append(1);

assertEquals(1, list.getSize());
assertEquals("[ 1 ]", list.toString());
assertEquals(1, list.remove(0));
assertEquals("[]", list.toString());
assertEquals(1, list.getSize(), "Size after single append should be 1.");
assertEquals("[ 1 ]", list.toString(), "Single element list should display properly.");
assertEquals(1, list.remove(0), "Single element removed should match appended value.");
assertEquals("[]", list.toString(), "List should be empty after removing the single element.");
}

@Test
public void testNullElement() {
CircleLinkedList<String> list = new CircleLinkedList<>();
assertThrows(NullPointerException.class, () -> list.append(null));
assertThrows(NullPointerException.class, () -> list.append(null), "Appending null should throw exception.");
}

@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.");
}
}