From 5149051e95aebba639b160297f8666f8cb16301d Mon Sep 17 00:00:00 2001 From: Alex Klymenko Date: Wed, 21 Aug 2024 19:39:09 +0200 Subject: [PATCH] refactor: `LinkedQueue` (#5352) --- .../datastructures/queues/LinkedQueue.java | 190 ++++++++-------- .../queues/LinkedQueueTest.java | 207 ++++++++++++++++-- 2 files changed, 280 insertions(+), 117 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/queues/LinkedQueue.java b/src/main/java/com/thealgorithms/datastructures/queues/LinkedQueue.java index 5fba2ff6..6ba16199 100644 --- a/src/main/java/com/thealgorithms/datastructures/queues/LinkedQueue.java +++ b/src/main/java/com/thealgorithms/datastructures/queues/LinkedQueue.java @@ -2,216 +2,200 @@ package com.thealgorithms.datastructures.queues; import java.util.Iterator; import java.util.NoSuchElementException; -import java.util.StringJoiner; public class LinkedQueue implements Iterable { - static class Node { - + /** + * Node class representing each element in the queue. + */ + private static class Node { T data; Node next; - Node() { - this(null); - } - Node(T data) { - this(data, null); - } - - Node(T data, Node next) { this.data = data; - this.next = next; + this.next = null; } } - /** - * Front of Queue - */ - private Node front; + private Node front; // Front of the queue + private Node rear; // Rear of the queue + private int size; // Size of the queue /** - * Rear of Queue - */ - private Node rear; - - /** - * Size of Queue - */ - private int size; - - /** - * Init LinkedQueue + * Initializes an empty LinkedQueue. */ public LinkedQueue() { - - front = new Node<>(); - rear = front; + front = null; + rear = null; + 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() { 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) { + if (data == null) { + throw new IllegalArgumentException("Cannot enqueue null data"); + } + Node newNode = new Node<>(data); - rear.next = newNode; + if (isEmpty()) { + front = newNode; + } else { + rear.next = newNode; + } rear = newNode; - /* make rear point at last node */ 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() { if (isEmpty()) { - throw new NoSuchElementException("queue is empty"); + throw new NoSuchElementException("Queue is empty"); } - Node destroy = front.next; - T retValue = destroy.data; - front.next = front.next.next; - /* clear let GC do it's work */ + + T retValue = front.data; + front = front.next; size--; if (isEmpty()) { - front = rear; + rear = null; } 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() { 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() { if (isEmpty()) { - throw new NoSuchElementException("queue is empty"); + throw new NoSuchElementException("Queue is empty"); } return rear.data; } /** - * Peeks the element at the index and - * returns the value - * @param pos at which to peek + * Returns the element at the specified position (1-based index). + * + * @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) { - if (pos > size) { - throw new IndexOutOfBoundsException("Position %s out of range!".formatted(pos)); + if (pos < 1 || pos > size) { + throw new IndexOutOfBoundsException("Position " + pos + " out of range!"); } + Node node = front; - while (pos-- > 0) { + for (int i = 1; i < pos; i++) { node = node.next; } return node.data; } /** - * Node iterator, allows to travel through - * the nodes using for() loop or forEach(Consumer) + * Returns an iterator over the elements in the queue. + * + * @return an iterator over the elements in the queue. */ - @Override public Iterator iterator() { return new Iterator<>() { - Node node = front; + private Node current = front; @Override public boolean hasNext() { - return node.next != null; + return current != null; } @Override public T next() { - if (hasNext()) { - node = node.next; - return node.data; + if (!hasNext()) { + throw new NoSuchElementException(); } - 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() { return size; } /** - * Clear all nodes in queue + * Clears all elements from the queue. */ public void clear() { - while (size > 0) { - dequeue(); - } + front = null; + rear = null; + size = 0; } + /** + * Returns a string representation of the queue. + * + * @return a string representation of the queue. + */ @Override public String toString() { - StringJoiner join = new StringJoiner(", "); // separator of ', ' - Node travel = front; - while ((travel = travel.next) != null) { - join.add(String.valueOf(travel.data)); + if (isEmpty()) { + return "[]"; } - return '[' + join.toString() + ']'; - } - /* Driver Code */ - public static void main(String[] args) { - LinkedQueue queue = new LinkedQueue<>(); - assert queue.isEmpty(); - - queue.enqueue(1); - /* 1 */ - queue.enqueue(2); - /* 1 2 */ - queue.enqueue(3); - /* 1 2 3 */ - 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); - /* [] */ + StringBuilder sb = new StringBuilder("["); + Node current = front; + while (current != null) { + sb.append(current.data); + if (current.next != null) { + sb.append(", "); + } + current = current.next; + } + sb.append(']'); + return sb.toString(); } } diff --git a/src/test/java/com/thealgorithms/datastructures/queues/LinkedQueueTest.java b/src/test/java/com/thealgorithms/datastructures/queues/LinkedQueueTest.java index 7bebf13e..157d771b 100644 --- a/src/test/java/com/thealgorithms/datastructures/queues/LinkedQueueTest.java +++ b/src/test/java/com/thealgorithms/datastructures/queues/LinkedQueueTest.java @@ -1,30 +1,209 @@ package com.thealgorithms.datastructures.queues; 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; class LinkedQueueTest { + + private LinkedQueue queue; + + @BeforeEach + void setUp() { + queue = new LinkedQueue<>(); + } + @Test - public void testQue() { - LinkedQueue queue = new LinkedQueue<>(); - for (int i = 1; i < 5; i++) { + void testIsEmptyOnNewQueue() { + assertTrue(queue.isEmpty(), "Queue should be empty on initialization."); + } + + @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 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); } - assertEquals(queue.peekRear(), 4); - assertEquals(queue.peek(2), 2); + for (int i = 1; i <= 100; i++) { + 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 - // as in the form of nodes - queue.forEach(integer -> { - if (element[0]++ != integer) { - throw new AssertionError(); - } - }); + queue.enqueue(10); + assertEquals(1, queue.size(), "Queue size should be 1 after one enqueue."); + + queue.enqueue(20); + 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 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."); } }