refactor: LinkedQueue (#5352)

This commit is contained in:
Alex Klymenko 2024-08-21 19:39:09 +02:00 committed by GitHub
parent 39ecf70857
commit 5149051e95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 280 additions and 117 deletions

View File

@ -2,216 +2,200 @@ package com.thealgorithms.datastructures.queues;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.StringJoiner;
public class LinkedQueue<T> implements Iterable<T> { public class LinkedQueue<T> implements Iterable<T> {
static class Node<T> { /**
* Node class representing each element in the queue.
*/
private static class Node<T> {
T data; T data;
Node<T> next; Node<T> next;
Node() {
this(null);
}
Node(T data) { Node(T data) {
this(data, null);
}
Node(T data, Node<T> next) {
this.data = data; this.data = data;
this.next = next; this.next = null;
} }
} }
/** private Node<T> front; // Front of the queue
* Front of Queue private Node<T> rear; // Rear of the queue
*/ private int size; // Size of the queue
private Node<T> front;
/** /**
* Rear of Queue * Initializes an empty LinkedQueue.
*/
private Node<T> rear;
/**
* Size of Queue
*/
private int size;
/**
* Init LinkedQueue
*/ */
public LinkedQueue() { public LinkedQueue() {
front = null;
front = new Node<>(); rear = null;
rear = front; size = 0;
} }
/** /**
* Check if queue is empty * Checks if the queue is empty.
* *
* @return true if queue is empty, otherwise false * @return true if the queue is empty, otherwise false.
*/ */
public boolean isEmpty() { public boolean isEmpty() {
return size == 0; return size == 0;
} }
/** /**
* Add element to rear of queue * Adds an element to the rear of the queue.
* *
* @param data insert value * @param data the element to insert.
* @throws IllegalArgumentException if data is null.
*/ */
public void enqueue(T data) { public void enqueue(T data) {
if (data == null) {
throw new IllegalArgumentException("Cannot enqueue null data");
}
Node<T> newNode = new Node<>(data); Node<T> newNode = new Node<>(data);
rear.next = newNode; if (isEmpty()) {
front = newNode;
} else {
rear.next = newNode;
}
rear = newNode; rear = newNode;
/* make rear point at last node */
size++; size++;
} }
/** /**
* Remove element at the front of queue * Removes and returns the element at the front of the queue.
* *
* @return element at the front of queue * @return the element at the front of the queue.
* @throws NoSuchElementException if the queue is empty.
*/ */
public T dequeue() { public T dequeue() {
if (isEmpty()) { if (isEmpty()) {
throw new NoSuchElementException("queue is empty"); throw new NoSuchElementException("Queue is empty");
} }
Node<T> destroy = front.next;
T retValue = destroy.data; T retValue = front.data;
front.next = front.next.next; front = front.next;
/* clear let GC do it's work */
size--; size--;
if (isEmpty()) { if (isEmpty()) {
front = rear; rear = null;
} }
return retValue; return retValue;
} }
/** /**
* Peek element at the front of queue without removing * Returns the element at the front of the queue without removing it.
* *
* @return element at the front * @return the element at the front of the queue.
* @throws NoSuchElementException if the queue is empty.
*/ */
public T peekFront() { public T peekFront() {
if (isEmpty()) { if (isEmpty()) {
throw new NoSuchElementException("queue is empty"); throw new NoSuchElementException("Queue is empty");
} }
return front.next.data; return front.data;
} }
/** /**
* Peek element at the rear of queue without removing * Returns the element at the rear of the queue without removing it.
* *
* @return element at the front * @return the element at the rear of the queue.
* @throws NoSuchElementException if the queue is empty.
*/ */
public T peekRear() { public T peekRear() {
if (isEmpty()) { if (isEmpty()) {
throw new NoSuchElementException("queue is empty"); throw new NoSuchElementException("Queue is empty");
} }
return rear.data; return rear.data;
} }
/** /**
* Peeks the element at the index and * Returns the element at the specified position (1-based index).
* returns the value *
* @param pos at which to peek * @param pos the position to peek at (1-based index).
* @return the element at the specified position.
* @throws IndexOutOfBoundsException if the position is out of range.
*/ */
public T peek(int pos) { public T peek(int pos) {
if (pos > size) { if (pos < 1 || pos > size) {
throw new IndexOutOfBoundsException("Position %s out of range!".formatted(pos)); throw new IndexOutOfBoundsException("Position " + pos + " out of range!");
} }
Node<T> node = front; Node<T> node = front;
while (pos-- > 0) { for (int i = 1; i < pos; i++) {
node = node.next; node = node.next;
} }
return node.data; return node.data;
} }
/** /**
* Node iterator, allows to travel through * Returns an iterator over the elements in the queue.
* the nodes using for() loop or forEach(Consumer) *
* @return an iterator over the elements in the queue.
*/ */
@Override @Override
public Iterator<T> iterator() { public Iterator<T> iterator() {
return new Iterator<>() { return new Iterator<>() {
Node<T> node = front; private Node<T> current = front;
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return node.next != null; return current != null;
} }
@Override @Override
public T next() { public T next() {
if (hasNext()) { if (!hasNext()) {
node = node.next; throw new NoSuchElementException();
return node.data;
} }
throw new NoSuchElementException();
T data = current.data;
current = current.next;
return data;
} }
}; };
} }
/** /**
* Return size of queue * Returns the size of the queue.
* *
* @return size of queue * @return the size of the queue.
*/ */
public int size() { public int size() {
return size; return size;
} }
/** /**
* Clear all nodes in queue * Clears all elements from the queue.
*/ */
public void clear() { public void clear() {
while (size > 0) { front = null;
dequeue(); rear = null;
} size = 0;
} }
/**
* Returns a string representation of the queue.
*
* @return a string representation of the queue.
*/
@Override @Override
public String toString() { public String toString() {
StringJoiner join = new StringJoiner(", "); // separator of ', ' if (isEmpty()) {
Node<T> travel = front; return "[]";
while ((travel = travel.next) != null) {
join.add(String.valueOf(travel.data));
} }
return '[' + join.toString() + ']';
}
/* Driver Code */ StringBuilder sb = new StringBuilder("[");
public static void main(String[] args) { Node<T> current = front;
LinkedQueue<Integer> queue = new LinkedQueue<>(); while (current != null) {
assert queue.isEmpty(); sb.append(current.data);
if (current.next != null) {
queue.enqueue(1); sb.append(", ");
/* 1 */ }
queue.enqueue(2); current = current.next;
/* 1 2 */ }
queue.enqueue(3); sb.append(']');
/* 1 2 3 */ return sb.toString();
System.out.println(queue);
/* [1, 2, 3] */
assert queue.size() == 3;
assert queue.dequeue() == 1;
assert queue.peekFront() == 2;
assert queue.peekRear() == 3;
queue.clear();
assert queue.isEmpty();
System.out.println(queue);
/* [] */
} }
} }

