Practical activity

Duration1h15

Presentation & objectives

This activity is dedicated to the manipulation of data structures and to see empirically the impact of the choice of structure over another.

Important

We provide you the solutions to the exercises. Make sure to check them only after you have a solution to the exercises, for comparison purpose! Even if you are sure your solution is correct, please have a look at them, as they sometimes provide additional elements you may have missed.

Activity contents

1 — List-based implementation of a stack

Gather the code presented in the introductory course about stacks to obtain an implementation of a stack relying on the built-in list type. Here are the primitives that should be implemented in your file name list_stack.py.

  • initialise – Initialises 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.

Then, in a second file named test_stack.py performs some operations on a stack to check its primitives.

Correction
"""
This module provides an implementation of the primitives to handle a stack of values. 
The stack is materialized by means of a list with a size limit to simulate an array.

"""
from typing import List,Any

type Stack = List[Any]

def initialise() -> Stack:
    return []

def is_empty(stack: Stack) -> bool:
    """
    This function checks if the stack is empty.
    In:
        * stack: Stack to check.
    Out:
        * True if the stack is empty, False otherwise.
    """
    return len(stack) == 0

def push(stack: Stack, item: Any) -> None:
    """
    This function adds an element to the stack.
    The element is added at the end.
    In:
        * stack: Stack to modify.
        * item: Element to add.
    """
    stack.append(item)

def pop(stack: Stack) -> Any:
    """
    This function removes the last element of the stack.
    In:
        * stack: Stack to modify.
    Out:
        * The last element of the stack.
    """
    return stack.pop()
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class ListStack {
    private List<Object> stack;

    //The constructeur replace the initialise primitive
    public ListStack() {
        this.stack = new ArrayList<>();
    }

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

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

    public Object pop() {
        /**
         * This function removes the last element of the stack.
         * @return The last element of the stack.
         */
        if (this.stack.isEmpty()) {
            throw new IllegalStateException("Stack is empty");
        }
        return this.stack.remove(this.stack.size() - 1);
    }
}

2 — Calculate a postfix arithmetic expression

In this exercise we will use a stack to calculate an arithmetic expression in postfix notation, i.e., where each operator is preceded by its operands, such as 3 12 3 - 3 / 1 - *. The equivalent infix expression using parentheses is 3 * (((12 - 3) / 3) - 1).

In the file test_stack.py write a function evaluate_postfix that calculates the result of a postfix integer arithmetic expression. The function takes as input a string containing an arithmetic expression and return its final result.

  • The result of the calculation will be a real number.
  • The input is a sequence of characters.
  • Each piece of data is separated from the others by a space.
  • The method consists of using a stack to store the numerical values as they are read, and performing a processing operation when an operator is read.
  • This processing operation removes the last two values in the stack, performs the operation, and then pushes the result.
  • The final result is stored in the stack.
  • Allowed operators are +, -, *, /.

It is for the moment considered that the expression is correctly written and only integers are used.

Information

You will need to use different programming tools to implement this function.

  • to split a string according to a separator "3 2 * 25 +".split(" ") will split the string using a space as a delimiter.
  • to check if a string can be translated into an int "3".isnumeric() returns true whereas "afea".isnumeric() returns false.
  • to translate a string into an integer int("34") returns 34.

Modify the entry point of your test_stack.py file to ask for the input of an expressoin and to then display the result of its evaluation.

Correction
def evaluate_postfix (expression: str) -> float:
    """
        This function uses a stack to evaluate a postfix expression.
        The function takes a string as input, which is a postfix expression.
        The function returns the result of the expression.
        In:
            * expression: Expression to be evaluated.
        Out:
            * Evaluated of the evaluated expression.
    """

    # Create an empty stack
    stack : Stack = initialise()

    # Iterate over the expression
    split_expression = expression.split(" ")
    for i in split_expression:

        # If the character is a number, push it to the stack
        if i.isnumeric():
            push(stack, int(i))
        # If the character is an operator, apply it
        else:
            # Pop the last two elements from the stack
            a = pop(stack)
            b = pop(stack)

            # Apply the operator
            if i == '+':
                push(stack, b + a)
            elif i == '-':
                push(stack, b - a)
            elif i == '*':
                push(stack, b * a)
            elif i == '/':
                push(stack, b / a)
    
    # Return the result
    return pop(stack)
