Thread - Data Structure
Thread info: Last updated: 03-11-2024
- 30-10-2024: Added MyArrayList and MyLinkedList
- 31-10-2024: Moved Test Classes to a separate section
Finished MyLinkedList
Added MyDoublyLinkedList
- 03-11-2024: Finished MyDoublyLinkedList
Data Structure
We implement basic data structures that will only work with int
first, evaluate the performance of the operations, and provide test classes using JUnit
for each of the data structure.
Linear Data Structure
Interface and Abstract Parent Class for Lists
To truly follow the fashion of Java, we use interface MyList
and MyAbstractList
. These classes will ensure consistency and handle basic operations like isEmpty()
, toString()
,…
import java.util.Iterator;
import java.util.List;
public interface MyList {
int size();
void append(int x);
void insertAtIndex(int x, int i);
void setAtIndex(int x, int i);
int get(int i);
void deleteAtIndex(int i);
void deleteAllOf(int x);
int[] toArray();
List<Integer> toList();
boolean isEmpty();
Iterator<Integer> iterator();
}
public abstract class MyAbstractList implements MyList {
public boolean isEmpty() {
return size() == 0;
}
public void checkBound(int index, int limit) {
if (index < 0 || index >= limit)
throw new IndexOutOfBoundsException
("Invalid index: " + index + " for length: " + size());
}
@Override
public String toString() {
StringBuilder list = new StringBuilder("[");
for (int i = 0; i < size(); i++) {
if (i == size() - 1) list.append(get(i));
else list.append(get(i)).append(", ");
}
return list.append("]").toString();
}
}
MyArrayList
A resizable array that can grow or shrink as needed. It offers efficient random access, insertion, and removal of elements. It’s widely used in data structures and algorithms due to its flexibility and ease of use. Below is the full implementation and test class, we will evaluate each operation methods. (You can use the 🔼 button to collapse the codeblocks, the 🔼 button at the header on top of the page will allow you to collapse all codeblocks)
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class MyArrayList extends MyAbstractList {
private static final int DEFAULT_CAPACITY = 4;
private int size;
private int[] data;
public MyArrayList() {
data = new int[DEFAULT_CAPACITY];
size = 0;
}
@Override
public int size() {
return size;
}
@Override
public void append(int x) {
if (size >= data.length) enlarge();
data[size++] = x;
}
@Override
public void insertAtIndex(int x, int i) {
checkBound(i, size + 1);
if (i == size) append(x);
else {
if (size >= data.length) enlarge();
else {
System.arraycopy(data, i, data, i + 1, size - i);
data[i] = x;
size++;
}
}
}
@Override
public void setAtIndex(int x, int i) {
checkBound(i, size);
data[i] = x;
}
@Override
public int get(int i) {
checkBound(i, size);
return data[i];
}
@Override
public void deleteAtIndex(int i) {
checkBound(i, size);
System.arraycopy(data, i + 1, data, i, size - i - 1);
size--;
correct();
}
@Override
public void deleteAllOf(int x) {
int shiftCount = 0;
for (int i = 0; i < size; i++) {
if (data[i] == x) {
shiftCount++;
} else if (shiftCount > 0) {
data[i - shiftCount] = data[i];
}
}
size -= shiftCount;
correct();
}
@Override
public int[] toArray() {
return Arrays.copyOf(data, size);
}
@Override
public List<Integer> toList() {
return Arrays.stream(toArray()).boxed().toList();
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
int pos = 0;
@Override
public boolean hasNext() {
return pos < size;
}
@Override
public Integer next() {
if (hasNext()) {
return data[pos++];
} else throw new NoSuchElementException();
}
};
}
private void enlarge() {
int[] enlargedData = new int[data.length * 2];
System.arraycopy(data, 0, enlargedData, 0, size);
data = enlargedData;
}
private void correct() {
for (int i = size; i < data.length; i++) {
data[i] = 0;
}
}
}
Helper Methods
private void enlarge() {
int[] enlargedData = new int[data.length * 2];
System.arraycopy(data, 0, enlargedData, 0, size);
data = enlargedData;
}
- Time complexity: \(O(n) \mid \Omega(n) \mid \Theta(n)\)
private void correct() {
for (int i = size; i < data.length; i++) {
data[i] = 0;
}
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(k)\)
- Where \(k\) =
data.length - size
Insertion
public void append(int x) {
if (size >= data.length) enlarge();
data[size++] = x;
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(1)\)
- \(O(n)\) because the method may call
enlarge()
public void insertAtIndex(int x, int i) {
checkBound(i, size + 1);
if (i == size) append(x);
else {
if (size >= data.length) enlarge();
else {
System.arraycopy(data, i, data, i + 1, size - i);
data[i] = x;
size++;
}
}
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
Modification and Access
public void setAtIndex(int x, int i) {
checkBound(i, size);
data[i] = x;
}
public int get(int i) {
checkBound(i, size);
return data[i];
}
- Time complexity: \(O(1)\)
Deletion
public void deleteAtIndex(int i) {
checkBound(i, size);
System.arraycopy(data, i + 1, data, i, size - i - 1);
size--;
correct();
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
public void deleteAllOf(int x) {
int shiftCount = 0;
for (int i = 0; i < size; i++) {
if (data[i] == x) {
shiftCount++;
} else if (shiftCount > 0) {
data[i - shiftCount] = data[i];
}
}
size -= shiftCount;
correct();
}
- Time complexity: \(O(n) \mid \Omega(n) \mid \Theta(n)\)
MyLinkedList
A linear data structure where elements are not stored in contiguous memory locations. Instead, each element, called a node, contains data and a reference (link) to the next node in the sequence. This allows for dynamic memory allocation and efficient insertion and deletion of elements, especially at the beginning or middle of the list. Class MyLinkedList
implements a singly linked list, utilizing class Node
with a single pointer and one-way traversal.
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
public class MyLinkedList extends MyAbstractList {
private Node head;
private int size;
static class Node {
int val;
Node next;
public Node(int val) {
this.val = val;
}
}
public MyLinkedList() {
head = null;
size = 0;
}
@Override
public int size() {
return size;
}
@Override
public void append(int x) {
if (head == null) {
head = new Node(x);
} else {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = new Node(x);
}
size++;
}
@Override
public void insertAtIndex(int x, int i) {
checkBound(i, size + 1);
if (i == 0) {
Node newNode = new Node(x);
newNode.next = head;
head = newNode;
} else {
Node current = head;
for (int j = 0; j < i - 1; j++) {
current = current.next;
}
Node newNode = new Node(x);
newNode.next = current.next;
current.next = newNode;
}
size++;
}
public void insertAtHead(int x) {
insertAtIndex(x, 0);
}
public void insertAtTail(int x) {
insertAtIndex(x, size);
}
@Override
public void setAtIndex(int x, int i) {
checkBound(i, size);
Node current = head;
for (int j = 0; j < i; j++) {
current = current.next;
}
current.val = x;
}
@Override
public int get(int i) {
checkBound(i, size);
Node current = head;
for (int j = 0; j < i; j++) {
current = current.next;
}
return current.val;
}
@Override
public void deleteAtIndex(int i) {
checkBound(i, size);
if (i == 0) {
head = head.next;
} else {
Node current = head;
for (int j = 0; j < i - 1; j++) {
current = current.next;
}
current.next = current.next.next;
}
size--;
}
public void deleteAtHead() {
deleteAtIndex(0);
}
public void deleteAtTail() {
deleteAtIndex(size - 1);
}
@Override
public void deleteAllOf(int x) {
Node dummy = new Node(0);
dummy.next = head;
Node current = dummy;
while (current.next != null) {
if (current.next.val == x) {
current.next = current.next.next;
size--;
} else {
current = current.next;
}
}
head = dummy.next;
}
@Override
public int[] toArray() {
int[] array = new int[size];
Node current = head;
for (int i = 0; i < size; i++) {
array[i] = current.val;
current = current.next;
}
return array;
}
@Override
public List<Integer> toList() {
List<Integer> list = new LinkedList<>();
Node current = head;
while (current != null) {
list.add(current.val);
current = current.next;
}
return list;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
Node current = head;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Integer next() {
if (hasNext()) {
int val = current.val;
current = current.next;
return val;
} else throw new NoSuchElementException();
}
};
}
}
Insertion
public void append(int x) {
if (head == null) {
head = new Node(x);
} else {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = new Node(x);
}
size++;
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
public void insertAtIndex(int x, int i) {
checkBound(i, size + 1);
if (i == 0) {
Node newNode = new Node(x);
newNode.next = head;
head = newNode;
} else {
Node current = head;
for (int j = 0; j < i - 1; j++) {
current = current.next;
}
Node newNode = new Node(x);
newNode.next = current.next;
current.next = newNode;
}
size++;
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
public void insertAtHead(int x) {
insertAtIndex(x, 0);
}
public void insertAtTail(int x) {
insertAtIndex(x, size);
}
insertAtHead(int x)
Time complexity: \(O(1) \mid \Omega(1) \mid \Theta(1)\)insertAtTail(int x)
Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
Modification and Access
public void setAtIndex(int x, int i) {
checkBound(i, size);
Node current = head;
for (int j = 0; j < i; j++) {
current = current.next;
}
current.val = x;
}
public int get(int i) {
checkBound(i, size);
Node current = head;
for (int j = 0; j < i; j++) {
current = current.next;
}
return current.val;
}
setAtIndex(int x, int i)
Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)get(int i)
Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
Deletion
public void deleteAtIndex(int i) {
checkBound(i, size);
if (i == 0) {
head = head.next;
} else {
Node current = head;
for (int j = 0; j < i - 1; j++) {
current = current.next;
}
current.next = current.next.next;
}
size--;
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
public void deleteAtHead() {
deleteAtIndex(0);
}
public void deleteAtTail() {
deleteAtIndex(size - 1);
}
deleteAtHead()
Time complexity: \(O(1) \mid \Omega(1) \mid \Theta(1)\)deleteAtTail()
Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
public void deleteAllOf(int x) {
Node dummy = new Node(0);
dummy.next = head;
Node current = dummy;
while (current.next != null) {
if (current.next.val == x) {
current.next = current.next.next;
size--;
} else {
current = current.next;
}
}
head = dummy.next;
}
- Time complexity: \(O(n) \mid \Omega(n) \mid \Theta(n)\)
MyDoublyLinkedList
Class MyDoublyLinkedList
implements a doubly linked list, where each node contains data and references to both the next and previous nodes. This bidirectional linking enables efficient insertion, deletion, and traversal in both forward and backward directions, making it a versatile data structure for various applications.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class MyDoublyLinkedList extends MyAbstractList {
private Node head;
private Node tail;
private int size;
static class Node {
int val;
Node next;
Node prev;
public Node(int val) {
this.val = val;
}
}
public MyDoublyLinkedList() {
head = null;
tail = null;
size = 0;
}
@Override
public int size() {
return size;
}
@Override
public void append(int x) {
Node newNode = new Node(x);
if (head == null) {
head = newNode;
tail = head;
} else {
newNode.prev = tail;
tail.next = newNode;
tail = newNode;
}
size++;
}
@Override
public void insertAtIndex(int x, int i) {
checkBound(i, size + 1);
if (i == 0) {
insertAtHead(x);
return;
}
if (i == size) {
append(x);
return;
}
Node newNode = new Node(x);
Node current = dynamicTraversal(i);
newNode.prev = current.prev;
newNode.next = current;
current.prev.next = newNode;
current.prev = newNode;
size++;
}
public void insertAtHead(int x) {
Node newNode = new Node(x);
if (head == null) {
head = newNode;
tail = head;
} else {
newNode.next = head;
head.prev = newNode;
head = newNode;
}
size++;
}
public void insertAtTail(int x) {
append(x);
}
@Override
public void setAtIndex(int x, int i) {
checkBound(i, size);
dynamicTraversal(i).val = x;
}
@Override
public int get(int i) {
checkBound(i, size);
return dynamicTraversal(i).val;
}
@Override
public void deleteAtIndex(int i) {
checkBound(i, size);
if (size == 1) {
head = null;
tail = null;
} else if (i == 0) {
deleteAtHead();
return;
} else if (i == size - 1) {
deleteAtTail();
return;
} else {
Node current = dynamicTraversal(i);
current.prev.next = current.next;
current.next.prev = current.prev;
}
size--;
}
public void deleteAtHead() {
if (size == 0) return;
if (size == 1) {
head = null;
tail = null;
} else {
head = head.next;
head.prev = null;
}
size--;
}
public void deleteAtTail() {
if (size == 0) return;
if (size == 1) {
head = null;
tail = null;
} else {
tail = tail.prev;
tail.next = null;
}
size--;
}
@Override
public void deleteAllOf(int x) {
Node current = head;
while (current != null) {
if (current.val == x) {
if (current == head) {
deleteAtHead();
current = head;
} else if (current == tail) {
deleteAtTail();
break;
} else {
current.prev.next = current.next;
current.next.prev = current.prev;
current = current.next;
size--;
}
} else {
current = current.next;
}
}
}
@Override
public int[] toArray() {
int[] arr = new int[size];
Node current = head;
for (int i = 0; current != null; i++) {
arr[i] = current.val;
current = current.next;
}
return arr;
}
@Override
public List<Integer> toList() {
List<Integer> list = new ArrayList<>();
Node current = head;
while (current != null) {
list.add(current.val);
current = current.next;
}
return list;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private Node current = head;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
int val = current.val;
current = current.next;
return val;
}
};
}
private Node dynamicTraversal(int i) {
Node current;
if (i < size / 2) {
current = head;
for (int j = 0; j < i; j++) {
current = current.next;
}
} else {
current = tail;
for (int j = size - 1; j > i; j--) {
current = current.prev;
}
}
return current;
}
}
Helper Method
private Node dynamicTraversal(int i) {
Node current;
if (i < size / 2) {
current = head;
for (int j = 0; j < i; j++) {
current = current.next;
}
} else {
current = tail;
for (int j = size - 1; j > i; j--) {
current = current.prev;
}
}
return current;
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
Insertion
public void append(int x) {
Node newNode = new Node(x);
if (head == null) {
head = newNode;
tail = head;
} else {
newNode.prev = tail;
tail.next = newNode;
tail = newNode;
}
size++;
}
- Time complexity: \(O(1) \mid \Omega(1) \mid \Theta(1)\)
public void insertAtIndex(int x, int i) {
checkBound(i, size + 1);
if (i == 0) {
insertAtHead(x);
return;
}
if (i == size) {
append(x);
return;
}
Node newNode = new Node(x);
Node current = dynamicTraversal(i);
newNode.prev = current.prev;
newNode.next = current;
current.prev.next = newNode;
current.prev = newNode;
size++;
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
public void insertAtHead(int x) {
Node newNode = new Node(x);
if (head == null) {
head = newNode;
tail = head;
} else {
newNode.next = head;
head.prev = newNode;
head = newNode;
}
size++;
}
- Time complexity: \(O(1) \mid \Omega(1) \mid \Theta(1)\)
public void insertAtTail(int x) {
append(x);
}
- Time complexity: \(O(1) \mid \Omega(1) \mid \Theta(1)\)
Modification and Access
public void setAtIndex(int x, int i) {
checkBound(i, size);
dynamicTraversal(i).val = x;
}
public int get(int i) {
checkBound(i, size);
return dynamicTraversal(i).val;
}
setAtIndex(int x, int i)
Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)get(int i)
Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
Deletion
public void deleteAtIndex(int i) {
checkBound(i, size);
if (size == 1) {
head = null;
tail = null;
} else if (i == 0) {
deleteAtHead();
return;
} else if (i == size - 1) {
deleteAtTail();
return;
} else {
Node current = dynamicTraversal(i);
current.prev.next = current.next;
current.next.prev = current.prev;
}
size--;
}
- Time complexity: \(O(n) \mid \Omega(1) \mid \Theta(n)\)
public void deleteAtHead() {
if (size == 0) return;
if (size == 1) {
head = null;
tail = null;
} else {
head = head.next;
head.prev = null;
}
size--;
}
- Time complexity: \(O(1) \mid \Omega(1) \mid \Theta(1)\)
public void deleteAtTail() {
if (size == 0) return;
if (size == 1) {
head = null;
tail = null;
} else {
tail = tail.prev;
tail.next = null;
}
size--;
}
- Time complexity: \(O(1) \mid \Omega(1) \mid \Theta(1)\)
public void deleteAllOf(int x) {
Node current = head;
while (current != null) {
if (current.val == x) {
if (current == head) {
deleteAtHead();
current = head;
} else if (current == tail) {
deleteAtTail();
break;
} else {
current.prev.next = current.next;
current.next.prev = current.prev;
current = current.next;
size--;
}
} else {
current = current.next;
}
}
}
- Time complexity: \(O(n) \mid \Omega(n) \mid \Theta(n)\)
MyStack
Hash-Based Data Structure
Tree Data Structure
Test Classes
MyArrayList
package test;
import data_structure.non_generic.MyArrayList;
import data_structure.non_generic.MyList;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Iterator;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class TestMyArrayList {
private MyList myList;
@BeforeEach
void setup() {
myList = new MyArrayList();
}
@Test
void testSize() {
assertEquals(0, myList.size());
myList.append(1);
assertEquals(1, myList.size());
myList.append(2);
assertEquals(2, myList.size());
}
@Test
void testAppend() {
myList.append(1);
myList.append(2);
myList.append(3);
assertEquals(1, myList.get(0));
assertEquals(2, myList.get(1));
assertEquals(3, myList.get(2));
}
@Test
void testInsertAtIndex() {
myList.append(1);
myList.append(3);
myList.insertAtIndex(2, 1);
assertEquals(1, myList.get(0));
assertEquals(2, myList.get(1));
assertEquals(3, myList.get(2));
}
@Test
void testSetAtIndex() {
myList.append(1);
myList.append(2);
myList.setAtIndex(3, 1);
assertEquals(1, myList.get(0));
assertEquals(3, myList.get(1));
}
@Test
void testGet() {
myList.append(1);
myList.append(2);
myList.append(3);
assertEquals(1, myList.get(0));
assertEquals(2, myList.get(1));
assertEquals(3, myList.get(2));
}
@Test
void testDeleteAtIndex() {
myList.append(1);
myList.append(2);
myList.append(3);
myList.deleteAtIndex(1);
assertEquals(1, myList.get(0));
assertEquals(3, myList.get(1));
assertEquals(2, myList.size());
}
@Test
void testDeleteAllOf() {
myList.append(1);
myList.append(2);
myList.append(1);
myList.append(3);
myList.deleteAllOf(1);
assertEquals(2, myList.get(0));
assertEquals(3, myList.get(1));
assertEquals(2, myList.size());
}
@Test
void testToArray() {
myList.append(1);
myList.append(2);
myList.append(3);
int[] arr = myList.toArray();
assertArrayEquals(new int[]{1, 2, 3}, arr);
}
@Test
void testToList() {
myList.append(1);
myList.append(2);
myList.append(3);
List<Integer> list = myList.toList();
assertEquals(List.of(1, 2, 3), list);
}
@Test
void testIterator() {
myList.append(1);
myList.append(2);
myList.append(3);
Iterator<Integer> iterator = myList.iterator();
assertTrue(iterator.hasNext());
assertEquals(1, iterator.next());
assertTrue(iterator.hasNext());
assertEquals(2, iterator.next());
assertTrue(iterator.hasNext());
assertEquals(3, iterator.next());
assertFalse(iterator.hasNext());
}
@Test
void testEdgeCases() {
// Test inserting at the beginning
myList.insertAtIndex(1, 0);
assertEquals(1, myList.get(0));
assertEquals(1, myList.size());
// Test appending at the end
myList.append(2);
assertEquals(2, myList.get(1));
assertEquals(2, myList.size());
// Test setting at the beginning
myList.setAtIndex(3, 0);
assertEquals(3, myList.get(0));
// Test setting at the end
myList.setAtIndex(4, 1);
assertEquals(4, myList.get(1));
// Test deleting at the beginning
myList.deleteAtIndex(0);
assertEquals(4, myList.get(0));
assertEquals(1, myList.size());
// Test deleting at the end
myList.deleteAtIndex(0);
assertEquals(0, myList.size());
// Test inserting out of bounds (should throw exception)
assertThrows(IndexOutOfBoundsException.class, () -> myList.insertAtIndex(5, 5));
// Test getting out of bounds
assertThrows(IndexOutOfBoundsException.class, () -> myList.get(2));
// Test setting out of bounds
assertThrows(IndexOutOfBoundsException.class, () -> myList.setAtIndex(10, 1));
}
@Test
void testEmptyListOperations() {
// Ensure delete operation on empty list doesn't throw unexpected errors
assertThrows(IndexOutOfBoundsException.class, () -> myList.deleteAtIndex(0));
// Ensure get operation on empty list throws an exception
assertThrows(IndexOutOfBoundsException.class, () -> myList.get(0));
// Ensure set operation on empty list throws an exception
assertThrows(IndexOutOfBoundsException.class, () -> myList.setAtIndex(1, 0));
}
@Test
void testToString() {
assertEquals("[]", myList.toString());
myList.append(1);
assertEquals("[1]", myList.toString());
myList.append(2);
assertEquals("[1, 2]", myList.toString());
myList.append(3);
assertEquals("[1, 2, 3]", myList.toString());
}
@Test
void testLargeNumberOfElements() {
// Append a large number of elements to test list expansion
for (int i = 0; i < 1000; i++) {
myList.append(i);
}
assertEquals(1000, myList.size());
assertEquals(999, myList.get(999));
// Delete some elements and check if the list is updated correctly
myList.deleteAtIndex(500);
assertEquals(999, myList.size());
assertEquals(501, myList.get(500));
}
@Test
void testEdgeCasesEnhanced() {
// Test appending minimum and maximum integer values
myList.append(Integer.MIN_VALUE);
myList.append(Integer.MAX_VALUE);
assertEquals(Integer.MIN_VALUE, myList.get(0));
assertEquals(Integer.MAX_VALUE, myList.get(1));
// Test inserting in the middle of a list with minimum and maximum values
myList.insertAtIndex(0, 1);
assertEquals(Integer.MIN_VALUE, myList.get(0));
assertEquals(0, myList.get(1));
assertEquals(Integer.MAX_VALUE, myList.get(2));
// Test deleting non-existent element
myList.deleteAllOf(999); // Element 999 is not in the list
assertEquals(3, myList.size());
// Test appending and deleting duplicate values
myList.append(5);
myList.append(5);
myList.deleteAllOf(5);
assertEquals(3, myList.size()); // Only the original three elements should remain
// Test consecutive deletions
myList.deleteAtIndex(0);
myList.deleteAtIndex(0);
myList.deleteAtIndex(0);
assertTrue(myList.isEmpty());
// Test appending and getting a very large number of duplicates
for (int i = 0; i < 100; i++) {
myList.append(42);
}
assertEquals(100, myList.size());
assertEquals(42, myList.get(99));
myList.deleteAllOf(42);
assertTrue(myList.isEmpty());
}
}
MyLinkedList
Note: All linked list implementation in this thread uses this test class.
package test;
import data_structure.non_generic.MyLinkedList;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import static org.junit.jupiter.api.Assertions.*;
public class TestMyLinkedList {
private MyLinkedList myList;
@BeforeEach
void setup() {
myList = new MyLinkedList();
}
@Test
void testSize() {
assertEquals(0, myList.size());
myList.append(1);
assertEquals(1, myList.size());
myList.append(2);
assertEquals(2, myList.size());
}
@Test
void testAppend() {
myList.append(1);
myList.append(2);
myList.append(3);
assertEquals(1, myList.get(0));
assertEquals(2, myList.get(1));
assertEquals(3, myList.get(2));
}
@Test
void testInsertAtIndex() {
myList.append(1);
myList.append(3);
myList.insertAtIndex(2, 1);
assertEquals(1, myList.get(0));
assertEquals(2, myList.get(1));
assertEquals(3, myList.get(2));
}
@Test
void testInsertAtHead() {
myList.insertAtHead(1);
myList.insertAtHead(2);
assertEquals(2, myList.get(0));
assertEquals(1, myList.get(1));
assertEquals(2, myList.size());
}
@Test
void testInsertAtTail() {
myList.insertAtTail(1);
myList.insertAtTail(2);
assertEquals(1, myList.get(0));
assertEquals(2, myList.get(1));
assertEquals(2, myList.size());
}
@Test
void testSetAtIndex() {
myList.append(1);
myList.append(2);
myList.setAtIndex(3, 1);
assertEquals(1, myList.get(0));
assertEquals(3, myList.get(1));
}
@Test
void testGet() {
myList.append(1);
myList.append(2);
myList.append(3);
assertEquals(1, myList.get(0));
assertEquals(2, myList.get(1));
assertEquals(3, myList.get(2));
}
@Test
void testDeleteAtIndex() {
myList.append(1);
myList.append(2);
myList.append(3);
myList.deleteAtIndex(1);
assertEquals(1, myList.get(0));
assertEquals(3, myList.get(1));
assertEquals(2, myList.size());
}
@Test
void testDeleteAtHead() {
myList.append(1);
myList.append(2);
myList.deleteAtHead();
assertEquals(2, myList.get(0));
assertEquals(1, myList.size());
}
@Test
void testDeleteAtTail() {
myList.append(1);
myList.append(2);
myList.append(3);
myList.deleteAtTail();
assertEquals(2, myList.get(1));
assertEquals(2, myList.size());
}
@Test
void testDeleteAllOf() {
myList.append(1);
myList.append(2);
myList.append(1);
myList.append(3);
myList.deleteAllOf(1);
assertEquals(2, myList.get(0));
assertEquals(3, myList.get(1));
assertEquals(2, myList.size());
myList.deleteAllOf(1);
myList.deleteAllOf(0);
assertEquals(2, myList.size());
}
@Test
void testToArray() {
myList.append(1);
myList.append(2);
myList.append(3);
int[] arr = myList.toArray();
assertArrayEquals(new int[]{1, 2, 3}, arr);
}
@Test
void testToList() {
myList.append(1);
myList.append(2);
myList.append(3);
List<Integer> list = myList.toList();
assertEquals(List.of(1, 2, 3), list);
}
@Test
void testIterator() {
myList.append(1);
myList.append(2);
myList.append(3);
Iterator<Integer> iterator = myList.iterator();
assertTrue(iterator.hasNext());
assertEquals(1, iterator.next());
assertTrue(iterator.hasNext());
assertEquals(2, iterator.next());
assertTrue(iterator.hasNext());
assertEquals(3, iterator.next());
assertFalse(iterator.hasNext());
}
@Test
void testIteratorEdgeCases() {
Iterator<Integer> iterator = myList.iterator();
assertFalse(iterator.hasNext());
assertThrows(NoSuchElementException.class, iterator::next);
myList.append(1);
myList.append(2);
iterator = myList.iterator();
assertTrue(iterator.hasNext());
assertEquals(1, iterator.next());
assertEquals(2, iterator.next());
assertFalse(iterator.hasNext());
}
@Test
void testToString() {
assertEquals("[]", myList.toString());
myList.append(1);
assertEquals("[1]", myList.toString());
myList.append(2);
assertEquals("[1, 2]", myList.toString());
myList.append(3);
assertEquals("[1, 2, 3]", myList.toString());
}
@Test
void testBoundaryConditions() {
// Test inserting and deleting on empty list
assertThrows(IndexOutOfBoundsException.class, () -> myList.deleteAtIndex(0));
assertThrows(IndexOutOfBoundsException.class, () -> myList.get(0));
// Add to head, delete head, check size adjustments
myList.insertAtHead(10);
assertEquals(1, myList.size());
myList.deleteAtHead();
assertEquals(0, myList.size());
}
@Test
void testInsertAtInvalidIndex() {
assertThrows(IndexOutOfBoundsException.class, () -> myList.insertAtIndex(1, -1)); // Negative index
assertThrows(IndexOutOfBoundsException.class, () -> myList.insertAtIndex(1, 1)); // Index > size
}
@Test
void testDeleteAtInvalidIndex() {
assertThrows(IndexOutOfBoundsException.class, () -> myList.deleteAtIndex(-1)); // Negative index
assertThrows(IndexOutOfBoundsException.class, () -> myList.deleteAtIndex(0)); // Empty list
myList.append(1);
assertThrows(IndexOutOfBoundsException.class, () -> myList.deleteAtIndex(1)); // Index > size
}
@Test
void testSetAtInvalidIndex() {
assertThrows(IndexOutOfBoundsException.class, () -> myList.setAtIndex(1, -1)); // Negative index
assertThrows(IndexOutOfBoundsException.class, () -> myList.setAtIndex(1, 0)); // Index > size
}
@Test
void testDeleteAllOfNonExistentValue() {
myList.append(1);
myList.append(2);
myList.deleteAllOf(3); // Value not present
assertEquals(2, myList.size()); // Size should remain unchanged
}
@Test
void testInsertMultipleElements() {
myList.append(1);
myList.append(2);
myList.append(3);
myList.insertAtIndex(4, 1);
assertEquals(4, myList.get(1));
assertEquals(3, myList.get(3));
}
@Test
void testDeleteAllElements() {
myList.append(1);
myList.append(2);
myList.append(3);
myList.deleteAllOf(1);
myList.deleteAllOf(2);
myList.deleteAllOf(3);
assertEquals(0, myList.size());
assertTrue(myList.isEmpty());
}
@Test
void testConsecutiveOperations() {
myList.append(1);
myList.append(2);
myList.insertAtIndex(3, 1); // List should be [1, 3, 2]
assertEquals(3, myList.get(1));
myList.deleteAtIndex(0); // List should be [3, 2]
assertEquals(3, myList.get(0));
}
@Test
void testLargeList() {
for (int i = 0; i < 10000; i++) {
myList.append(i);
}
assertEquals(10000, myList.size());
assertEquals(9999, myList.get(9999));
}
@Test
void testIteratorAfterModification() {
myList.append(1);
myList.append(2);
Iterator<Integer> iterator = myList.iterator();
assertTrue(iterator.hasNext());
assertEquals(1, iterator.next());
myList.append(3); // Modify list
assertTrue(iterator.hasNext());
assertEquals(2, iterator.next());
assertTrue(iterator.hasNext());
assertEquals(3, iterator.next());
assertFalse(iterator.hasNext());
}
}