Stacks

Reading time15 min

In brief

Article summary

In this article, we present you a data structure called the “stack”. With a stack, elements are removed in the opposite order in which they were added. This is particularly useful in many applications, and is omnipresent in computer science. In particular, calling functions uses a stack.

Below, we also propoe two possible implmentations for a stack, and show that they can be used independendly if the primitives abstract the details.

Main takeaways

  • Stacks are data structures where the first element to be added is the last element to be removed.

  • Depending on implementation of the stack, some operations may take longer than others.

  • Using primitives allows to hide implementation details.

Article contents

1 — What is a stack?

A stack is a data structure with restricted access, which means that its processing is limited to a few manipulations, called “primitives”. This type of data is managed in such a way that each new element is added to the top of the stack and the last element added is the first to be removed. This access method, where the last element added is the first to be removed, is called LIFO (Last In, First Out).

Stack operations Stack operations

Stacks are particularly useful for processes that require data to wait for later processing. This is the case when calculating an arithmetic expression in postfixed notation, where the data (operands) appear before the operator, for example, 3 2 + to perform the sum of 3 and 2.

Stacks are also ideal for making recursive processing iterative. The values corresponding to the recursive calls are pushed, and popping them simulates the return of the calls.

2 — Primitives

In a program using a stack, the implementation details of the data are abstracted away. The program simply pushes or pops elements from the stack using sub-programs known as “management primitives”. The main primitives for stack operations are:

  • initialize – Initializes the stack.
  • push – Adds an element to the top of the stack.
  • pop – Removes the top element from the stack.
  • is_empty – Returns true if the stack is empty, and false otherwise.

These primitives manage the stack in the form of “arrays” or “linked lists”.

3 — Implementation of stacks

Let’s have a look as possible implementations. In practice, each programming language offers a proper implementation of a stack. Here, we show possible implementations for pedagogical purpose.

3.1 — Use of arrays

The simplest representation of a stack is an array. If the maximum number of elements contained in the stack is known in advance, it is easy to define the size of the array. Otherwise, you need to define a sufficiently large array, or use a dynamic array.

Each new element in the stack is added to an empty cell in the array. Popping removes this element. An integer variable indicating the number of elements controls the state of the array.

Here is a possible implementation of a stack, using a dynamic size array (list) to store information.

# Needed imports
from typing import Any
from typing_extensions import Self



class Stack:

    """
        This is a possible implementation of a stack.
        Here, the stack manipulates a list.
        Therefore, it is of dynamic size.
    """



    def __init__ (self) -> Self:

        """
            This is the constructor of the class.
            It initializes the stack.
            In:
                * None.
            Out:
                * A new stack.
        """

        # Initialize data
        self.data = []
    


    def __str__ (self) -> str:

        """
            This method returns a string representation of the stack.
            In:
                * None.
            Out:
                * A string representation of the stack.
        """

        # Create string
        return str(self.data)
    


    def push (self, item: Any) -> None:

        """
            This method adds an element to the stack.
            The element is added at the end.
            In:
                * item: Element to add.
            Out:
                * None.
        """

        # Add element
        self.data.append(item)



    def pop (self) -> Any:

        """
            This method removes the last element of the stack.
            The element is removed from the end.
            In:
                * None.
            Out:
                * The last element of the stack.
        """

        # Remove element
        return self.data.pop()



    def is_empty (self) -> bool:

        """
            This method checks if the stack is empty.
            In:
                * None.
            Out:
                * True if the stack is empty, False otherwise.
        """

        # Check contents
        return len(self.data) == 0