/**
 * 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.Stack;

public class Main {

    /**
     * This function uses a stack to evaluate a postfix expression.
     * The function takes a string as input, which is a postfix expression.
     * The function returns the result of the expression.
     *
     * @param expression Expression to be evaluated.
     * @return Evaluated result of the postfix expression.
     */
    public static double evaluatePostfix(String expression) {
        // Create an empty stack
        Stack<Double> stack = new Stack<>();

        // Iterate over the expression
        String[] splitExpression = expression.split(" ");
        for (String i : splitExpression) {

            // If the character is a number, push it to the stack
            if (i.matches("-?\\d+(\\.\\d+)?")) {
                stack.push(Double.parseDouble(i));
            }

            // If the character is an operator, apply it
            else {

                // Pop the last two elements from the stack
                double a = stack.pop();
                double b = stack.pop();

                // Apply the operator
                switch (i) {
                    case "+":
                        stack.push(b + a);
                        break;
                    case "-":
                        stack.push(b - a);
                        break;
                    case "*":
                        stack.push(b * a);
                        break;
                    case "/":
                        stack.push(b / a);
                        break;
                    default:
                        throw new IllegalArgumentException("Invalid operator: " + i);
                }
            }
        }

        // Return the result
        return stack.pop();
    }
}

3 — Compare different implementations of queues

Using built-in list

As you defined an implementation of a stack using the built-in list type (file list_stack.py), create a file name list_queue.py that implements the primitives of a queue using a list. The code is already available and explained in the following course, you just have to regroup all the functions and to test them, for example by enqueueing a few elements and dequeueing them.

Recall of the primitives:

  • initialise – Initializes the queue.
  • enqueue – Adds an element to the end of the queue.
  • dequeue – Removes the first element from the queue.
  • is_empty ** Returns true if the queue is empty, and false otherwise.
Correction
"""
This module provides an implementation of the primitives to handle a queue of values. 
The queue is materialized by means of a list with a size limit to simulate an array.

"""
from typing import List,Any

type Queue = List[Any]

def initialise() -> Queue:
    return []

def is_empty(queue: Queue) -> bool:
    """
    This function checks if the queue is empty.
    In:
        * queue: Queue to check.
    Out:
        * True if the queue is empty, False otherwise.
    """
    return len(queue) == 0

def push(queue: Queue, item: Any) -> None:
    """
    This function adds an element to the queue.
    The element is added at the end.
    In:
        * queue: Queue to modify.
        * item: Element to add.
    """
    queue.insert(0,item)

def pop(queue: Queue) -> Any:
    """
    This function removes the last element of the queue.
    In:
        * queue: Queue to modify.
    Out:
        * The last element of the queue.
    """
    return queue.pop()

def display(queue: Queue) -> None:
    """
    This function displays the content of the queue.
    In:
        * queue: Queue to display.
    """
    print(queue)
import java.util.ArrayList;
import java.util.List;

public class ListQueue {
    private List<Object> queue;

    public ListQueue() {
        this.queue = new ArrayList<>();
    }

    public boolean isEmpty() {
        /**
         * This function checks if the queue is empty.
         * @return True if the queue is empty, False otherwise.
         */
        return this.queue.isEmpty();
    }

    public void push(Object item) {
        /**
         * This function adds an element to the queue.
         * The element is added at the end.
         * @param item Element to add.
         */
        this.queue.add(item);
    }

    public Object pop() {
        /**
         * This function removes the last element of the queue.
         * @return The removed element.
         */
        if (this.queue.isEmpty()) {
            throw new IllegalStateException("Queue is empty");
        }
        return this.queue.remove(0);
    }
}

Using doubly-linked list

Doubly-linked list is a data structure giving an access to the first and last element of a list. You shall find below a home-made implementations of the primitives to manage a doubly-linked list. This implementation is not optimised and just used here for pedagogical purpose.