View File

@ -1,30 +1,209 @@
package com.thealgorithms.datastructures.queues; package com.thealgorithms.datastructures.queues;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class LinkedQueueTest { class LinkedQueueTest {
private LinkedQueue<Integer> queue;
@BeforeEach
void setUp() {
queue = new LinkedQueue<>();
}
@Test @Test
public void testQue() { void testIsEmptyOnNewQueue() {
LinkedQueue<Integer> queue = new LinkedQueue<>(); assertTrue(queue.isEmpty(), "Queue should be empty on initialization.");
for (int i = 1; i < 5; i++) { }
@Test
void testEnqueueAndSize() {
queue.enqueue(10);
assertFalse(queue.isEmpty(), "Queue should not be empty after enqueue.");
assertEquals(1, queue.size(), "Queue size should be 1 after one enqueue.");
queue.enqueue(20);
queue.enqueue(30);
assertEquals(3, queue.size(), "Queue size should be 3 after three enqueues.");
}
@Test
void testDequeueOnSingleElementQueue() {
queue.enqueue(10);
assertEquals(10, queue.dequeue(), "Dequeued element should be the same as the enqueued one.");
assertTrue(queue.isEmpty(), "Queue should be empty after dequeueing the only element.");
}
@Test
void testDequeueMultipleElements() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
assertEquals(10, queue.dequeue(), "First dequeued element should be the first enqueued one.");
assertEquals(20, queue.dequeue(), "Second dequeued element should be the second enqueued one.");
assertEquals(30, queue.dequeue(), "Third dequeued element should be the third enqueued one.");
assertTrue(queue.isEmpty(), "Queue should be empty after dequeueing all elements.");
}
@Test
void testDequeueOnEmptyQueue() {
org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, () -> queue.dequeue(), "Dequeueing from an empty queue should throw NoSuchElementException.");
}
@Test
void testPeekFrontOnEmptyQueue() {
org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, () -> queue.peekFront(), "Peeking front on an empty queue should throw NoSuchElementException.");
}
@Test
void testPeekRearOnEmptyQueue() {
org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, () -> queue.peekRear(), "Peeking rear on an empty queue should throw NoSuchElementException.");
}
@Test
void testPeekFront() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
assertEquals(10, queue.peekFront(), "Peek front should return the first enqueued element.");
assertEquals(10, queue.peekFront(), "Peek front should not remove the element.");
assertEquals(3, queue.size(), "Queue size should remain unchanged after peek.");
}
@Test
void testPeekRear() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
assertEquals(30, queue.peekRear(), "Peek rear should return the last enqueued element.");
assertEquals(30, queue.peekRear(), "Peek rear should not remove the element.");
assertEquals(3, queue.size(), "Queue size should remain unchanged after peek.");
}
@Test
void testPeekAtPosition() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
assertEquals(10, queue.peek(1), "Peek at position 1 should return the first enqueued element.");
assertEquals(20, queue.peek(2), "Peek at position 2 should return the second enqueued element.");
assertEquals(30, queue.peek(3), "Peek at position 3 should return the third enqueued element.");
}
@Test
void testPeekAtInvalidPosition() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
org.junit.jupiter.api.Assertions.assertThrows(IndexOutOfBoundsException.class, () -> queue.peek(4), "Peeking at a position greater than size should throw IndexOutOfBoundsException.");
org.junit.jupiter.api.Assertions.assertThrows(IndexOutOfBoundsException.class, () -> queue.peek(0), "Peeking at position 0 should throw IndexOutOfBoundsException.");
}
@Test
void testClearQueue() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
queue.clear();
assertTrue(queue.isEmpty(), "Queue should be empty after clear.");
assertEquals(0, queue.size(), "Queue size should be 0 after clear.");
}
@Test
void testIterator() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
Iterator<Integer> it = queue.iterator();
assertTrue(it.hasNext(), "Iterator should have next element.");
assertEquals(10, it.next(), "First iterator value should be the first enqueued element.");
assertEquals(20, it.next(), "Second iterator value should be the second enqueued element.");
assertEquals(30, it.next(), "Third iterator value should be the third enqueued element.");
assertFalse(it.hasNext(), "Iterator should not have next element after last element.");
org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, it::next, "Calling next() on exhausted iterator should throw NoSuchElementException.");
}
@Test
void testToString() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
assertEquals("[10, 20, 30]", queue.toString(), "toString should return a properly formatted string representation of the queue.");
}
@Test
void testEnqueueAfterDequeue() {
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
assertEquals(10, queue.dequeue(), "Dequeued element should be 10.");
assertEquals(20, queue.dequeue(), "Dequeued element should be 20.");
queue.enqueue(40);
assertEquals(30, queue.peekFront(), "Peek front should return 30 after dequeuing and enqueuing new elements.");
assertEquals(40, queue.peekRear(), "Peek rear should return 40 after enqueuing new elements.");
}
@Test
void testQueueMaintainsOrder() {
for (int i = 1; i <= 100; i++) {
queue.enqueue(i); queue.enqueue(i);
} }
assertEquals(queue.peekRear(), 4); for (int i = 1; i <= 100; i++) {
assertEquals(queue.peek(2), 2); assertEquals(i, queue.dequeue(), "Queue should maintain the correct order of elements.");
}
assertEquals(queue.peek(4), 4); assertTrue(queue.isEmpty(), "Queue should be empty after dequeuing all elements.");
}
final int[] element = {1}; @Test
void testSizeAfterOperations() {
assertEquals(0, queue.size(), "Initial queue size should be 0.");
// iterates over all the elements present queue.enqueue(10);
// as in the form of nodes assertEquals(1, queue.size(), "Queue size should be 1 after one enqueue.");
queue.forEach(integer -> {
if (element[0]++ != integer) { queue.enqueue(20);
throw new AssertionError(); assertEquals(2, queue.size(), "Queue size should be 2 after two enqueues.");
}
}); queue.dequeue();
assertEquals(1, queue.size(), "Queue size should be 1 after one dequeue.");
queue.clear();
assertEquals(0, queue.size(), "Queue size should be 0 after clear.");
}
@Test
void testQueueToStringOnEmptyQueue() {
assertEquals("[]", queue.toString(), "toString on empty queue should return '[]'.");
}
@Test
void testEnqueueNull() {
org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> queue.enqueue(null), "Cannot enqueue null data.");
}
@Test
void testIteratorOnEmptyQueue() {
Iterator<Integer> it = queue.iterator();
assertFalse(it.hasNext(), "Iterator on empty queue should not have next element.");
org.junit.jupiter.api.Assertions.assertThrows(NoSuchElementException.class, it::next, "Calling next() on empty queue iterator should throw NoSuchElementException.");
} }
} }