# A few tests
stack = Stack()
stack.push(1)
print(stack)
stack.push(2)
print(stack)
stack.push(3)
print(stack)
element = stack.pop()
print(element, stack)
element = stack.pop()
print(element, stack)
element = stack.pop()
print(element, stack)
/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Stack.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Stack.java` to compile the code.
 * - Run the command `java -ea Stack` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */

// Needed imports

import java.util.ArrayList;
import java.util.List;

/**
 * This is a possible implementation of a stack.
 * Here, the stack manipulates a dynamically sized ArrayList.
 * This class should be put in a file named 'Stack.java'.
 */
public class Stack<T> {

    /**
     * Here is the attribute we need to define for data.
     */
    private List<T> data;


    /**
     * This is the constructor of the class.
     * It initializes the empty stack.
     */
    public Stack() {
        // Initialize data
        this.data = new ArrayList<>();
    }


    /**
     * This method returns a string representation of the stack.
     *
     * @return A string representation of the stack.
     */
    public String toString() {
        // Create string
        StringBuilder string = new StringBuilder("[");
        for (T d : data) {
            string.append(d).append(", ");
        }
        if (!data.isEmpty()) {
            string.setLength(string.length() - 2);
        }
        string.append("]");
        return string.toString();
    }


    /**
     * This method adds an element to the stack.
     * The element is added at the end.
     *
     * @param item Element to add.
     */
    public void push(T item) {
        // Add element
        data.add(item);
    }


    /**
     * This method removes the last element of the stack.
     *
     * @return The last element of the stack.
     */
    public T pop() {
        // Make sure the stack is not empty
        if (isEmpty()) {
            throw new IllegalStateException("Stack is empty");
        }

        // Remove element
        return data.remove(data.size() - 1);
    }


    /**
     * This method checks if the stack is empty.
     *
     * @return True if the stack is empty, False otherwise.
     */
    public boolean isEmpty() {
        // Check data size
        return data.isEmpty();
    }

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     *
     * @param args Command line arguments received.
     */
    public static void main(String[] args) {
        // A few tests
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        System.out.println(stack);
        stack.push(2);
        System.out.println(stack);
        stack.push(3);
        System.out.println(stack);
        int element = stack.pop();
        System.out.println(element + " " + stack);
        element = stack.pop();
        System.out.println(element + " " + stack);
        element = stack.pop();
        System.out.println(element + " " + stack);
    }

}

3.2 — Use of linked lists

Linked lists are more appropriate when the maximum number of elements to be stored is unknown. They define dynamic stacks that grow as they are filled. Like dynamic arrays, they ensure a good functionality regardless of the number of elements stored, which is not the case with static arrays.

With this organization, the stack becomes a node pointer. Each element is stored in a node which contains a pointer to the next node. The stack corresponds to the head pointer of the linked list. A second pointer indicating the end of the list makes it easier to manage, as it avoids having to systematically traverse the list to add an element at the end. This is known as a doubly linked list (see figure below). The stack and the end-of-list pointer must be accessible and modifiable by all subroutines or methods.

Doubly linked list Doubly linked list

Here is a possible implementation:

# Needed imports
from typing import Any
from typing_extensions import Self



class Node:

    """
        This class represents a node in a doubly linked list.
        Each node contains data, a reference to the next node, and a reference to the previous node.
    """



    def __init__(self, data: Any):

        """
            This is the constructor of the class.
            It initializes a node with the given data.
            In:
                * data: Data to store in the node.
            Out:
                * None.
        """

        # Here we initialize the node with the given data
        self.data = data
        self.next = None
        self.prev = None



class Stack:

    """
        This is a possible implementation of a stack using a doubly linked list.
        The stack can grow dynamically as elements are added.
    """



    def __init__ (self) -> Self:
        
        """
            This is the constructor of the class.
            It initializes an empty stack using a doubly linked list.
            In:
                * None.
            Out:
                * A new stack.
        """

        # Here we initialize the stack with no elements
        self.top = None
        self.nb_elements = 0



    def __str__ (self) -> str:

        """
            This method returns a string representation of the stack.
            In:
                * None.
            Out:
                * A string representation of the stack.
        """

        # Create string
        string = "["
        current = self.top
        while current:
            string += str(current.data) + ", "
            current = current.prev
        if self.nb_elements > 0:
            string = string[:-2]
        string += "]"
        return string



    def push (self, item: Any) -> None:

        """
            This method adds an element to the stack.
            The element is added at the top, and the counter is incremented.
            In:
                * item: Element to add.
            Out:
                * None.
        """
        # Create a new node with the item
        new_node = Node(item)

        # Link the new node with the current top node
        new_node.prev = self.top

        # Set the new node as the top node
        if self.top:
            self.top.next = new_node
        self.top = new_node

        # Increment the number of elements
        self.nb_elements += 1



    def pop (self) -> Any:
        
        """
            This method removes the last element of the stack.
            The element is removed and the counter is decremented.
            In:
                * None.
            Out:
                * The last element of the stack.
        """

        # Make sure the stack is not empty
        assert not self.is_empty()

        # Remove the top element
        element = self.top.data
        self.top = self.top.prev
        if self.top:
            self.top.next = None

        # Decrement the number of elements
        self.nb_elements -= 1
        return element



    def is_empty(self) -> bool:

        """
            This method checks if the stack is empty.
            In:
                * None.
            Out:
                * True if the stack is empty, False otherwise.
        """

        # Check if the stack is empty
        return self.nb_elements == 0



# A few tests
stack = Stack()
stack.push(1)
print(stack)
stack.push(2)
print(stack)
stack.push(3)
print(stack)
element = stack.pop()
print(element, stack)
element = stack.pop()
print(element, stack)
element = stack.pop()
print(element, stack)
/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Stack.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Stack.java` to compile the code.
 * - Run the command `java -ea Stack` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */

