diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/Stack.java b/src/main/java/com/thealgorithms/datastructures/stacks/Stack.java new file mode 100644 index 000000000..87058bd97 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/stacks/Stack.java @@ -0,0 +1,51 @@ +package com.thealgorithms.datastructures.stacks; + +/** + * A generic interface for Stack data structures. + * + * @param the type of elements in this stack + */ +public interface Stack { + + /** + * Adds an element to the top of the stack. + * + * @param value The element to add. + */ + void push(T value); + + /** + * Removes the element at the top of this stack and returns it. + * + * @return The element popped from the stack. + * @throws IllegalStateException if the stack is empty. + */ + T pop(); + + /** + * Returns the element at the top of this stack without removing it. + * + * @return The element at the top of this stack. + * @throws IllegalStateException if the stack is empty. + */ + T peek(); + + /** + * Tests if this stack is empty. + * + * @return {@code true} if this stack is empty; {@code false} otherwise. + */ + boolean isEmpty(); + + /** + * Returns the size of this stack. + * + * @return The number of elements in this stack. + */ + int size(); + + /** + * Removes all elements from this stack. + */ + void makeEmpty(); +} diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java b/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java index cb2cb25e9..f98db7cc1 100644 --- a/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java +++ b/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java @@ -3,170 +3,80 @@ package com.thealgorithms.datastructures.stacks; /** * This class implements a Stack using a regular array. * - *

- * A stack is exactly what it sounds like. An element gets added to the top of - * the stack and only the element on the top may be removed. This is an example - * of an array implementation of a Stack. So an element can only be - * added/removed from the end of the array. In theory stack have no fixed size, - * but with an array implementation it does. + * @param the type of elements in this stack */ -public class StackArray { +public class StackArray implements Stack { - /** - * Driver Code - */ - public static void main(String[] args) { - // Declare a stack of maximum size 4 - StackArray myStackArray = new StackArray(4); - - assert myStackArray.isEmpty(); - assert !myStackArray.isFull(); - - // Populate the stack - myStackArray.push(5); - myStackArray.push(8); - myStackArray.push(2); - myStackArray.push(9); - - assert !myStackArray.isEmpty(); - assert myStackArray.isFull(); - assert myStackArray.peek() == 9; - assert myStackArray.pop() == 9; - assert myStackArray.peek() == 2; - assert myStackArray.size() == 3; - } - - /** - * Default initial capacity. - */ private static final int DEFAULT_CAPACITY = 10; - /** - * The max size of the Stack - */ private int maxSize; - - /** - * The array representation of the Stack - */ - private int[] stackArray; - - /** - * The top of the stack - */ + private T[] stackArray; private int top; - /** - * init Stack with DEFAULT_CAPACITY - */ + @SuppressWarnings("unchecked") public StackArray() { this(DEFAULT_CAPACITY); } - /** - * Constructor - * - * @param size Size of the Stack - */ + @SuppressWarnings("unchecked") public StackArray(int size) { - maxSize = size; - stackArray = new int[maxSize]; - top = -1; + if (size <= 0) { + throw new IllegalArgumentException("Stack size must be greater than 0"); + } + this.maxSize = size; + this.stackArray = (T[]) new Object[size]; + this.top = -1; } - /** - * Adds an element to the top of the stack - * - * @param value The element added - */ - public void push(int value) { - if (!isFull()) { // Checks for a full stack - top++; - stackArray[top] = value; - } else { + @Override + public void push(T value) { + if (isFull()) { resize(maxSize * 2); - push(value); // don't forget push after resizing } + stackArray[++top] = value; } - /** - * Removes the top element of the stack and returns the value you've removed - * - * @return value popped off the Stack - */ - public int pop() { - if (!isEmpty()) { // Checks for an empty stack - return stackArray[top--]; + @Override + public T pop() { + if (isEmpty()) { + throw new IllegalStateException("Stack is empty, cannot pop element"); } - - if (top < maxSize / 4) { + T value = stackArray[top--]; + if (top + 1 < maxSize / 4 && maxSize > DEFAULT_CAPACITY) { resize(maxSize / 2); - return pop(); // don't forget pop after resizing - } else { - System.out.println("The stack is already empty"); - return -1; } + return value; } - /** - * Returns the element at the top of the stack - * - * @return element at the top of the stack - */ - public int peek() { - if (!isEmpty()) { // Checks for an empty stack - return stackArray[top]; - } else { - System.out.println("The stack is empty, cant peek"); - return -1; + @Override + public T peek() { + if (isEmpty()) { + throw new IllegalStateException("Stack is empty, cannot peek element"); } + return stackArray[top]; } private void resize(int newSize) { - int[] transferArray = new int[newSize]; - - for (int i = 0; i < stackArray.length; i++) { - transferArray[i] = stackArray[i]; - } - // This reference change might be nice in here - stackArray = transferArray; + @SuppressWarnings("unchecked") T[] newArray = (T[]) new Object[newSize]; + System.arraycopy(stackArray, 0, newArray, 0, top + 1); + stackArray = newArray; maxSize = newSize; } - /** - * Returns true if the stack is empty - * - * @return true if the stack is empty - */ - public boolean isEmpty() { - return (top == -1); - } - - /** - * Returns true if the stack is full - * - * @return true if the stack is full - */ public boolean isFull() { - return (top + 1 == maxSize); + return top + 1 == maxSize; } - /** - * Deletes everything in the Stack - * - *

- * Doesn't delete elements in the array but if you call push method after - * calling makeEmpty it will overwrite previous values - */ - public void makeEmpty() { // Doesn't delete elements in the array but if you call + @Override + public boolean isEmpty() { + return top == -1; + } + + @Override public void makeEmpty() { // Doesn't delete elements in the array but if you call top = -1; // push method after calling makeEmpty it will overwrite previous values } - /** - * Return size of stack - * - * @return size of stack - */ + @Override public int size() { return top + 1; } diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java new file mode 100644 index 000000000..3cda2f547 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java @@ -0,0 +1,121 @@ +package com.thealgorithms.datastructures.stacks; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class StackArrayTest { + + private Stack stack; + + @BeforeEach + void setUp() { + stack = new StackArray<>(5); // Initialize a stack with capacity of 5 + } + + @Test + void testPushAndPop() { + stack.push(1); + stack.push(2); + stack.push(3); + stack.push(4); + stack.push(5); + + Assertions.assertEquals(5, stack.pop()); // Stack follows LIFO, so 5 should be popped first + Assertions.assertEquals(4, stack.pop()); // Next, 4 should be popped + Assertions.assertEquals(3, stack.pop()); // Followed by 3 + Assertions.assertEquals(2, stack.pop()); // Then 2 + Assertions.assertEquals(1, stack.pop()); // Finally 1 + } + + @Test + void testPeek() { + stack.push(10); + stack.push(20); + stack.push(30); + + Assertions.assertEquals(30, stack.peek()); // Peek should return 30, the top of the stack + Assertions.assertEquals(3, stack.size()); // Size should remain 3 after peek + + stack.pop(); + Assertions.assertEquals(20, stack.peek()); // After popping, peek should return 20 + } + + @Test + void testIsEmpty() { + Assertions.assertTrue(stack.isEmpty()); // Initially, the stack should be empty + stack.push(42); + Assertions.assertFalse(stack.isEmpty()); // After pushing an element, the stack should not be empty + stack.pop(); + Assertions.assertTrue(stack.isEmpty()); // After popping the only element, the stack should be empty again + } + + @Test + void testResizeOnPush() { + StackArray smallStack = new StackArray<>(2); // Start with a small stack size + smallStack.push(1); + smallStack.push(2); + Assertions.assertTrue(smallStack.isFull()); // Initially, the stack should be full + + smallStack.push(3); // This push should trigger a resize + Assertions.assertFalse(smallStack.isFull()); // The stack should no longer be full after resize + Assertions.assertEquals(3, smallStack.size()); // Size should be 3 after pushing 3 elements + + Assertions.assertEquals(3, smallStack.pop()); // LIFO behavior check + Assertions.assertEquals(2, smallStack.pop()); + Assertions.assertEquals(1, smallStack.pop()); + } + + @Test + void testResizeOnPop() { + StackArray stack = new StackArray<>(4); + stack.push(1); + stack.push(2); + stack.push(3); + stack.push(4); + + stack.pop(); // Removing elements should trigger a resize when less than 1/4 of the stack is used + stack.pop(); + stack.pop(); + Assertions.assertEquals(1, stack.size()); // After popping, only one element should remain + + stack.pop(); + Assertions.assertTrue(stack.isEmpty()); // The stack should be empty now + } + + @Test + void testMakeEmpty() { + stack.push(1); + stack.push(2); + stack.push(3); + stack.makeEmpty(); + + Assertions.assertTrue(stack.isEmpty()); // The stack should be empty after calling makeEmpty + Assertions.assertThrows(IllegalStateException.class, stack::pop); // Popping from empty stack should throw exception + } + + @Test + void testPopEmptyStackThrowsException() { + Assertions.assertThrows(IllegalStateException.class, stack::pop); // Popping from an empty stack should throw an exception + } + + @Test + void testPeekEmptyStackThrowsException() { + Assertions.assertThrows(IllegalStateException.class, stack::peek); // Peeking into an empty stack should throw an exception + } + + @Test + void testConstructorWithInvalidSizeThrowsException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new StackArray<>(0)); // Size 0 is invalid + Assertions.assertThrows(IllegalArgumentException.class, () -> new StackArray<>(-5)); // Negative size is invalid + } + + @Test + void testDefaultConstructor() { + StackArray defaultStack = new StackArray<>(); // Using default constructor + Assertions.assertEquals(0, defaultStack.size()); // Initially, size should be 0 + + defaultStack.push(1); + Assertions.assertEquals(1, defaultStack.size()); // After pushing, size should be 1 + } +}