"""
This module provides functions to manipulate a doubly-linked list.
The list is represented as a dictionary with 'head' and 'tail' keys pointing to the first and last Elements, respectively.
Each Element is also represented as a dictionary with 'data', 'prev', and 'next' keys.
"""

from typing import Any, Dict, Optional

# Define the Element structure
Element = Dict[str, Any]

def create_Element(data: Any) -> Element:
    """
    Create a new Element with the given data.

    Args:
        data (Any): The data to store in the Element.

    Returns:
        Element: A dictionary representing the new Element.
    """
    return {'data': data, 'prev': None, 'next': None}

def initialise_list() -> Dict[str, Optional[Element]]:
    """
    Initialise an empty doubly-linked list.

    Returns:
        Dict[str, Optional[Element]]: A dictionary representing the doubly-linked list with 'head' and 'tail' keys.
    """
    return {'head': None, 'tail': None}

def insert_at_beginning(dll: Dict[str, Optional[Element]], data: Any) -> None:
    """
    Insert a new Element with the given data at the beginning of the list.

    Args:
        dll (Dict[str, Optional[Element]]): The doubly-linked list.
        data (Any): The data to store in the new Element.
    """
    new_Element = create_Element(data)
    if dll['head'] is None:
        dll['head'] = dll['tail'] = new_Element
    else:
        new_Element['next'] = dll['head']
        dll['head']['prev'] = new_Element
        dll['head'] = new_Element

def is_empty(dll: Dict[str, Optional[Element]]) -> bool:
    """
    Check if the doubly-linked list is empty.

    Args:
        dll (Dict[str, Optional[Element]]): The doubly-linked list.

    Returns:
        bool: True if the list is empty, False otherwise.
    """
    return dll['head'] is None

def insert_at_end(dll: Dict[str, Optional[Element]], data: Any) -> None:
    """
    Insert a new Element with the given data at the end of the list.

    Args:
        dll (Dict[str, Optional[Element]]): The doubly-linked list.
        data (Any): The data to store in the new Element.
    """
    new_Element = create_Element(data)
    if dll['tail'] is None:
        dll['head'] = dll['tail'] = new_Element
    else:
        new_Element['prev'] = dll['tail']
        dll['tail']['next'] = new_Element
        dll['tail'] = new_Element

def pop_from_beginning(dll: Dict[str, Optional[Element]]) -> Any:
    """
    Remove and return the data of the Element at the beginning of the list.

    Args:
        dll (Dict[str, Optional[Element]]): The doubly-linked list.

    Returns:
        Any: The data of the Element removed from the beginning of the list.
    """
    if dll['head'] is None:
        return None
    data = dll['head']['data']
    if dll['head'] == dll['tail']:
        dll['head'] = dll['tail'] = None
    else:
        dll['head'] = dll['head']['next']
        dll['head']['prev'] = None
    return data

def pop_from_end(dll: Dict[str, Optional[Element]]) -> Any:
    """
    Remove and return the data of the Element at the end of the list.

    Args:
        dll (Dict[str, Optional[Element]]): The doubly-linked list.

    Returns:
        Any: The data of the Element removed from the end of the list.
    """
    if dll['tail'] is None:
        return None
    data = dll['tail']['data']
    if dll['head'] == dll['tail']:
        dll['head'] = dll['tail'] = None
    else:
        dll['tail'] = dll['tail']['prev']
        dll['tail']['next'] = None
    return data


def delete_Element(dll: Dict[str, Optional[Element]], data: Any) -> None:
    """
    Delete the first Element with the given data from the list.

    Args:
        dll (Dict[str, Optional[Element]]): The doubly-linked list.
        data (Any): The data of the Element to delete.
    """
    current = dll['head']
    while current is not None:
        if current['data'] == data:
            if current['prev'] is not None:
                current['prev']['next'] = current['next']
            else:
                dll['head'] = current['next']
            if current['next'] is not None:
                current['next']['prev'] = current['prev']
            else:
                dll['tail'] = current['prev']
            return
        current = current['next']