/**
 * This class represents a node in a doubly linked list.
 * Each node contains data, a reference to the next node, and a reference to the previous node.
 * This class should be put in a file named 'Node.java'.
 */

class Node<T> {

    /**
     * Here are the attributes we need to define for data and the number of elements.
     */
    T data;
    Node<T> next;
    Node<T> prev;


    /**
     * This is the constructor of the class.
     * It initializes a node with the given data.
     *
     * @param data Data to store in the node.
     */
    public Node(T data) {
        // Here we initialize the node with the given data
        this.data = data;
        this.next = null;
        this.prev = null;
    }

}


/**
 * This is a possible implementation of a stack using a doubly linked list.
 * The stack can grow dynamically as elements are added.
 * This class should be put in a file named 'Stack.java'.
 */
public class Stack<T> {

    /**
     * Here are the attributes we need to define for data and the number of elements.
     */
    private Node<T> top;
    private int nbElements;


    /**
     * This is the constructor of the class.
     * It initializes an empty stack using a doubly linked list.
     */
    public Stack() {
        // Here we initialize the stack with no elements
        this.top = null;
        this.nbElements = 0;
    }


    /**
     * This method returns a string representation of the stack.
     *
     * @return A string representation of the stack.
     */
    public String toString() {
        StringBuilder string = new StringBuilder("[");
        Node<T> current = top;
        while (current != null) {
            string.append(current.data).append(", ");
            current = current.prev;
        }
        if (nbElements > 0) {
            string.setLength(string.length() - 2);
        }
        string.append("]");
        return string.toString();
    }


    /**
     * This method adds an element to the stack.
     * The element is added at the top, and the counter is incremented.
     *
     * @param item Element to add.
     */
    public void push(T item) {
        // Create a new node with the item
        Node<T> newNode = new Node<>(item);

        // Link the new node with the current top node
        newNode.prev = top;

        // Set the new node as the top node
        if (top != null) {
            top.next = newNode;
        }
        top = newNode;

        // Increment the number of elements
        nbElements++;
    }


    /**
     * This method removes the last element of the stack.
     * The element is removed and the counter is decremented.
     *
     * @return The last element of the stack.
     */
    public T pop() {
        // Make sure the stack is not empty
        if (isEmpty()) {
            throw new IllegalStateException("Stack is empty");
        }

        // Remove the top element
        T element = top.data;
        top = top.prev;
        if (top != null) {
            top.next = null;
        }

        // Decrement the number of elements
        nbElements--;
        return element;
    }


    /**
     * This method checks if the stack is empty.
     *
     * @return True if the stack is empty, False otherwise.
     */
    public boolean isEmpty() {
        // Check if the stack is empty
        return nbElements == 0;
    }

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     *
     * @param args Command line arguments received.
     */
    public static void main(String[] args) {
        // A few tests
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        System.out.println(stack);
        stack.push(2);
        System.out.println(stack);
        stack.push(3);
        System.out.println(stack);
        int element = stack.pop();
        System.out.println(element + " " + stack);
        element = stack.pop();
        System.out.println(element + " " + stack);
        element = stack.pop();
        System.out.println(element + " " + stack);
    }
}

4 — A practical use

The following python program reverses a chain of characters. The algorithm stores character by character and then removes them until the stack is empty.

# Initialize
s = Stack()

# Fill stack
sentence = input("Enter a sentence:")
for c in sentence :
    s.push(c)

# Pop stack to reverse sentence
reversed_sentence = ""
while not s.is_empty():
    reversed_sentence += s.pop()
print("Reversed sentence:", reversed_sentence)
/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */

// Needed imports

import java.util.Scanner;

public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     *
     * @param args Command line arguments received.
     */
    public static void main(String[] args) {
        // Initialize the stack
        Stack<Character> s = new Stack<>();

        // Read input from user
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter a sentence: ");
        String sentence = scanner.nextLine();

        // Fill the stack with characters from the sentence
        for (char c : sentence.toCharArray()) {
            s.push(c);
        }

        // Pop stack to reverse sentence
        StringBuilder reversedSentence = new StringBuilder();
        while (!s.isEmpty()) {
            reversedSentence.append(s.pop());
        }

        // Print the reversed sentence
        System.out.println("Reversed sentence: " + reversedSentence.toString());
    }

}

Note that the program may use one implementation of the stack or the other, as the primitives hide the implementation details.

To go further

Important

The content of this section is optional. It contains additional material for you to consolidate your understanding of the current topic.

To go beyond

Important

The content of this section is very optional. We suggest you directions to explore if you wish to go deeper in the current topic.

  • name.

    Short description.