Skip to content

Commit 5e9d9f7

Browse files
authored
Added Circular Doubly Linked List in the datastructures section (#6565)
1 parent 421ac6d commit 5e9d9f7

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package com.thealgorithms.datastructures.lists;
2+
3+
/**
4+
* This class is a circular doubly linked list implementation. In a circular
5+
* doubly linked list,
6+
* the last node points back to the first node and the first node points back to
7+
* the last node,
8+
* creating a circular chain in both directions.
9+
*
10+
* This implementation includes basic operations such as appending elements to
11+
* the end,
12+
* removing elements from a specified position, and converting the list to a
13+
* string representation.
14+
*
15+
* @param <E> the type of elements held in this list
16+
*/
17+
public class CircularDoublyLinkedList<E> {
18+
static final class Node<E> {
19+
Node<E> next;
20+
Node<E> prev;
21+
E value;
22+
23+
private Node(E value, Node<E> next, Node<E> prev) {
24+
this.value = value;
25+
this.next = next;
26+
this.prev = prev;
27+
}
28+
}
29+
30+
private int size;
31+
Node<E> head = null;
32+
33+
/**
34+
* Initializes a new circular doubly linked list. A dummy head node is used for
35+
* simplicity,
36+
* pointing initially to itself to ensure the list is never empty.
37+
*/
38+
public CircularDoublyLinkedList() {
39+
head = new Node<>(null, null, null);
40+
head.next = head;
41+
head.prev = head;
42+
size = 0;
43+
}
44+
45+
/**
46+
* Returns the current size of the list.
47+
*
48+
* @return the number of elements in the list
49+
*/
50+
public int getSize() {
51+
return size;
52+
}
53+
54+
/**
55+
* Appends a new element to the end of the list. Throws a NullPointerException
56+
* if
57+
* a null value is provided.
58+
*
59+
* @param value the value to append to the list
60+
* @throws NullPointerException if the value is null
61+
*/
62+
public void append(E value) {
63+
if (value == null) {
64+
throw new NullPointerException("Cannot add null element to the list");
65+
}
66+
Node<E> newNode = new Node<>(value, head, head.prev);
67+
head.prev.next = newNode;
68+
head.prev = newNode;
69+
size++;
70+
}
71+
72+
/**
73+
* Returns a string representation of the list in the format "[ element1,
74+
* element2, ... ]".
75+
* An empty list is represented as "[]".
76+
*
77+
* @return the string representation of the list
78+
*/
79+
public String toString() {
80+
if (size == 0) {
81+
return "[]";
82+
}
83+
StringBuilder sb = new StringBuilder("[ ");
84+
Node<E> current = head.next;
85+
while (current != head) {
86+
sb.append(current.value);
87+
if (current.next != head) {
88+
sb.append(", ");
89+
}
90+
current = current.next;
91+
}
92+
sb.append(" ]");
93+
return sb.toString();
94+
}
95+
96+
/**
97+
* Removes and returns the element at the specified position in the list.
98+
* Throws an IndexOutOfBoundsException if the position is invalid.
99+
*
100+
* @param pos the position of the element to remove
101+
* @return the value of the removed element - pop operation
102+
* @throws IndexOutOfBoundsException if the position is out of range
103+
*/
104+
public E remove(int pos) {
105+
if (pos >= size || pos < 0) {
106+
throw new IndexOutOfBoundsException("Position out of bounds");
107+
}
108+
Node<E> current = head.next;
109+
for (int i = 0; i < pos; i++) {
110+
current = current.next;
111+
}
112+
current.prev.next = current.next;
113+
current.next.prev = current.prev;
114+
E removedValue = current.value;
115+
size--;
116+
return removedValue;
117+
}
118+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.thealgorithms.datastructures.lists;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
9+
public class CircularDoublyLinkedListTest {
10+
11+
private CircularDoublyLinkedList<Integer> list;
12+
13+
@BeforeEach
14+
public void setUp() {
15+
list = new CircularDoublyLinkedList<>();
16+
}
17+
18+
@Test
19+
public void testInitialSize() {
20+
assertEquals(0, list.getSize(), "Initial size should be 0.");
21+
}
22+
23+
@Test
24+
public void testAppendAndSize() {
25+
list.append(10);
26+
list.append(20);
27+
list.append(30);
28+
29+
assertEquals(3, list.getSize(), "Size after appends should be 3.");
30+
assertEquals("[ 10, 20, 30 ]", list.toString(), "List content should match appended values.");
31+
}
32+
33+
@Test
34+
public void testRemove() {
35+
list.append(10);
36+
list.append(20);
37+
list.append(30);
38+
39+
int removed = list.remove(1);
40+
assertEquals(20, removed, "Removed element at index 1 should be 20.");
41+
42+
assertEquals("[ 10, 30 ]", list.toString(), "List content should reflect removal.");
43+
assertEquals(2, list.getSize(), "Size after removal should be 2.");
44+
45+
removed = list.remove(0);
46+
assertEquals(10, removed, "Removed element at index 0 should be 10.");
47+
assertEquals("[ 30 ]", list.toString(), "List content should reflect second removal.");
48+
assertEquals(1, list.getSize(), "Size after second removal should be 1.");
49+
}
50+
51+
@Test
52+
public void testRemoveInvalidIndex() {
53+
list.append(10);
54+
list.append(20);
55+
56+
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2), "Removing at invalid index 2 should throw exception.");
57+
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1), "Removing at negative index should throw exception.");
58+
}
59+
60+
@Test
61+
public void testToStringEmpty() {
62+
assertEquals("[]", list.toString(), "Empty list should display as [].");
63+
}
64+
65+
@Test
66+
public void testSingleElement() {
67+
list.append(10);
68+
69+
assertEquals(1, list.getSize(), "Size after adding single element should be 1.");
70+
assertEquals("[ 10 ]", list.toString(), "Single element list string should be formatted correctly.");
71+
int removed = list.remove(0);
72+
assertEquals(10, removed, "Removed element should be the one appended.");
73+
assertEquals("[]", list.toString(), "List should be empty after removing last element.");
74+
assertEquals(0, list.getSize(), "Size after removing last element should be 0.");
75+
}
76+
77+
@Test
78+
public void testNullAppend() {
79+
assertThrows(NullPointerException.class, () -> list.append(null), "Appending null should throw NullPointerException.");
80+
}
81+
82+
@Test
83+
public void testRemoveLastPosition() {
84+
list.append(10);
85+
list.append(20);
86+
list.append(30);
87+
int removed = list.remove(list.getSize() - 1);
88+
assertEquals(30, removed, "Last element removed should be 30.");
89+
assertEquals(2, list.getSize(), "Size should decrease after removing last element.");
90+
}
91+
92+
@Test
93+
public void testRemoveFromEmptyThrows() {
94+
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(0), "Remove from empty list should throw.");
95+
}
96+
97+
@Test
98+
public void testRepeatedAppendAndRemove() {
99+
for (int i = 0; i < 100; i++) {
100+
list.append(i);
101+
}
102+
assertEquals(100, list.getSize());
103+
104+
for (int i = 99; i >= 0; i--) {
105+
int removed = list.remove(i);
106+
assertEquals(i, removed, "Removed element should match appended value.");
107+
}
108+
assertEquals(0, list.getSize(), "List should be empty after all removes.");
109+
}
110+
111+
@Test
112+
public void testToStringAfterMultipleRemoves() {
113+
list.append(1);
114+
list.append(2);
115+
list.append(3);
116+
list.remove(2);
117+
list.remove(0);
118+
assertEquals("[ 2 ]", list.toString(), "ToString should correctly represent remaining elements.");
119+
}
120+
}

0 commit comments

Comments
 (0)