/**
 * This class provides methods to manipulate a doubly-linked list.
 * The list is represented with a head and tail pointing to the first and last nodes, respectively.
 * Each node is represented with data, prev, and next fields.
 */

import java.util.Optional;

 class DoublyLinkedList {
     // Define the Node structure
     static class Node {
         int data;
         Node prev;
         Node next;
 
         Node(int data) {
             this.data = data;
             this.prev = null;
             this.next = null;
         }
     }
 
     private Node head;
     private Node tail;
 
     // Initialize the list
     public DoublyLinkedList() {
         this.head = null;
         this.tail = null;
     }

    
    /**
     * Check if the list is empty.
     *
     * @return true if the list is empty, false otherwise.
     */
    public boolean isEmpty() {
        return head == null;
    }
    
     /**
      * Insert a new node with the given data at the beginning of the list.
      *
      * @param data The data to store in the new node.
      */
     public void insertAtBeginning(int data) {
         Node newNode = new Node(data);
         if (head == null) {
             head = tail = newNode;
         } else {
             newNode.next = head;
             head.prev = newNode;
             head = newNode;
         }
     }
 
     /**
      * Insert a new node with the given data at the end of the list.
      *
      * @param data The data to store in the new node.
      */
     public void insertAtEnd(int data) {
         Node newNode = new Node(data);
         if (tail == null) {
             head = tail = newNode;
         } else {
             newNode.prev = tail;
             tail.next = newNode;
             tail = newNode;
         }
     }
 
     /**
      * Delete the first node with the given data from the list.
      *
      * @param data The data of the node to delete.
      */
     public void deleteNode(int data) {
         Node current = head;
         while (current != null) {
             if (current.data == data) {
                 if (current.prev != null) {
                     current.prev.next = current.next;
                 } else {
                     head = current.next;
                 }
                 if (current.next != null) {
                     current.next.prev = current.prev;
                 } else {
                     tail = current.prev;
                 }
                 return;
             }
             current = current.next;
         }
     }

      /**
     * Remove and return the data from the node at the head of the list.
     *
     * @return The data from the node at the head of the list, or -1 if the list is empty.
     */
    public int popFromHead() {
        if (head == null) {
            throw new RuntimeException("Empty list");//notion of Exception seen in prog session 
        }
        int data = head.data;
        head = head.next;
        if (head != null) {
            head.prev = null;
        } else {
            tail = null;
        }
        return data;
    }

    /**
     * Remove and return the data from the node at the tail of the list.
     *
     * @return The data from the node at the tail of the list, or -1 if the list is empty.
     */
    public int popFromTail() {
        if (tail == null) {
            throw new RuntimeException("Empty list");
        }
        int data = tail.data;
        tail = tail.prev;
        if (tail != null) {
            tail.next = null;
        } else {
            head = null;
        }
        return data;
    }
 
     /**
      * Search for a node with the given data.
      *
      * @param data The data to search for.
      * @return An Optional containing the found node, or an empty Optional if no node was found.
      */
     public Optional<Node> searchNode(int data) {
         Node current = head;
         while (current != null) {
             if (current.data == data) {
                 return Optional.of(current);
             }
             current = current.next;
         }
         return Optional.empty();
     }
 
     /**
      * Display the data of all nodes in the list.
      */
     public void displayList() {
         Node current = head;
         while (current != null) {
             System.out.print(current.data + " ");
             current = current.next;
         }
         System.out.println();
     }
 }

As you defined an implementation of a queue using the built-in list type (file list_queue.py), create a file name ddl_queue.py that implements the primitives of a queue using a doubly-linked list. Then test your implementation for example by enqueueing a few elements and dequeueing them.

Comparing implementations

At this point of the practical activity, you should have available two possible implementations of a queue:

  • using the built-in list type (in list_queue.py),
  • using the home-made, thus perfectible, doubly-linked list type (in ddl_queue.py).

