{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "markdown",
     "checksum": "476b1fe594021a18d45b1e96ed4650f0",
     "grade": false,
     "grade_id": "jupyter",
     "locked": true,
     "schema_version": 3,
     "solution": false
    }
   },
   "source": [
    "# ENG 209 Midterm - Problèmes ouverts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Callable, Any\n",
    "from dataclasses import dataclass, field\n",
    "from datetime import datetime\n",
    "from pprint import pprint\n",
    "from collections import defaultdict\n",
    "try:\n",
    "    import numpy as np\n",
    "except:\n",
    "    print(\"Run 'pip install numpy' in a terminal\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"font-size: 120%\">\n",
    "\n",
    "___Les problèmes ci-dessous sont à résoudre individuellement.  \n",
    "Vous n’avez pas accès à Copilot, ChatGPT ou à tout autre outil de génération de code.  \n",
    "Vous pouvez vous référer à votre résumé/formulaire.  \n",
    "La consultation des séries précédentes est interdite.___\n",
    "\n",
    "* N’écrivez du code **que** dans les cellules avec `### YOUR CODE HERE`\n",
    "* **Supprimez** les lignes `raise NotImplementedError()`\n",
    "* **N’ajoutez pas** de cellules et ne détruisez pas les cellules vides.\n",
    "</div>\n",
    "\n",
    "<hr>\n",
    "\n",
    "<div style=\"font-size: 120%\">\n",
    "\n",
    "___The problems below must be solved individually.  \n",
    "You do not have access to Copilot, ChatGPT, or any other code generation tool.  \n",
    "You may refer to your summary/form sheet.  \n",
    "Consulting previous exercises is not allowed.___\n",
    "\n",
    "* Write code **only** in the cells marked with `### YOUR CODE HERE`\n",
    "* **Delete** the lines `raise NotImplementedError()`\n",
    "* **Do not add** or remove any cells, including empty ones.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Problème 1\n",
    "\n",
    "<table width=\"100%;\">\n",
    "<tr>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; border-right: 1px solid lightgrey; padding-right: 10px; align: center;\">\n",
    "\n",
    "Implémentez la fonction `reduce` ci-dessous. Elle accepte une liste de `int`s et une fonction `f` qui prend deux `int`s et retourne un `int`, et un élément initial `initial`. La valeur qu'elle retourne est le résultat de l'application répétée de `f` aux éléments de la liste, en partant de la gauche, et en considérant `initial` comme valeur de départ.\n",
    "\n",
    "Par exemple, si `values = [1, 2]`, `f = lambda x, y: x + y` (simplement, l'addition de deux `int`s) et `initial = 0`, alors `reduce(values, f, initial)` doit retourner `3`, soit la somme des éléments de la liste, car le calcul est effectué comme suit: `f(f(initial, 1), 2) = f(f(0, 1), 2) = ((0 + 1) + 2) = 3`.\n",
    "\n",
    "</td>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; align: center\">\n",
    "\n",
    "Implement the `reduce` function below. It takes a list of `int`s, a function `f` that takes two `int`s and returns an `int`, and an initial element `initial`. The value it returns is the result of repeatedly applying `f` to the elements of the list from left to right, starting with `initial` as the initial value.\n",
    "\n",
    "For example, if `values = [1, 2]`, `f = lambda x, y: x + y` (simply the addition of two `int`s), and `initial = 0`, then `reduce(values, f, initial)` should return `3`, i.e. the sum of the list elements, since the computation proceeds as follows: `f(f(initial, 1), 2) = f(f(0, 1), 2) = ((0 + 1) + 2) = 3`.\n",
    "\n",
    "</td>\n",
    "</tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "e018ba637c4e981936b1660178e7d874",
     "grade": false,
     "grade_id": "reduce",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "def reduce(values: list[int], f: Callable[[int, int], int], initial: int) -> int:\n",
    "    # YOUR CODE HERE\n",
    "    raise NotImplementedError()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# devrait afficher/should display 10\n",
    "reduce([1, 2, 3, 4], lambda x, y: x + y, 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# devrait afficher/should display 24\n",
    "reduce([1, 2, 3, 4], lambda x, y: x * y, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "dacd1c5cee80fc61360cb0a8c38d498a",
     "grade": true,
     "grade_id": "reduce_tests_1",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify\n",
    "assert reduce([1, 2, 3, 4], lambda x, y: x + y, 0) == 10, \"addition does not work\"\n",
    "assert reduce([1, 2, 3, 4], lambda x, y: x * y, 1) == 24, \"multiplication does not work\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "05457de184bc1f193ea0961e2eec95f3",
     "grade": true,
     "grade_id": "reduce_tests_2",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Problème 2\n",
    "\n",
    "<table width=\"100%;\">\n",
    "<tr>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; border-right: 1px solid lightgrey; padding-right: 10px; align: center;\">\n",
    "\n",
    "Complétez la fonction `to_dict` ci-dessous, **sans modifier** la classe `Record`. N'**utilisez pas `defaultdict`**.\n",
    "\n",
    "La fonction accepte une liste de tuples `(str, int)` représentant des heures travaillées par des employés identifiés par une chaîne de caractères (leur nom).\n",
    "\n",
    "La fonction doit retourner un dictionnaire où chaque clé est le nom d'un employé et la valeur associée est une instance de la classe `Record`, qui indique le nom de l'employé, la liste de ses heures travaillées et le total des heures.\n",
    "\n",
    "Par exemple, si `pairs = [('a', 1), ('b', 2), ('a', 3), ('b', 2)]`, le dictionnaire retourné devra être:\n",
    "\n",
    "````\n",
    "{'a': Record(name='a', values=[1, 3], total=4),\n",
    " 'b': Record(name='b', values=[2, 2], total=4)}\n",
    "````\n",
    "\n",
    "</td>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; align: center\">\n",
    "\n",
    "Complete the `to_dict` function below **without modifying** the `Record` class. Do **not use `defaultdict`**.\n",
    "\n",
    "The function takes a list of tuples `(str, int)` representing hours worked by employees identified by a string (their name).\n",
    "\n",
    "It must return a dictionary where each key is an employee’s name and the associated value is an instance of the `Record` class, containing the employee’s name, the list of their worked hours, and the total number of hours.\n",
    "\n",
    "For example, if `pairs = [('a', 1), ('b', 2), ('a', 3), ('b', 2)]`,  \n",
    "the returned dictionary should be:\n",
    "\n",
    "````\n",
    "{'a': Record(name='a', values=[1, 3], total=4),\n",
    " 'b': Record(name='b', values=[2, 2], total=4)}\n",
    "````\n",
    "\n",
    "</td>\n",
    "</tr>\n",
    "</table>\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "2efb6648bf8c1ff39d9ddf808371304b",
     "grade": false,
     "grade_id": "to_dict",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class Record:\n",
    "    name: str\n",
    "    values: list[int]\n",
    "    total: int\n",
    "\n",
    "\n",
    "def to_dict(pairs: list[tuple[str, int]]) -> dict[str, Record]:\n",
    "    # YOUR CODE HERE\n",
    "    raise NotImplementedError()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# devrait afficher/should display:\n",
    "# {'a': Record(name='a', values=[1, 3], total=4),\n",
    "#  'b': Record(name='b', values=[2, 2], total=4)}\n",
    "to_dict([('a', 1), ('b', 2), ('a', 3), ('b', 2)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "468acb8792ed1c1504045e06fd770e91",
     "grade": true,
     "grade_id": "to_dict_tests_1",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify\n",
    "assert to_dict([(\"a\", 1), (\"b\", 2), (\"a\", 3)]) == {'a': Record(name='a', values=[1, 3], total=4), 'b': Record(name='b', values=[2], total=2)}, \"to_dict does not work\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "ce2aff222add31cc5a7c677801b0fdc8",
     "grade": true,
     "grade_id": "to_dict_tests_2",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Problème 3\n",
    "\n",
    "<table width=\"100%\">\n",
    "<tr>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; border-right: 1px solid lightgrey; padding-right: 10px; align: center;\">\n",
    "\n",
    "Complétez la classe `Contact` ci-dessous en suivant les instructions suivantes.\n",
    "\n",
    " 1. Donnez-lui un champ en plus appelé `friends`, qui est une liste de `Contact`.  \n",
    "    Pour le type, utilisez `list[\"Contact\"]` (notez les guillemets) plutôt que `list[Contact]` pour éviter les problèmes de dépendances circulaires.  \n",
    "    Ce champ doit être initialisé par défaut à une nouvelle liste vide pour chaque nouvelle instance créée.\n",
    " 2. Ajoutez une propriété `full_name` qui retourne le nom complet du contact.  \n",
    "    On considère que le `first_name` et le `last_name` sont toujours définis, mais que `middle_name` peut être vide (`\"\"`).  \n",
    "    Le `full_name` doit être la concaténation des trois noms, séparés par un espace, mais sans espaces superflus. Par exemple, si `first_name=\"John\"`, `middle_name=\"\"` et `last_name=\"Doe\"`, alors `full_name` doit retourner `\"John Doe\"`.\n",
    "\n",
    "    Faites en sorte que le `full_name` soit calculé à la demande et non stocké dans un champ et s'obtienne avec la syntaxe `p.full_name` (sans parenthèses), et non `p.full_name()`, si `p` est une instance de `Contact`.\n",
    " 3. Ajoutez une méthode `filtered_friends` qui accepte un prédicat (une fonction qui prend un `Contact` et retourne un booléen, de type `Callable[['Contact'], bool]`) et retourne une liste des amis qui satisfont ce prédicat.  \n",
    "    Implémentez cette méthode **en une seule ligne**.\n",
    "\n",
    "</td>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; align: center\">\n",
    "\n",
    "Complete the `Contact` class below according to the following instructions.\n",
    "\n",
    "1. Add an extra field called `friends`, which is a list of `Contact`.  \n",
    "   For the type annotation, use `list[\"Contact\"]` (note the quotes) instead of `list[Contact]` to avoid circular dependency issues.  \n",
    "   This field must be initialized by default to a new empty list for each new instance created.\n",
    "\n",
    "2. Add a property `full_name` that returns the contact’s full name.  \n",
    "   The `first_name` and `last_name` are always defined, but `middle_name` may be empty (`\"\"`).  \n",
    "   The `full_name` should be the concatenation of the three names, separated by spaces, but without extra spaces.  \n",
    "   For example, if `first_name=\"John\"`, `middle_name=\"\"`, and `last_name=\"Doe\"`, then `full_name` should return `\"John Doe\"`.\n",
    "\n",
    "   The `full_name` must be computed on demand (not stored in a field) and accessed using the syntax `p.full_name` (without parentheses), not `p.full_name()`, if `p` is an instance of `Contact`.\n",
    "\n",
    "3. Add a method `filtered_friends` that takes a predicate (a function that accepts a `Contact` and returns a boolean, of type `Callable[['Contact'], bool]`) and returns a list of friends that satisfy this predicate.  \n",
    "   Implement this method **in a single line**.\n",
    "<td>\n",
    "</tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "17c627e9dc0ad8cb6e09dd2b85e0f833",
     "grade": false,
     "grade_id": "contact",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class Contact:\n",
    "    first_name: str\n",
    "    middle_name: str\n",
    "    last_name: str\n",
    "    # YOUR CODE HERE\n",
    "    raise NotImplementedError()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p1 = Contact(\"John\", \"B.\", \"Robertson\")\n",
    "p2 = Contact(\"Joyce\", \"Jenna\", \"Brown\")\n",
    "p3 = Contact(\"James\", \"\", \"Kerringan\")\n",
    "\n",
    "p1.friends.append(p2)\n",
    "p1.friends.append(p3)\n",
    "\n",
    "print(p1.full_name)  # John B. Robertson\n",
    "print(p2.full_name)  # Joyce Jenna Brown\n",
    "print(p3.full_name)  # James Kerringan"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "9a39b96bde31069459237099b8eca4e2",
     "grade": true,
     "grade_id": "contact_tests_1",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify\n",
    "assert Contact(\"John\", \"B.\", \"Robertson\").full_name == \"John B. Robertson\", \"full_name does not work\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "7834aab013179d7b71acfe9bd5c3d8bd",
     "grade": true,
     "grade_id": "contact_tests_2",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "e7360fd3356ca4c6aa1e13bf926cabb1",
     "grade": true,
     "grade_id": "contact_tests_3",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "04353532dc5aca55875eb07ad29df012",
     "grade": true,
     "grade_id": "contact_tests_4",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Problème 4\n",
    "\n",
    "<table width=\"100%;\">\n",
    "<tr>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; border-right: 1px solid lightgrey; padding-right: 10px;\">\n",
    "\n",
    "L’angle $\\theta$ entre deux vecteurs $\\vec{u}$ et $\\vec{v}$ dans un espace euclidien est défini par la formule suivante:\n",
    "\n",
    "</td>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none;\">\n",
    "\n",
    "The angle $\\theta$ between two vectors $\\vec{u}$ and $\\vec{v}$ in a Euclidean space is defined by the following formula:\n",
    "</td>\n",
    "</tr>\n",
    "</table>\n",
    "\n",
    "$$\\theta = \\arccos\\left(\\frac{\\vec{u} \\cdot \\vec{v}}{\\|\\vec{u}\\|\\|\\vec{v}\\|}\\right)$$\n",
    "\n",
    "\n",
    "<table width=\"100%\">\n",
    "<tr>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; border-right: 1px solid lightgrey; padding-right: 10px;\">\n",
    "\n",
    "Voici une implémentation de cette formule en Python, utilisant NumPy:\n",
    "\n",
    "</td>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none;\">\n",
    "\n",
    "Here is an implementation of this formula in Python using NumPy:\n",
    "</td>\n",
    "</tr>\n",
    "</table>\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def angle_between(v1: np.ndarray, v2: np.ndarray) -> float:\n",
    "    numer = np.dot(v1, v2)\n",
    "    denom = np.linalg.norm(v1) * np.linalg.norm(v2)\n",
    "    if denom == 0:\n",
    "        raise ValueError(\"One of the vectors has zero magnitude.\")\n",
    "    angle_radians = np.arccos(numer / denom)\n",
    "    return float(np.degrees(angle_radians))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table width=\"100%;\">\n",
    "<tr>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; border-right: 1px solid lightgrey; padding-right: 10px;\">\n",
    "\n",
    "Complétez la fonction `random_vector(num_dimensions: int) -> np.ndarray`, qui génère un vecteur aléatoire de `num_dimensions` dimensions, où chaque composante est un nombre flottant dans l'intervalle $[-1, 1)$.\n",
    "\n",
    "Pour cela, utilisez d'abord `np.random.rand(num_dimensions)`. Cela génère un vecteur dont chaque composante est un nombre aléatoire dans l'intervalle $[0, 1)$. Modifiez-le ensuite avec des opérations arithmétiques pour que les composantes soient dans l'intervalle $[-1, 1)$ afin de couvrir toutes les directions possibles.\n",
    "\n",
    "</td>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none;\">\n",
    "\n",
    "Complete the function `random_vector(num_dimensions: int) -> np.ndarray`, which generates a random vector of `num_dimensions` dimensions, where each component is a floating-point number in the interval $[-1, 1)$.\n",
    "\n",
    "To do this, first use `np.random.rand(num_dimensions)`.\n",
    "This generates a vector where each component is a random number in the interval $[0, 1)$.\n",
    "Then modify it using arithmetic operations so that the components fall within the interval $[-1, 1)$, in order to cover all possible directions.\n",
    "\n",
    "</td>\n",
    "</tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "5aa0b0d09406b440f8d6b0bb47734d16",
     "grade": false,
     "grade_id": "random_vector",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "def random_vector(num_dimensions: int) -> np.ndarray:\n",
    "    # YOUR CODE HERE\n",
    "    raise NotImplementedError()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(random_vector(1))\n",
    "print(random_vector(2))\n",
    "print(random_vector(3))\n",
    "print(random_vector(10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "73cd436fab2566b8ce60ab6db8457dd4",
     "grade": true,
     "grade_id": "random_vector_tests_1",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify\n",
    "assert random_vector(3).shape == (3,)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table width=\"100%;\">\n",
    "<tr>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none; border-right: 1px solid lightgrey; padding-right: 10px;\">\n",
    "\n",
    "Complétez la fonction `sample_mean_angle_between_random_vectors` ci-dessous afin qu'elle calcule une estimation de l'angle moyen entre deux vecteurs aléatoires.\n",
    "\n",
    "Pour cela, elle doit générer `num_samples` paires de vecteurs aléatoires à `num_dimensions` composantes (en appelant `random_vector`), calculer l'angle entre chaque paire en utilisant la fonction `angle_between`, et retourner la moyenne de ces angles.\n",
    "\n",
    "</td>\n",
    "<td width=\"50%\" style=\"vertical-align: top; border: none;\">\n",
    "\n",
    "Complete the `sample_mean_angle_between_random_vectors` function below so that it computes an estimate of the mean angle between two random vectors.  \n",
    "\n",
    "To do this, it should generate `num_samples` pairs of random vectors with `num_dimensions` components (by calling `random_vector`), compute the angle between each pair using the `angle_between` function, and return the mean of these angles.\n",
    "\n",
    "</td>\n",
    "</tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "93eac0f8f2e14dcd62d0e9268d37182c",
     "grade": false,
     "grade_id": "sample_mean",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "def sample_mean_angle_between_random_vectors(num_samples: int, num_dimensions: int) -> float:\n",
    "    # YOUR CODE HERE\n",
    "    raise NotImplementedError()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "nbgrader": {
     "cell_type": "code",
     "checksum": "768f36eb4f2792989b5a91db727900aa",
     "grade": true,
     "grade_id": "sample_mean_tests_1",
     "locked": true,
     "points": 2,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Tests - ne pas modifier/do not modify\n",
    "sample_mean_angle_between_random_vectors(1000, 3)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv (3.13.7)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