The idea now is to see the impact of chosing an implementation over another. In addition to the two previously mentioned implementation, a third implementation using the deque python time will be used. The deque type is similar to the doubly-linked list but optimized compared to our home-made version.

The following code aims at comparing three implementations of a queue focusing on the queueing primitive. 10 000 values are added to each implementation of the queue and the average time needed to perform this queueing operation is calculated and displayed. Copy the following code in a file named test_perf_queueing.py and to run this code, check that:

  • the files named list_queue.py and ddl_queue.py are located in the same directory as test_perf_queueing.py
  • the primitives initialise() and push(queue, element) are available.
"""
compares the time needed to add elements to the front of a list and a deque
"""
from collections import deque
from time import perf_counter
from typing import List, Deque, Callable, Any

import list_queue as lq
import dll_queue as dllq

NBINSERT : int = 10_000
qlq : lq.Queue = lq.initialise() #Queue with list
qdll : dllq.Queue = dllq.initialise()#Queue with doubly linked list
a_deque : Deque[int] = deque()#Queue with deque

def average_time(func : Callable[[Any], None], times : int) -> float:
    total : float = 0.0
    for i in range(times):
        start = perf_counter()
        func(i)
        total += (perf_counter() - start) * 1e9
    return total / times

list_time = average_time(lambda i: lq.push(qlq, i), NBINSERT)
dll_time = average_time(lambda i: dllq.push(qdll, i), NBINSERT)
deque_time = average_time(lambda i: a_deque.appendleft(i), NBINSERT)
gain = list_time / deque_time
gain2 = list_time / dll_time
gain3 = dll_time / deque_time

print(f"Average time list queue      {list_time:.6} ns")
print(f"Average time doubly_linked list queue      {dll_time:.6} ns")
print(f"Average time deque      {deque_time:.6} ns")
print(f"deque over list ({gain:.6}x faster)")
print(f"doubly-linked list over list ({gain2:.6}x faster)")
print(f"deque over doubly-linked list ({gain3:.6}x faster)")

Here are the kind of results you should obtain:

Average time list queue      2455.53 ns
Average time doubly_linked list queue      229.59 ns
Average time deque      53.9438 ns
deque over list (45.5202x faster)
doubly-linked list over list (10.6953x faster)
deque over doubly-linked list (4.2561x faster)

Two main conclusions to draw from this first experiment. The type of data structure used to implement a queue may strongly affects the efficiency of some primitives. Here, inserting an element at the first position of a list, even using the optimised built-in list type, is costly as it requires to physically shift, in memory, all the elements in the list. So an queue operation leads to N shift operations when the list contains N elements. This explains the significative difference in terms of efficiency compared to the doubly-linked list version. Comparing the home-made version of doubly-linked list and the deque shows you should always favor official implementation when they are available.

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.

Here are some additional exercices for those of you who would like to go further in the use of data structures.

4 — Removing elements in a doubly-linked list

Complete the home-made implementation of the doubly-linked list type with a function that removes the first element of the list having a given value (passed as a parameter of the function).

In some application cases, Where the stored values are ordinal, it may be interesting (to possibly speed up value retrieval) to maintain an order of the values within the list. Given that integer values are stored in the list, suggest an implementation of an insert_element function that keeps the values in descending (or ascending) order.

5 — Convert infix expression to postfix expression

Write a function infix_to_prefix that converts an infix expression with integers (in brackets) to postfix notation. You will need to use both a stack and a queue.

For instance, the expression 3*(((12-3)/3)-1) will be converted to: 3 12 3 - 3 / 1 - *

  • The valid operators are +, -, * and /.
  • The algorithm reads an arithmetic infix expression and stores the result of the conversion in a queue, which is displayed.
  • The algorithm will use a stack to manage the operators, and a queue to construct the postfix expression.
  • The numeric values must be directly stored in the queue, while the operators must wait before adding them to the queue after their operands.
  • The treatment of an operator, poping then enqueuing, must occur when a closing parenthesis is encountered.
  • Also, beware of operators priority!
Correction
# Needed imports
import re


def infix_to_postfix (infix_expression: str) -> str:

    """
        This function uses a stack and a queue to transform an infix expression to a postfix expression.
        All expressions are supposed to be well parenthesized.
        In:
            * infix_expression: Expression to be transformed to postfix.
        Out:
            * Postfix expression equivalent to the input infix expression.
    """

    # Define the precedence of the operators
    precedence = {"*": 2, "/": 2, "+": 1, "-": 1, "(": 0, ")": 0}

    # Initialize the stack and the queue
    stack = []
    queue = []

    # Split the infix expression into tokens
    tokens = [t for t in re.split(r"(\+|\-|\*|\/|\(|\))", infix_expression) if t.strip() != ""]

    # Iterate over tokens
    for token in tokens:
        
        # If the token is an operand, add it to the queue
        if token.isnumeric():
            queue.append(token)

        # If the token is a left parenthesis, add it to the stack
        elif token == "(":
            stack.append(token)

        # If the token is a right parenthesis, pop all the operators from the stack and add them to the queue until a left parenthesis is found
        elif token == ")":
            while stack and stack[-1] != "(":
                queue.append(stack.pop())
            stack.pop()

        # If the token is an operator, pop all the operators from the stack with higher precedence and add them to the queue
        # Then, add the operator to the stack
        else:
            while stack and precedence[stack[-1]] >= precedence[token]:
                queue.append(stack.pop())
            stack.append(token)

    # Add the remaining operators to the queue
    while stack:
        queue.append(stack.pop())

    # Return the postfix expression
    return " ".join(queue)


# Test the function
print("3*(((12-3)/3)-1) transformed into", infix_to_postfix("3*(((12-3)/3)-1)"))
print("1+(2*3) transformed into", infix_to_postfix("1+(2*3)"))
print("(1+2)*3 transformed into", infix_to_postfix("(1+2)*3"))
print("3*(2+1) transformed into", infix_to_postfix("3*(2+1)"))
/**
 * 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.Stack;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    /**
     * This function uses a stack and a queue to transform an infix expression to a postfix expression.
     * All expressions are supposed to be well parenthesized.
     *
     * @param infixExpression Expression to be transformed to postfix.
     * @return Postfix expression equivalent to the input infix expression.
     */
    public static String infixToPostfix(String infixExpression) {
        // Define the precedence of the operators
        var precedence = new java.util.HashMap<String, Integer>();
        precedence.put("*", 2);
        precedence.put("/", 2);
        precedence.put("+", 1);
        precedence.put("-", 1);
        precedence.put("(", 0);
        precedence.put(")", 0);

        // Initialize the stack and the queue
        Stack<String> stack = new Stack<>();
        List<String> queue = new ArrayList<>();

        // Split the infix expression into tokens
        Pattern pattern = Pattern.compile("(\\+|\\-|\\*|\\/|\\(|\\))");
        Matcher matcher = pattern.matcher(infixExpression);
        List<String> tokens = new ArrayList<>();
        int lastIndex = 0;
        while (matcher.find()) {
            if (matcher.start() > lastIndex) {
                tokens.add(infixExpression.substring(lastIndex, matcher.start()).trim());
            }
            tokens.add(matcher.group());
            lastIndex = matcher.end();
        }
        if (lastIndex < infixExpression.length()) {
            tokens.add(infixExpression.substring(lastIndex).trim());
        }

        // Iterate over tokens
        for (String token : tokens) {
            // If the token is an operand, add it to the queue
            if (token.matches("\\d+")) {
                queue.add(token);
            }

            // If the token is a left parenthesis, add it to the stack
            else if (token.equals("(")) {
                stack.push(token);
            }

            // If the token is a right parenthesis, pop all the operators from the stack and add them to the queue until a left parenthesis is found
            else if (token.equals(")")) {
                while (!stack.isEmpty() && !stack.peek().equals("(")) {
                    queue.add(stack.pop());
                }
                stack.pop();
            }

            // If the token is an operator, pop all the operators from the stack with higher precedence and add them to the queue
            // Then, add the operator to the stack
            else {
                while (!stack.isEmpty() && precedence.get(stack.peek()) >= precedence.get(token)) {
                    queue.add(stack.pop());
                }
                stack.push(token);
            }
        }

        // Add the remaining operators to the queue
        while (!stack.isEmpty()) {
            queue.add(stack.pop());
        }

        // Return the postfix expression
        return String.join(" ", queue);
    }


    /**
     * 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) {
        // Test the function
        System.out.println("3*(((12-3)/3)-1) transformed into " + infixToPostfix("3*(((12-3)/3)-1)"));
        System.out.println("1+(2*3) transformed into " + infixToPostfix("1+(2*3)"));
        System.out.println("(1+2)*3 transformed into " + infixToPostfix("(1+2)*3"));
        System.out.println("3*(2+1) transformed into " + infixToPostfix("3*(2+1)"));
    }

}

4 — Compute the Graham convex hull

The Graham scan algorithm is a computational geometry method used to find the convex hull of a set of points in a 2D plane, with complexity O(n log n). The convex hull is the smallest convex polygon that can enclose all the given points. It uses a stack to detect and remove concavities in the boundary efficiently.

Write the graham algorithm which takes:

  • as input: a set of n points in the plane, each represented by its coordinates (abscissa and ordinate).
  • as output: a list of points that are the vertices of the polygon surrounding the convex envelope, in the order in which they appear on this polygon.

You must follow the steps of the Graham algorithm:

  1. Find the point with the lowest y-coordinate. If there are multiple points with the same lowest y-coordinate, choose the one with the lowest x-coordinate. This point is called the pivot point.
  2. Sort the set of points in increasing order of the angle they and the pivot point make with the x-axis. Any general-purpose sorting algorithm is appropriate for this.
  3. Iterate through the sorted list of points to construct the convex hull: We start with the pivot. Then the sorted points are examined. For each point, we first determine whether travelling from the two points immediately preceding this point is a left turn (counter-clockwise orientation) or a right turn (clockwise orientation).
  • In the case of a right turn, this means that the point that ended the polygon being created (the point in red) is to the left of the segment formed by the penultimate point of the polygon being created and the point under consideration (the point in black). In this case, the last point (the one in red) is not part of the convex envelope and must therefore be rejected. These rejections are repeated as long as there is a ‘right turn’.
  • If the algorithm encounters a ‘left turn’, it moves on to the next point in the sorted array.

Graham algorithm right turn Graham algorithm right turn Determining whether three points constitute a “left turn” or a “right turn” does not require computing the actual angle between the two line segments, and can be achieved with simple arithmetic only. For three points (x1, y1), (x2, y2) and (x3, y3), simply calculate the direction of the vector product of the two vectors defined by the points (x1, y1), (x2, y2) and (x1, y1), (x3, y3), given by the sign of the expression (x2 - x1)(y3 - y1) - (y2 - y1)(x3 - x1):

  • If the result is zero, the points are aligned.
  • If it is positive, the three points form a ‘left turn’ or counter-clockwise orientation.
  • If the result is negative, it is a ‘right turn’ or clockwise orientation.
Correction
import math

def euclidean_distance(p1:tuple[int,int], p2:tuple[int,int])-> float:
    """
       This function calculates the Euclidean distance between two points
       In : two points, each defined by two coordinates
       Out : the Euclidean distance
    """
    return (p2[0] - p1[0])**2 + (p2[1] - p1[1])**2

def right_turn(p:tuple[int,int],q:tuple[int,int],r:tuple[int,int])-> bool:
    """
       This function calculates the direction of the vector product of the two vectors defined by the points p, q and p, r
       In : three points p, q and r, each defined by two coordinates
       Out : true if the vector product is negative, false otherwise
    """

    direction=(q[0]- p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0])
    if direction == 0: # the points are aligned.
        return False
    elif direction > 0: # the three points form a left turn or counter-clockwise orientation
        return False
    else: # it is a right turn or clockwise orientation
        return True
  
def graham_scan (points:list[tuple[int,int]]) -> list[tuple[int,int]]:
    
    """
        This function implements the Graham algorithm to compute the convex hull of point in a 2D plane.  
        In:
            * points: list of points in the plane, each represented by its coordinates (abscissa, ordinate) - a tuple with two integer elements.
        Out:
            * list of points forming the convex envelope.
    """

    # Find the point with the lowest y-coordinate, if there are multiple points with the same y-coordinate, pick the one with the smallest x-coordinate
    min_y = min(points, key=lambda p:(p[1],p[0]))
   

    # Sort the set of points in increasing order of the angle they and the pivot point make with the x-axis.
    points = sorted(points, key=lambda p: (math.atan2(p[1] - min_y[1], p[0] - min_y[0]), euclidean_distance(min_y,p)))
    hull=[]
    for point in points:
        while len(hull)>1 and right_turn(hull[-2],hull[-1],point):
            hull.pop() # remove the point from the stack
        hull.append(point) # add the point to the stack and move to the next one

    return hull

# example
points = [(0,0), (1,1), (1.5,2.5), (2,2), (3,0), (3.5,2), (0,3), (3,3),(2,3.5)]
hull = graham_scan(points)
print("Convex hull:", hull)
 import java.util.*;

public class ConvexHull {
    /**
     * This function calculates the Euclidean distance between two points.
     * @param p1 point 1 defined by its two coordinates - array of 2 integers
     * @param  p2 point 2 defined by its two coordinates - array of 2 integers
     * @return the Euclidean distance.
     */

    public static double euclideanDistance(int[] p1, int[] p2) {
        return Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2);
    }

    /**
     * This function calculates the direction of the vector product of the two vectors defined by the points p, q and p, r
     * @param p point 1 defined by its two coordinates - array of 2 integers
     * @param q point 2 defined by its two coordinates - array of 2 integers
     * @param r point 3 defined by its two coordinates - array of 2 integers
     * @return  true if the vector product is negative, false otherwise
     */

    public static boolean rightTurn(int[] p, int[] q, int[] r) {
        int direction = (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0]);
        return direction < 0;
    }

    /**
     * This function implements the Graham algorithm to compute the convex hull of point in a 2D plane.  
     * @param points list of points in the plane, each represented by its coordinates (abscissa, ordinate) - a tuple with two integer elements.
     * @return list of integer arrays representing the points forming the convex envelope.
     */

    public static List<int[]> grahamScan(List<int[]> points) {
        // Find the point with the lowest y-coordinate, if there are multiple points with the same y-coordinate, pick the one with the smallest x-coordinate
        int[] minY = Collections.min(points, Comparator.comparingInt((int[] p) -> p[1]).thenComparingInt(p -> p[0]));

        // Sort the set of points in increasing order of the angle they and the pivot point make with the x-axis.
        points.sort((p1, p2) -> {
            double angle1 = Math.atan2(p1[1] - minY[1], p1[0] - minY[0]);
            double angle2 = Math.atan2(p2[1] - minY[1], p2[0] - minY[0]);
            if (angle1 != angle2) {
                return Double.compare(angle1, angle2);
            } else {
                return Double.compare(euclideanDistance(minY, p1), euclideanDistance(minY, p2));
            }
        });

        // Create the convexe hull
        Stack<int[]> hull = new Stack<>();
        for (int[] point : points) {
            while (hull.size() > 1 && rightTurn(hull.get(hull.size() - 2), hull.peek(), point)) {
                hull.pop(); // remove the point from the stack
            }
            hull.push(point); // add the point to the stack and move to the next one
        }

        return new ArrayList<>(hull);
    }
    public static void main(String[] args) {
        // Example 
        List<int[]> points = Arrays.asList(
            new int[]{0, 0}, new int[]{1, 1}, new int[]{1, 2}, new int[]{2, 2},
            new int[]{3, 0}, new int[]{3, 2}, new int[]{0, 3}, new int[]{3, 3},
            new int[]{2, 4}
        );

        List<int[]> hull = grahamScan(points);
        System.out.println("Convex hull:");
        for (int[] point : hull) {
            System.out.println(Arrays.toString(point));
        }
    }
}

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.