Introduction

Histoire

https://fr.wikipedia.org/wiki/Histoire_des_langages_de_programmation https://en.wikipedia.org/wiki/Timeline_of_programming_languages

Motivation: why studying C and Python?

Cool to study both

  • C is low-level and Python is high-level
  • C is statically typed VS Python is dynamically typed
  • C is old-fashioned while Python has some new trendy features (pip <3)
  • Possible to do bindings C/Python
    • Actually many Python librairies are written in C, C++ etc.
  • They are both successful languages
  • They are languages used at agrégation d'informatique

Why C?

  • C is simple (KISS!)
  • With C, you will understand how your computer works
  • Important for your system and architecture course
  • C is still used for system development (but not so much!)
  • The syntax of C is in many languages
  • A way to understand Rust, C++... and actually programming in general (and Tensorflow is coded in C++)

Why Python?

  • Python is really really really used everyhere (data science, machine learning, software development, backend, etc.)
  • Python has good libraries
  • Python is elegant
  • Python steals good features from other languages (Haskell, object-programming languages)
  • Python is used in education
  • A way to understand OOP, Javascript, other script languages
  • Python has a strong community

The Zen of Python

Good practice

  • git
  • tests
  • bonnes structures
  • commentaires (spécifications)
  • good variable names, etc.
  • encapsulation
  • abstraction

Exercices

  • Collect few software or libraries you use and look in which languages they have been developped

Aller plus loin

Organisation du module

Equipe pédagogique

  • Alice Brenon
  • François Pitois
  • François Schwarzentruber

prénom.nom@ens-lyon.fr en remplaçant ç par c

Quand ?

  • Cours/TP mercredi matin de 10h15-12h15
  • TP d'approfondissement, mardi 8h-10h 130/E001

Evaluation

  • Un TP noté sur place, créneau habituel du TP, une des semaines
  • Projet sur 3 semaines à un moment de l'année
  • Examen final la semaine du 13-17 janvier

Autres idées

  • Projet Wikipedia (écrire/traduire/illuster des articles)
  • Projet rédaction de quizz / création d'un jeu de cartes sur la programmation
  • Création d'illustrations pour le cours

Git

Git est un gestionnaire de versions. Il est décentralisé.

Pourquoi ?

  • Pour travailler à plusieurs (sans s'envoyer des mails !)
  • Pour garder plusieurs versions courantes du logiciel (branches)
  • Pour de la documentation "historique" : quelle était la raison de cette ligne de code ?

Pourquoi décentralisé ?

  • Pour pouvoir travailler dans le train

Idée générale

Quelques commandes

Pour faire çaIl faut taper ça
Pour récupérer du code du serveurgit clone <adresse>
Pour dire qu'un fichier particulier doit être versionnégit add <nomdufichier>
Pour estampiller mes fichiersgit commit -a -m "algorithme Dijkstra"
Pour mettre mes modifications sur le serveurgit push
Pour récupérer les modifications des autres depuis le serveurgit pull
Pour connaître l'URL du dépôt distant (serveur)git remote -v

Commandes avancées

Pour faire çaIl faut taper ça
Modifier l'URL du serveur d'un dépôt localgit remote rm origin puis git remote add origin <url to NEW repo>, puis git push origin --all

Quiz

  • Quelle est la différence entre upstream et origin ?
  • Quelles est la différence entre workspace, index et local repository ?
  • A-t-on besoin d'une connexion Internet pour faire un git commit ?

Bash

Pourquoi ?

  • Parce que les interfaces graphiques n'offrent pas l'automatisation du terminal
  • Impossible de donner des arguments à un programme avec une interface graphique

Racourcis clavier

Pour faire....Faire
Aller au début de ligneCtrl + A
Aller à la fin de ligneCtrl + E
Supprimer tout ce qu'il y a après le curseurCtrl + K
Pour coller ce qu'il y avaitCtrl + Y
Pour enlever la commande couranteCtrl + C
Rechercher une commande qu'on a écrite il y a longtempsCtrl + R

Basic commands

To do...write
Going in the directory <oùaller>cd <oùaller>
Going to the home directorycd ~
List the filesls and better ls -l
Create a directorymkdir <name>
Remove/delete sthrm <what>
Search inside a filegrep whatIwantToSearchInsideTheFile FileName

Redirection

./1sum < inputTextFile.txt
./1sum < inputTextFile.txt > outputTextFile.txt

Tube

ls -l | grep key | less

  • list of the files, one per line
  • then we keep the lines containing the word "key"
  • then we show the result in a scrolling page with less

Gestion des droits des fichiers sous UNIX

Connaître les droits d'un fichier

Avec ls -l, on connaît les droits des fichiers dans le répartoire courant. Avec lst -l fichier.txt, on connaît les droits du fichier fichier.txt. Une ligne comme

-rwxr-xr-x 1 fschwarz logica 25648 Oct 13 13:56 a.out

se lit comme : Droit du propriétaire 👩 | Droit du groupe 🏠 | Droit des autres 🐱🐶 | Nom du propriétaire 👩 | Nom du groupe 🏠 | |---|----|---|----|---| r w x | r - x | r - x | fschwarz | logica |

  • r signifie que l'on peut lire le fichier 👁
  • w signifique que l'on peut écrire 🖊
  • x signifie que l'on peut exécuter ⚙

Changer les droits

chmod

Créer une documentation à partir des commentaires

doxygen est un outil pour générer de la documentation à partir de sources C, C++, Python etc. correctement commentés.

Installation de doxygen

L'outil est disponible dans les paquets Linux traditionnels :

    sudo dnf install doxygen

Bien commenter

Au début de chaque fichier, il faut quelque chose comme :

        /**
        * \file main.c
        * \brief Programme de tests.
        * \author Franck.H
        * \version 0.1
        * \date 11 septembre 2007
        *
        * Programme de test pour l'objet de gestion des chaines de caractères Str_t.
        *
        */

Pour les fonctions, on commente comme ça :

        /**
        * \fn static Str_t * str_new (const char * sz)
        * \brief Fonction de création d'une nouvelle instance d'un objet Str_t.
        *
        * \param sz Chaine à stocker dans l'objet Str_t, ne peut être NULL.
        * \return Instance nouvellement allouée d'un objet de type Str_t ou NULL.
        */

Utilisation de doxygen

Dans le répertoire d'un projet, on lance doxygen -g pour créer un fichier doxyfile qui est le fichier de configuration de la documentation.

On lance alors doxygen pour générer la documentation.

VSCode

Pour faire...Faire
Sélectionner une ligne entièreCtrl + L
Bien formater le fichierCtrl + Maj + I
Fermer le fichier courantCtrl + W
Pour renommer une variable/functionF2

Bibliothèque raylib

raylib est une bibliothèque pour dessiner et plus généralement développer des jeux vidéos en C. Elle offre des fonctions pour dessiner des rectangles, des cercles, des lignes, etc.

Installation

  • cloner le dépôt (or git clone --depth 1 https://github.com/raysan5/raylib.git raylib)
  • lire https://github.com/raysan5/raylib/wiki/Working-on-GNU-Linux
  • aller dans raylib/src
  • exécuter make PLATFORM=PLATFORM_DESKTOP. Un fichier .a a été généré. Il s'agit d'une librairie statique (statique dans le sens où elle contient du code qui va être ajouté à vos exécutables qui utilisent raylib).
  • Faire sudo make install. Cela va copier la librairie statique .adans le bon dossier où gcc va chercher les librairies (généralement /usr/local)

Ecrire un programme qui utilise raylib

Ecrire un fichier main.c contenant :

#include <stdio.h>
#include <raylib.h>

int main()
{
    InitWindow(640, 480, "Test membership of a point in a polygon");
    SetTargetFPS(20);
    while (!WindowShouldClose())
    {
        BeginDrawing();
        
        EndDrawing();
    }

    CloseWindow();

    return 0;
}

Puis compiler votre fichier main.c avec gcc main.c -lraylib -lGL -lm -lpthread -ldl -lrt -lX11.

Débugueur gdb

Le débugueur gdb (pour GNU Debugger) permet d'arrêter le programme (points d'arrêt) et d'inspecter des variables, la pile d'appel etc.

Mise en place

L'utilisation de gdb se fait sur l'exécutable directement. Mais il faut donc que l'exécutable contienne des données supplémentaires pour permettre le débogage. Pour cela on utilise le flag -g par exemple

gcc -Wall -g programme.c -o programme

Avec -g, l'exécutable contient des informations de débogage (liens entre code machine et numéro de lignes dans le code source) pour que gdb puisse nous raconter des choses :

Utilisation de gdb

On lance le programme en mode débogage en faisant

    gdb programme

On a alors un invite de commande pour réaliser le débogage. On peut toujours quitter le débogueur en tapant quit.

Points d'arrêt

Un point d'arrêt est un endroit du programme où le débogueur doit arrêter l'exécution.

Commandes

CommandesRaccourcisEffets et exemples
quitqon quitte le débogueur
breakpoint 15b 15met un point d'arrêt à la ligne 15
breakpoint bloup.c:15b bloup.c:15met un point d'arrêt à la ligne 15 du fichier bloup.c
breakpoint dfsb dfsmet un point d'arrête au début de la fonction dfs
watch xw xon s'arrête à chaque fois que la variable x change
print xp xon affiche la valeur de la variable x
listlon affiche l'endroit dans le code où on est
stepson fait un pas dans l'exécution du programme
nextnon fait un pas mais en considérant un appel de fonction au niveau courant comme atomique
continuecon continue l'exécution jusqu'au prochain point d'arrêt
backtracebtOn affiche la pile d'exécution (la pile d'appels)
clear 15enlève le point d'arrêt à la ligne 15
clear dfsenlève le point d'arrête au début de la fonction dfs
deletesupprime tous les points d'arrêt
delete 2supprime le deuxième point d'arrêt / watchpoint

Débugueur Valgrind

Fuite mémoire

Parfois, des cellules mémoire ne sont plus accessibles. Par exemple, si on appelle la fonction f suivante :

int f() {
    char* p = malloc(3);
}

cela allouera 3 cases mémoires qui ne seront pas libérées.

Lecture de zones non allouées

Si on essaie d'accéder à une zone non allouée, on obtient généralement un Segmentation fault (core dumped) comme avec :

void main() {
    int* p = NULL;
    printf("%d\n", *p);
}

Avec valgrind, on peut savoir où est l'erreur.

Utilisation

 gcc -g main.c
 valgrind ./a.out
 valgrind -v --leak-check=summary ./a.out

Makefile

Compiler le projet en un coup

gcc myotherCfile.c main.c -o main -Wall

où le flag -o veut dire output.

  • Quel défaut y-a-t-il de compiler tout ?
L'ajout d'une toute petite instruction dans `main.c` demande de compiler tout depuis le début, le compilateur doit relire `myotherCfile.c` en entier aussi, alors que ce fichier n'a pas été modifié.

Compiler un projet fichier par fichier

gcc -c -o myotherCfile.o myotherCfile.c
gcc -c -o main.o main.c
gcc -o main main.o myotherCfile.o

où le flag -c signifie que l'on ne fait compiler mais pas lier.

Comme le montre l'image ci-dessous :

  • la compilation consiste à transformer un fichier source .c en fichier objet (du code machine) .o mais en laissant des "trous" pour les fonctions qui sont définis dans d'autres modules.
  • la liaison vient remplir les trous avec le code machine manquant.

Pourquoi a-t-on besoin d'un outil pour compiler automatiquement ?

Parce que les commandes ci-dessus sont rébarbatives à taper et à retenir. Il vaut mieux un outil pour gérer la compilation.

Compiler avec Makefile ( version naïve)

  • Créer un fichier Makefile.
  • Y écrire :
    all:
        gcc myotherCfile.c main.c -o main -Wall

Dans le terminal, on tape make pour construire le projet. Le nom all s'appelle une cible.

Attention, la ligne d'après contient un tab (et pas 4 espaces !) suivi de la commande à exécuter pour construire le projet.

Compiler intelligente avec Makefile

Avec un Makefile, on peut avoir plusieurs cible.

all: main

main: main.o myotherCfile.o
    gcc -o main main.o myotherCfile.o

main.o: main.c
    gcc -c -o main.o main.c

myotherCfile.o: myotherCfile.c
    gcc -c -o myotherCfile.o myotherCfile.c

clean:
    rm *.o main

La cible main a besoin d'avoir déjà effectué le travail pour les cibles main.o et myotherCfile.o, et consiste à effectuer gcc -o main main.o myotherCfile.o.

  • Où est-ce qu'a lieu la liaison dans le Makefile ci-dessus ? Dans la cible main.

  • Qu'est ce que fait gcc -c -o main.o main.c ?

Elle compile le fichier main.c en main.o en laissant des trous pour les fonctions déclarées mais non définies.

Variables dans un MakeFile

On peut définir des constantes dans un MakeFile. Par exemple, on définit la constante CC qui donne le nom du compilateur. Pour avoir le contenu de la constante on écrit $(CC). Ecrire CC ça écrit juste CC ; nous on veut le contenu.

CC=gcc

all: main

main: main.o myotherCfile.o
    $(CC) -o main main.o myotherCfile.o

main.o: main.c
    $(CC) -c -o main.o main.c

myotherCfile.o: myotherCfile.c
    $(CC) -c -o myotherCfile.o myotherCfile.c

clean:
    rm *.o main

Pattern

Voici trois règles qui ont le même pattern :

myotherCfile.o: myotherCfile.c
    $(CC) -c -o myotherCfile.o myotherCfile.c

bloup.o: bloup.c
    $(CC) -c -o bloup.o bloup.c

miaou.o: miaou.c
    $(CC) -c -o miaou.o miaou.c

Au lieu de cela, on peut écrire :

%.o: %.c
    $(CC) -c -o $@ $^
  • Le % signifie nimportequelnomdefichier.
  • $@ = le nom de la règle nimportequelnomdefichier.o
  • $^ = la prémisse, ici nimportequelnomdefichier.c
nom de la règle:prémisse
$@$^

Lister les fichiers

La commande principale pourrait être :

main: main.o myotherCfile.o bloup.o miaou.o
    $(CC) -o main main.o myotherCfile.o bloup.o miaou.o

Pour réaliser cela, on a besoin de lister les .o. Or, on ne les connait pas encore. Mais on sait qu'il y a en un par fichier source .c. On peut lister les fichiers sources avec la commande wildcard :

SOURCES=$(wildcard *.c)

La fonction wildcard prend un argument qui est une expression régulière de fichiers et elle produit la liste des fichiers qui correspondent à l'expression régulière. Dans l'exemple, la constante SOURCES vaut main.c myotherCfile.c bloup.c miaou.c.

Pour obtenir la liste des .o correspondantes, on fait une substitution :

main.c myotherCfile.c bloup.c miaou.c

🡇 main.o myotherCfile.o bloup.o miaou.o

Pour cela on écrit :

OBJECTS=$(SOURCES:.c=.o)

Et maintenant, la règle principale qui était :

main: main.o myotherCfile.o bloup.o miaou.o
    $(CC) -o main main.o myotherCfile.o bloup.o miaou.o

devient

main: $(OBJECTS)
    $(CC) -o main $^

Aller plus loin

On peut faire des boucles et autres en Makefile... bref...

Histoire du C

|https://www.trytoprogram.com/images/C-programming-history.jpg](https://www.trytoprogram.com/images/C-programming-history.jpg)

WhenWhat
1972Creation of C by Dennis Ritchie
1973Re-implementation of the Unix kernel
1978 C78first edition of the book The C Programming Language by Brian Kernighan
and Dennis Ritchie and improvements of the language (functions returning struct)
1989 C89First standardization of C, locales (taking into account user's languages)
1999 C99inline functions! complex numbers! variable-length arrays!
2011 C11generic macros, multi-threading in the standard

Première séance

Durant cette première séance, nous allons écrire de petits programmes qui écrivent dans le terminal. Le but est d'apprendre :

  • à compiler un seul fichier C (un seul fichier pour l'instant !)
  • écrire un programme C sans pointeur

Header files

Des fichiers comme stdio.h sont dans usr/include. Ils contiennent des signatures de fonctions. Ils font partie de la bibliothèque standard. Mais on peut aussi en créer, ou utiliser ceux d'une bibliothèque non-standard (comme raylib pour dessiner à l'écran !).

Entrées-sorties

stdio.h est le fichier pour de la bibliothèque standard (std) pour les entrées-sorties (io).

Pour utiliser ces fonctions, on écrit :

      #include <stdio.h>

Ecrire sur la sortie stdout

putchar('c') écrit un c dans le terminal.

printf est la fonction pour écrire d'un coup des informations plus compliquées ✎ :

    printf("Bonjour");
    printf("Bonjour\n");

    printf("Voici votre identifiant : %d", id);
    printf("Voici votre nouveau prénom : %s", prenom);
    printf("Voici votre nouveau prénom et votre identifiant : %s %d", prenom, id);

⚠ Ne jamais écrire printf(dataFromTheUser) car l'utilisateur pourrait écrire des %s, %d, voire des %n qui écrivent dans la mémoire. Le comportement peut être inattendu, et c'est faille de sécurité garantie :).

Lire l'entrée stdin

getchar() renvoie le prochain caractère sur l'entrée puis le consomme, c'est-à-dire que la prochaine fois que l'on exécute getchar() on lira le prochain caractère. getchar() renvoie EOF si c'est fini.

scanf est la fonction pour lire des informations plus compliquées que l'utilisateur écrit dans la console ou de manière générale dans stdin ⌨ :

    int n;
    scanf("%d", &n);

Quelques éléments du langage C

Une déclaration de variable est un morceau de code qui renseigne le type d'une variable. Par exemple :

    int x;

Il y a pleins de types primitifs : int, long, unsigned int, char, bool (avec #include <stdbool.h>).

Une instruction est un morceau du programme qui ne renvoie rien. Par exemple, printf("aïe");

Une expression est un morceau du programme qui renvoie une valeur. Par exemple 1+2. Mais aussi x = 42 qui renvoie... 42.

Incrémentation

Considérons l'instruction (qui est aussi une expression)

    x = 42;
  • Both expressions x++ and ++x increment x.
  • The value of the expression x++ is 42.
  • The value of expression ++x is 43.

L'instruction (qui est aussi une expression) suivante

    x += 3;

ajoute 3 à x.

Boucle for

    for(int i = 0; i < n; i++)
        ...


    for(int i = 0; i < n; i++) {
        ...
    }

En fait, la boucle for en C est puissante. Elle a l'expressivité des boucles while :

    for(expr1; expr2; expr3) {
        ...
    }

est équivalent à

    expr1;
    while(expr2) {
        ...
        expr3;
    }

La fonction main

Un programme C contient toujours une fonction main qui est la fonction appelée quand on exécute le programme :

    #include <stdlib.h>

    int main() {
        ...
        return EXIT_SUCCESS;
    }

On verra plus tard une signature plus compliquée pour main car on peut gérer des arguments donnés au programme.

Utilisation du compilateur

gcc -Wall -pedantic -ainsi -std=c99 programme.c -o programme
OptionsMeaning
-Wallenables all compiler's warning messages
-Wextrastill a bit more warnings
-pedanticenables all compiler's warning messages
-ansiturns off gcc features that are not in the ANSI C
-std=c99ANSI C99
-std=c11ANSI C99
-o programmetells that the output is the file programme

Exercices

Triangle d'étoiles

Ecrire un programme qui prend demande à l'utilisateur/rice un nombre entier n puis affiche un triangle d'étoiles sur n lignes. Par exemple si n vaut 5, on affiche :

*
** 
***
****
*****

Pyramide

Même chose avec une pyramide :

   *
  *** 
 *****
*******

Nombre de caractères dans le texte sur stdin

En utilisant getchar(), écrire un programme nombreligne.c qui lit stdin et sur donne le nombre de lignes dans l'entrée.

Par exemple sur

    ./nombreligne < textExemple.txt

il donne le nombre de lignes dans le fichier texte textExemple.txt.

  • Pareil avec le nombre de lignes
  • Pareil avec le nombre de mots

Occurrences de caractères

Dans cet exercice, vous aurez besoin d'un tableau d'entiers de taille 26 déclaré avec int A[26];. On accède au i-ème élément du tableau A avec A[i]. On rappelle que chaque caractère est un nombre entre 0 et 255, codé sur 8 bits. Si c est un char, on peut écrire c - 'a' pour faire la différence entre le code de c et celui de la lettre 'a'.

Ecrire un programme qui lit stdin et qui affiche un histogramme du nombre des occurrences des lettres (on ignore les autres caractères, les chiffres, etc.) comme :

    a ***
    b ******
    c **
    d *
    e ************
    z ***

Variables

La portée (scope) d'une variable est la portion de code dans laquelle la variable peut être utilisée.

La durée de vie (life-time) d'une variable est la durée d'existence d'une variable.

ScopeLife-time
Global variablesAllAlways
Variables in functionsFunctionFunction
Static variables in functionsFunctionAlways

Les variables statiques peuvent servir pour :

  • faire un compteur et cacher le contenu du compteur en dehors
  • faire une fonction pour le drag drop avec la souris (on stocke/cache la position précédente de la souris)

Mémoire

La mémoire allouée pour un programme est divisée en trois parties :

  • Segment de données. On y trouve les variables globales et les variables statiques.
  • Pile d'appels. On y trouve les variables locales à une fonction. Les tableaux locaux sont stockées sur la pile aussi.
  • Le tas mémoire. C'est pour y mettre des données dont on ne connait pas à l'avance la taille, ou dont la taille peut modifiée au cours du programme (structure de données comme des tableaux redimensionnables, des listes chaînées, etc.).

Typage en C

Le type en C sert à dire combien d'octets prend une variable.

La fonction sizeof renvoie combien d'octets sont utilisé pour stocker un certain type / une certaine variable.

sizeof(truc)utilise ... octets
sizeof(char)1
sizeof(int)4
    int x;
    printf("%d\n", sizeof(x));
    
    int A[5];
    printf("%d\n", sizeof(A));

Conversion implicite

    1 + 2.3f

Casting

    int i = 2;
    int j = 3;
    float quotient = 2/3;

Oh non, quotient vaut 0. Mais on peut caster :

    quotient = ((float) 2) / 3

Le C est de bas niveau...

Voici un site où on peut compiler du code C et voir le résultat en assembleur :

https://godbolt.org/

Exercices

Génération de labyrinthes

Proposer un algorithme aléatoire pour générer un labyrinthe comme

XXXXXXXXXXXXXXX
X X     X     X
X XXX X X X XXX
X X   X X X   X
X X XXXXX XXX X
X X   X     X X
X X X X XXXXX X
X X X X X   X X
X XXX X X X X X
X X   X X X   X
X X XXX X XXX X
X X     X X X X
X XXXXXXX X X X
X         X   X
XXXXXXXXXXXXXXX

Il s'agit de produire un tableau A 2D de taille n x m où n et m sont des entiers impairs avec :

  • A[i, j] = 'X' ou ' '
  • A[0, j], A[i, 0], A[n-1, j], A[i, m-1] contiennent 'X' ;
  • A[i, j] = 'X' lorsque i et j sont pairs
  • A[i, j] = ' ' lorsque i et j sont impairs
  • Le graphe G est un arbre, où G est le graphe non orienté (V, E) où V sont les couples (i, j) dans {1, 3, .., n-2} x {1, 3, ..., m-2} et E contient les arêtes {(i, j), (i', j')} avec :
  • i' == i+2 et j'== j et A[i+1, j] == ' '
  • ou i' == i et j' == j+1 et A[i, j+1] == ' '

Prochaine séance

Nous allons utiliser la bibliothèque raylib pour pouvoir afficher des dessins (rectangles, points, lignes, etc.) à l'écran :).

  • Jeu vidéo comme agar.io
  • Afficher une fractale comme celle de mandelbrot
  • Promenade aléatoire
  • Appartenance d'un point à un polygone ?
  • parcours de Graham

Pour aller plus loin

Tableaux

Un tableau consiste en des cases contigus dans la mémoire.

int A[4];

On peut initialiser un tableau directement comme ça :

int A[4] = {1, 2, 3, 5};

C'est le type qui détermine le nombre d'octets d'une case mémoire :

char A[4];

Ils peuvent être multidimensionels.

int A[4][3];

Tableau de taille non connue à la compilation

Depuis C99, on peut définir des tableaux de taille non connue à l'avance, à la compilation.

void f(int n) {
    int A[n+5];
    ...
}

Attention cependant, cette fonctionnalité n'est pas autorisée en C++. La raison : une incompatibilité avec les templates car A[1], A[2], A[3], A[4], etc. sont tous des types différents.

Tableaux globaux

Les tableaux globaux doivent avoir une taille fixée à la compilation. Ils sont stockés dans le segment de données.

Tableaux locaux à une fonction

Ils sont stockés dans la pile pas dans le tas.

Tableau 1D en paramètre

Quand on passe un tableau en paramètres, on donne un pointeur, c'est-à-dire l'adresse mémoire du tableau. Autrement dit, quand on empile le paramètre, on empile juste son adresse, et pas touuuuuuuuuuut le tableau.

void f(char A[300000]) {
    ...
}

En particulier, si on fait :

void f(char A[300000]) {
    printf("%d\n", sizeof(A));
}

Ca imprime 8 et pas 300000.

Ces trois signatures sont équivalentes :

        void f(char p[3]);
        void f(char p[]);
        void f(char* p);

Tableau 2D en paramètre

On peut passer un tableau 2D en paramètre.

    void f(char p[4][3])

Par contre, ce n'est pas équivalent à

void f(char p[])
void f(char* p)

Forcément, vu que l'on a pas l'information sur la taille.

On ne peut pas non plus écrire :

void f(char p[][])

car la machine ne sait pas comment interpréter le tableau 2D.

Par contre, on peut omettre la taille selon la première coordonnée et écrire :

void f(char p[][3])

En effet, on a l'information suffisante dans le corps de la fonction pour savoir comment utiliser p, même si on ne connaît la borne pour le premier indice. Mais c'est à l'humain de faire attention.

Impossible de renvoyer un tableau

⚠ Le code suivant ne compile pas.. le type `int[]` n'existe pas :
int[] createArrayButNotPossible()
{
    int A[3] = {1, 2, 5};
    return A;
}

Le code suivant compile... mais renvoie une adresse vers une zone mémoire qui n'est plus allouée :

int* createArrayButNotPossible()
{
    int A[3] = {1, 2, 5};
    return A;
}

Aller plus loin

  • For 2D arrays: see here

  • Peut-on allouer un tableau dynamiquement dans le tas qui est multidimensionel ?

Types primitifs

Entiers non signés

Les types entiers non signés (comme unsigned int, unsigned long, unsigned char etc.) sur n bits permettent de stocker des valeurs entières entre 0 et \(2^{n} - 1\).

TypeNombre de bits
int32
char8
short16
long32 ou 64
long long64
  • Quelles sont les valeurs possibles pour un unsigned char ? Entre \(0\) et \(2^{8}-1\) autrement dit entre Entre 0 et 255.

Entiers signés

Les types entiers signés (comme int, long, char etc.) sur n bits permettent de stocker des valeurs entières entre \(-2^{n-1}\) et \(2^{n-1}-1\).

  • Quelles sont les valeurs possibles pour un char ? Entre \(-2^{7}\) et \(2^{7}-1\) autrement dit entre Entre -128 et 127.

Représentations

octale

En ajoutant le symbole 0, on écrit un nombre entier en représentation octale : 02322

hexadécimale

Avec 0x, pareil mais c'est en hexadécimal : 0x4D2

Nombres flottants

Il n'y a pas de nombres flottants non signés.

TypeNombre de bitsExemples
float3212.34F
double6412.34, 12.e-4
long double80 ou 12812.34L

Conversions

Implicites

On convertit de int à float

float x = 1;

### Explicites avec cast

L'inverse, convertir n'est pas automatique, il faut "caster" :

float x = ((float) 2) / 3;

Struct

Un struct permet de regrouper plusieurs informations dans un bloc. Par exemple, un point sur l'écran regroupe deux informations : l'abscisse et l'ordonnée.

struct Point
{
    int x;
    int y;
};

Initialisation

Nouveauté ;) C99 ! On peut initialiser en une ligne un struct :

struct Point point = {2, 1};

ou alors en nommant les champs :

struct Point point = {.x = 2, .y = 1};

On peut aussi assigner un struct plus tard mais il faut caster (i.e. mentionner le type entre parenthèses) :

point = (struct Point) {.x = 2, .y = 1};

Accès aux champs

On accède aux champs avec un point (!). Par exemple pour l'abcisse de notre point point on écrit :

point.x

Et pour récupérer son ordonnée :

point.y

Accès aux champs d'un struct pointé

Quand on a un pointeur p vers un struct Point, on a un raccourci d'écriture :

(*p).x

s'écrit

p->x

C'est plus lisible.

Enum

Un enum est un type avec un domaine fini, dont les éléments sont listés. Par exemple, un jour de la semaine est parmi lundi, mardi, mercredi, jeudi, vendredi, samedi ou dimanche :

enum weekday {Mon, Tue, Wed, Thur, Fri, Sat, Sun};

On déclare une variable comme ça, par exemple disant que mon jour favori est le dimanche :

enum weekday favoriteDay = Sun;

Voici un autre exemple de type enum pour les couleurs des cartes :

enum cardsuit {CLUBS, HEARTS, SPADES, DIAMONDS};

Union

Un type union permet de lire/écrire dans la même portion mémoire mais avec des champs différents. Considérons :

union mask
{
    unsigned char n[4];
    unsigned long fulldata;
};

En déclarant

union mask m = {.fulldata = 1025};

on a une variable m sur 4 octets dont les bits sont :

00000000 00000000 00000010 00000001

Avec m.fulldata, on lit 00000000 00000000 00000010 00000001 en entier que l'on interprète comme 1025, alors que m.n[i] permet de lire chaque octets.

  • Que vaut m.n[0] ? On lit 00000000, donc il vaut 0.

Que vaut m.n[1] ? On lit 00000000, donc il vaut 0.

Que vaut m.n[2] ? On lit 00000010, donc il vaut 2.

Que vaut m.n[3] ? On lit 00000001, donc il vaut 1.

Donner un nom à un type

Le mot-clé typedef permet de donner un nom personalisé à un type existant.

typedef struct Point
{
    int x;
    int y;
} tPoint;

Ainsi, au lieu de faire struct Point p;, on peut écrire tout simplement tPoint p;.

typedef unsigned int distance;

distance d = 3;

Bref, l'utilisation de typedef est :

Champs de bits

Alors là, c'est très bas niveau. Dans l'exemple qui suit, on a un point où l'abcisse est codé sur 4 bits, l'ordonnée sur 3 bits, auquel on ajoute 1 bit pour savoir si le point est sélectionné ou non.

typedef struct Point
{
    int x: 4;
    int y: 3;
    int selected: 1;
} point_t;

A priori, vous pouvez oublier les champs de bits ; on ne va pas les utiliser je pense.

Exemples plus compliqués

Vendeur.se de livres, mugs et Tshirt, vous tenez une base de données des objets dans votre magasin. Voici un enum pour le type d'objets (un livre, un mug ou un T-shirt) :

enum itemType {Book, Mug, Shirt};

On pourrait définit un struct pour stocker le prix, le type d'objets puis les informations de l'objet :

#define TITLE_LEN 30
#define AUTHOR_LEN 20
#define DESIGN_LEN 20

struct item_crazy
{
    double price;
    enum itemType type;
    char booktitle[TITLE_LEN + 1];
    int book_num_pages;
    char design[DESIGN_LEN + 1];
    int shirt_color;
    int shirt_size;
};

Mais c'est dommage car il y aura toujours des champs que l'on utilisera pas. Par exemple, pour un livre, on utilisera booktitle et book_num_pages, mais pas design, shirt_color, shirt_size.

La solution est d'utiliser un union pour réutiliser la même portion de mémoire :

struct item
{
    double price;
    enum itemType type;
    union
    {
        struct
        {
            char title[TITLE_LEN + 1];
            int num_pages;
        } book;
        struct
        {
            char design[DESIGN_LEN + 1];
        } mug;
        struct
        {
            char design[DESIGN_LEN + 1];
            int color;
            int size;
        } shirt;
    } item;
};

Pour l'initialisation, on peut faire comme ça (merci le C99) :

struct Item item = {.price = 10.0, .type = Shirt, .item = {.shirt = {.design = "miaou", .color = 0, .size = 2}}};

ou alors affecter chaque champ séparemment :

item.price = 10.0;
item.type = Shirt;
strcpy(item.item.shirt.design, "miaou");
item.item.shirt.color = 0;
item.item.shirt.size = 2;

Switch case

switch case est une construction de conditionnelle sur la valeur d'une expression, ici item.type. On peut donc effectuer différentes actions selon le type d'objet à promouvoir.

switch (item.type)
{
case Book:
    printf("The book %s is available!\n", item.item.book.title);
    break;
case Shirt:
    printf("Shirt with %s of size %d available!\n", item.item.shirt.design, item.item.shirt.size);
    break;
case Mug:
    printf("Mug with %s available!\n", item.item.mug.design);
    break;
default:
    exit(-1);
}

Pourquoi mettre des break ? Car sinon, ça passe au case d'après. C'est pratique car on peut mettre du code commun pour plusieurs cas :

    switch(i) {
        case 0: case 1: dothejob(); break();
        case 2: doanotherthing(); break();
        default: doDefault();
    }

Pour aller plus loin : Rust

Rust est un langage beaucoup plus sûr. On peut y définir directement des enum qui sont aggrémentés des données. On fait ensuite du pattern matching pour effectuer la bonne action selon le type de l'objet.

enum Item {
    Book(f64, String, i64),
    Mug(f64, String),
    Shirt(f64, String, i32, i32)
}

fn main() {
    let item = Item::Shirt(10.0, "miaou".to_string(), 0, 2);

    match item {
        Item::Book(price, title, nb_pages) => &println!("The book {} is available!", title),
        Item::Mug(price, design) => &println!("Mug with {} available!", design),
        Item::Shirt(price, design, color, size) => &println!("Shirt with {} of size {} available!", design, size),
    };
}

En tout cas, le C contrairement au Rust montre mieux comment les objets sont représentés en mémoire. C'est tout l'intérêt pédagogique du C, même si c'est lourdingue à programmer...

Pointeurs

Motivation

struct human {
        int x;
        int y;
        int souffle;
};

void deplacerDroite(struct human a) {
    a.x++;
    a.souffle++;
}

struct human player; 

void game_loop() {
    ...
    if(isKeyRight())
        deplacerDroite(player);
    ...
}

Que pensez-vous du programme ci-dessus ? La variable globale player n'est pas modifée quand on appuie sur la flèche de droite. Le contenu de player est copié quand on appelle deplacerDroite donc seul l'argument a, qui est une copie de player est modifié.

Solution : un pointeur

Un pointeur est une variable contenant une adresse mémoire.

void deplacerDroite(struct human *a) {
    (*a).x++;
    (*a).souffle++;
}

deplacerDroite(&player);
AdresseDonnées
adresse*adresse
&datadata

On a déjà vu cette solution pour les tableaux car on ne pouvait de toute façon pas faire autrement. Un tableau se dégrade en pointeur quand il est passé en argument :

void fillArray(int A[10]) {
 ...
}

est la même chose que

void fillArray(int A[]) {
 ...
}

qui est la même chose que

void fillArray(int *A) {
 ...
}

Exercice métaphorique

  • Si x est une maison, qu'est ce que &x ? L'adresse postale de x

  • Si x est un livre dans une bibliothèque, qu'est ce que &x ? Sa côte qui donne sa localisation (pièce, étagère, etc.)

  • Si x est une page dans un livre, qu'est ce que &x ? Le numéro de la page.

  • Si x est une variable déclaré par int x;, qu'est ce que &x ? L'adresse mémoire où on trouve l'entier x.


  • Si a est une adresse postale, qu'est ce que *a ? La maison, le garage, l'hôpital, l'ENS, etc. qui se trouve à l'adresse a

  • Si a est la côte d'un livre dans une bibliothèque, qu'est ce que *a ? Le livre en question dont la côte est a

  • Si a est une numéro de page d'un livre, qu'est ce que *a ? Le contenu de la page numéro a

  • Si a est un pointeur sur un entier, déclaré par int *a;, qu'est ce que *a ? *a désigne l'entier que l'on peut lire à l'adresse mémoire a.

Déclaration

Déclarer un entier

int a;

a est une variable contenant un entier.

Déclarer un pointeur sur un entier

int *p;

p est une variable contenant une adresse mémoire, à partir de laquelle on interprète le contenu comme un entier noté *p.

Combien de bits prend p en mémoire ? 64 sur les systèmes 64 bits.

Combien d'octets prend p en mémoire ? 8 octets sur les systèmes 64 bits.

Exercices

    int x = 42;
    int y;
    y = x;
    y++;

Que vaut x ? L'instruction y = x réalise une affectation de la valeur de x dans y. C'est une copie. A la fin, y vaut 43 mais x vaut toujours 42.

    int x = 42;
    int *y;
    y = &x;
    (*y)++;

Que vaut x ? L'instruction y = &x réalise une affectation de l'adresse de x dans y. A la fin, *y, qui n'est autre que x vaut 43. Bref, x vaut 43.

Placement de l'étoile

On peut écrire

    int* p;

et

    int *p;

Malheureusement, il vaut mieux écrire attacher l'étoile au nom de la variable. Par exemple :

int *p, i;  // OUI

ou de manière équivalente

int* p, i;  // NON car on pourrait croire que p et i sont de même type

déclare p comme un pointeur sur un int alors i est vraiment un int. Mieux encore, évitez les déclarations sur une seule ligne et écrivez :

int *p; // TRES CLAIR
int i;  // TRES CLAIR

Typage

Toutes les variables suivantes sont des pointeurs :

int *p1;
float *p2;
struct point *p3;
...

Quelles est la taille de p1 ? de p2, de p3 ? Il sont tous sont de taille 8 octets (sur système 64 bits), de quoi écrire une adresse.

Il y aussi le type générique

void*

qui veut dire pointeur sur n'importe quoi.

void *p;

Cela veut dire pointeur vers n'importe quoi, et c'est à nous de savoir ce qu'il y a derrière. Le type void nous empêche d'interpréter les données pointées. Pour cela, on introduit une nouvelle variable :

void f(void *p)
{
    int* pInt = p;

    /*
    j'utilise ici pInt qui est un pointeur sur un entier
    */
}

ou alors on caste :

void f(void *p)
{
    int x = *((int*) p) + 1;

    /*
    j'utilise ici pInt qui est un pointeur sur un entier
    */
}

Scoop, quel est la taille d'un void* ? Toujours 8 octets (sur système 64 bits), de quoi écrire une adresse.

Pointeur nul

Il y a une valeur particulière NULL (qui vaut 0). Elle signifie que le pointeur pointe sur rien. Le comportement normal d'un pointeur est implicitement d'un type optionel :

  • soit il n'y a pas de données (et on pointe vers NULL)
  • soit on pointe vers une donnée

Supposons que p est le pointeur nul, combien d'octets faut-il pour stocker p ? Toujours 8 sur 64 bits !

C. A. R. Hoare a dit en 2009 :

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Blog à lire : https://lucid.co/techblog/2015/08/31/the-worst-mistake-of-computer-science

Pointeurs (suite)

Rappel de l'organisation de la mémoire

  • Qu'est ce qu'il y a dans la zone des données ?

Les variables globales et statiques.

  • Qu'est ce qu'il y a dans la pile ?

Les variables locales.

Allocation mémoire

On peut allouer des octets sur le tas avec la fonction malloc. C'est un peu comme si on achetait de la mémoire. On indique le nombre d'octets, par exemple 4 :

malloc(4);

malloc(4) renvoie :

  • soit un pointeur vers la zone allouée de 4 octets (i.e. l'adresse mémoire du début de la zone allouée)
  • soit NULL en cas d'échec
        int* p = malloc(4);

  • Que contient *p ?
Un entier.
  • Où est stocker *p avec le malloc ci-dessus ?

Dans le tas.

  • Que contient p ?
Une adresse mémoire.
  • Où est stocker p avec le malloc ci-dessus ?

Dans la pile. On peut imaginer :

int main()
{
    int* p = malloc(4);
    ...
}

p est une variable locale comme les autres, sauf qu'elle contient une adresse mémoire.

Amélioration de l'appel

En fait on écrit plutôt

int* p = malloc(sizeof(int));

Encore mieux :

int *p = malloc(sizeof(*p));
  • Pourquoi est-ce mieux ?

Car si on change le type de p par exemple :

struct point *p = malloc(sizeof(*p));

ça fonctionne toujours bien !

Accès à la valeur pointée

    int* p = malloc(sizeof(*p));
    print("%d\n", *p);

  • Que pensez-vous du programme suivant ?
    int* p = malloc(4);
    print("%d\n", *p);

Ca fonctionne, même si c'est maladroit de laisser un 4 volant comme ça.

  • Que pensez-vous du programme suivant ?
    int* p;
    print("%d\n", *p);

C'est le mal. L'adresse mémoire p vaut une valeur non connue. On va donc lire *p alors que la zone mémoire n'a pas été allouée. D'abord, tu achètes, ensuite tu utilises.

  • Que pensez-vous du programme suivant ?
    int* p = 1452;
    print("%d\n", *p);
C'est le mal. L'adresse mémoire 1452 n'est peut-être pas allouée. Donc pareil, on ne peut pas forcément y lire.
  • Que pensez-vous du programme suivant ?
    int* p = malloc(3);
    print("%d\n", *p);

Outre qu'il ne faut pas écrire 3 comme ça, ça ne va pas. On achète 3 octets mais il en faut 4. Là, le dernier octet n'a pas été acheté.

  • Que pensez-vous du programme suivant ?
    int* p = malloc(10);
    print("%d\n", *p);

Outre qu'il ne faut pas écrire 10 comme ça, ça fonctionne. On peut lire les 4 premiers octets et l'interpréter comme un entier *p.

Pour les struct

On écrit p->x au lieu de (*p).x.

Bonne utilisation de malloc

En fait, malloc peut échouer s'il n'y a plus assez de mémoire. Dans ce cas, malloc renvoie NULL. Il faut faire :

int *p;
if(p = malloc(sizeof(int)))
{
    printf("Out of memory!\n");
    exit(EXIT_FAILURE);
}

Vous pouvez par exemple écrire :

void* xmalloc(size_t nbytes) {
    void* p = malloc(nbytes);
    if(!p)
    {
        printf("Out of memory!\n");
        exit(EXIT_FAILURE);
    }
    return p;
}

Bon, c'est un peu brutal pour un utilisateurrice. Il peut le vivre comme ça :

void* xmalloc(size_t nbytes) {
    void* p = malloc(nbytes);
    if(!p)
    {
        printf("Pas de mémoire. Et j'ai codé rapidement mon programme. Donc tu perds toutes tes données, dommage pour toi. Relance le programme et refais tout ce que tu as fait ! Bonne chance !\n");
        exit(EXIT_FAILURE);
    }
    return p;
}

On peut imaginer des solutions plus agréables pour l'utilisateur. On débute une action... mais une erreur mémoire ne quitte pas le programme. On annule juste le début de l'action et on revient à l'état d'avant.

Désallocation avec free

    free(p);

Désalloue la mémoire allouée par malloc (ou ses copines realloc, calloc, aligned_alloc). C'est comme vendre de la mémoire car on n'en a plus besoin. Par exemple :

    int *p = malloc(sizeof(*p));
    *p = 5;
    free(p);
  • Que pensez-vous du programme suivant ?
    int *p = malloc(sizeof(*p));
    *p = 5;
    free(p);
    free(p);
Ne revends pas quelque chose que tu as déjà vendu.
  • Que pensez-vous du programme suivant ?
    int *p = 4321;
    free(p);

Ne vends pas quelque chose qui n'est pas à toi. (de toute façon, int *p = 4321 c'est le mal).

  • Que pensez-vous du programme suivant ?
void f() {
    int *p = xmalloc(sizeof(*p));
}

Un appel à f achète 4 octets, mais c'est une zone mémoire à laquelle on ne pourra plus accéder.

  • Que pensez-vous du programme suivant ?
int* f() {
    return xmalloc(sizeof(int));
}

Un appel à f achète 4 octets puis renvoie le pointeur vers ces 4 octets. Pas de soucis car on garde l'accès à ces 4 octets. Attention, à ne pas oublier de libérer la mémoire plus tard !

Applications : liste chaînée

  • Ecrire les opérations pour le type abstrait d'une pile
  • Implémenter la pile avec une liste chaînée

Applications directes

  • Implémenter une pile avec une liste chaînée
  • Implémenter le parcours de Graham pour calculer l'enveloppe convexe d'un ensemble de points du plan
    • Etudier l'algorithme
    • Utiliser l'implémentation de la pile
    • Utiliser pointeur sur une fonction pour trier les points

Pointeurs et tableaux

Un pointeur peut être utilisé comme un tableau

Le programme suivant alloue sur le tas 3 cases contenant chacune un entier.

int* p = malloc(3*sizeof(*p));
p[0] = 1
p[1] = 3
p[2] = 35

p[0] et *p sont synonymes.

  • Que pensez-vous de ce programme ?
int* p = malloc(3*sizeof(*p));
p[0] = 1
p[1] = 3
p[2] = 35
p[10] = 5

malloc c'est acheter une zone mémoire. Là, avec p[10] = 5, on écrit là où on a pas acheté.

  • Qu'est ce qui est moche dans int* p = malloc(3*sizeof(*p)); ?

Le fait d'avoir 3 qui est volant. Si c'est une constante, on peut faire par exemple :

#define NB_POTATOES 3
int* p = malloc(NB_POTATOES*sizeof(*p));

Un tableau est quasiment comme un pointeur

Un tableau statique ou un tableau déclaré dans une fonction est considéré comme un tableau : il a une taille et sizeof(tableau) renvoie le nombre d'octets utilisés pour stocker tout le tableau.

Mais si on passe un tableau à une fonction, ça devient un pointeur. Les signatures suivantes sont équivalentes :

    void f(char* p)
    void f(char p[])
    void f(char p[3])

Dans f, sizeof(p) renvoie 8 octets (64 bits).

Exercice : tableau dynamique

realloc permet de réallouer une zone mémoire. Voir TD.

Arithmétique des pointeurs

Addition d'un pointeur et d'un entier

Considérons le pointeur p tableau de 3 int :

avec

     p++

on avance de 1 case int dans le tableau (i.e. de 4 octets dans la plage mémoire) :

De la même manière, on peut faire p += 5 et on avance de 5 cases du tableau (5 fois 4 octets).

Différence de pointeurs

    int* p;
    int* q;
    ...
    int nbCasesTableauEntrePetQ = q - p;

Sur le dessin, q - p vaut 2 (il s'agit de 2 cases int).

La différence de pointeurs est un nombre relatif. Dans l'exemple p - q vaut -2.

Marrant !

int a[3] = {0, 1, 42};

Que vaut a[2] ? 42

Que vaut 2 [a] ? 42 aussi mais on ne va pas arrêter le module pour autant.

En fait a[i] est une macro pour *(a+i).

Pointeurs vers un tableau 2D

Prenons l'exemple d'un tableau 2D d'entiers. Une façon de faire est de considérer un pointeur de pointeurs vers des entiers :

int **p;

Exercice à faire en TD

  • Implémenter une fonction qui alloue un tableau 2D de taille n x n.

Un tableau 1D énorme qui simule un tableau 2D

typedef char* array2d_t;

array2d_t array2d_create(size_t sizeX, size_t sizeY) {
    char *array = malloc(SIZEX * SIZEY * sizeof(char));
    return array;
}

void array2d_free(array2d_t A) {
    free(A);
}

char array2d_get(array2d_t A, size_t x, size_t y) {
    return A[x*SIZEY + y];
}

void array2d_free(array2d_t A) {
    free(A);
}

Un tableau 1D de pointeurs vers des tableaux 1D

typedef char** array2d_t;

array2d_t array2d_create(size_t sizeX, size_t sizeY) {
    char **array = malloc(SIZEX * sizeof(char *));
    for (int i = 0; i < SIZEX; i++) 
        array[i] = malloc(SIZEY * sizeof(char *));
    return array;
}

void array2d_free(array2d_t A) {
    for (int i = 0; i < SIZEX; i++) 
        free(array[i]);
    free(A);
}

/*
array2d_t A = array2d_create(64, 32);
... on utilise A[x][y]...
array2d_free(A);
*/

Un tableau 1D de pointeurs vers des endroits dans un seul tableau 1D

typedef char** array2d_t;

array2d_t array2d_create(size_t sizeX, size_t sizeY) {
    char **array = malloc(SIZEX * sizeof(char *));
    array[0] = malloc(SIZEX*SIZEY);
    for (int i = 1; i < SIZEX; i++)
        array[i] = array[0] + (SIZEY * i);
    return array;
}

void array2d_free(array2d_t A) {
    free(A[0]);
    free(A);
}

/*
array2d_t A = array2d_create(64, 32);
... on utilise A[x][y]...
array2d_free(A);
*/

Mot clé Const

En C, le mot clé const s'applique sur ce qu'il y a à sa gauche. Il veut dire "je suis une constante".

Pointeur vers un (ou des) caractères en lecture seule

    char const *  pointer1 = malloc(1);
    *pointer1 = 'a'; //non car le char est une constante
    pointer1 = malloc(1); //yes

Par convention, on met souvent le mot-clé const tout à gauche :

    const char *  pointer1 = malloc(1);
    *pointer1 = 'a'; //no
    pointer1 = malloc(1); //yes
    const char* p = malloc(4);
    p[0] = 'a'; // no
    p[1] = 'a'; // no
    p = malloc(4); // yes

Pointeur en lecture seule sur des caractères

Dans l'exemple suivant, le "je suis une constante" s'applique sur l'étoile, autrement dit sur le pointeur.

    char* const q = malloc(4);
    q[0] = 'a'; //yes
    q = NULL; // non car on ne peut pas modifier le pointeur

Pointeur en lecture seule sur des caractères en lecture seule

    const char* const r = malloc(4);
    r[0] = 'a'; //no
    r = NULL; //no

Réallocation

void * realloc( void * pointer, size_t memorySize );

Calloc

void* calloc( size_t num, size_t size );
int* A = calloc(NB_PATATES, sizeof int);

## Exercices

  • Implémenter un tableau dynamique

Pointeurs et fonctions

Mettre des fonctions dans des variables

On peut déclarer une variable f de type int(*)(void), c'est-à-dire une fonction qui ne prend rien et renvoie un entier :

int myFunction(){ return 42; }

int (*f)(void); // là on déclare un pointeur sur une fonction qui prend pas d'argument et renvoie un entier
f = &myFunction; // mais en fait le & est optionnel car de tte façon, ça ne veut rien dire d'autres
x = (*f)()  // appel de la fonction

Pourquoi parle-t-on de pointeurs sur une fonction ?

f = &myFunction;

La segment text contient le programme lui-même. Cette zone est en lecture seule. Mais on peut pointer dessus. &myFunction est une adresse vers une case de cette zone.

Passer des fonctions en paramètre

Du coup, on peut passer des fonctions en paramètre, par exemple pour renseigner comment trier un tableau d'entiers :

#include <stdlib.h>

int cmp(const void *pA, const void *pB)
{
    /**
     * ici on fait comme si c'était des entiers à trier
    **/
    
    int* intpA = pA;
    int* intpB = pB;
    int a = *intpA;
    int b = *intpB;
    
    return a - b;
    /**
        < 0 si "a < b"
        = 0 si "a = b"
        > 0 si "a > b"
    */
}

qsort(tableau, nbElementsTableau, nbOctetsElement, cmp)

Types pour les fonctions

Déclarer une variable

Voici le type d'une fonction :

int (*) (const void*, const void*)

En C, les types donnent une position pour mettre la variable déclarée. Vous avez toujours vu TYPE NOMVARIABLE;. Là on met la variable sur le partie verte :

int (*f) (const void*, const void*);

Définir un type

typedef int (*ComparisonFunction) (const void*, const void*);

Et après on peut déclarer une variable f qui contient une fonction dont la signature est donnée par le type ComparisonFunction :

ComparisonFunction f;

ou

int cmp(const int *pA, const int *pB)
{
    return *pA - *pB;
}


qsort(tableau, nbElementsTableau, nbOctetsElement, (ComparisonFunction) cmp)

Chaînes de caractères en C

Se termine par \0

"chouette"

Chaîne de caractères = pointeur sur char

On peut initialiser

char* s = "chouette";

En mémoire, s est un pointeur, une adresse mémoire, vers une zone de la mémoire avec 9 octets alloués où on a :

QCM

  • Que pensez-vous de char[3] s = "abc"; ? Il manque une case pour '\0'.

  • Que pensez-vous de char[10] s = "abc"; ? Pas de soucis, le compilateur complète avec des \0.

  • Que pensez-vous de char s[] = "abc"; ? Pas de soucis, le compilateur comprend qu'il faut allouer 4 octets, et d'ailleurs il n'alloue que 4 octets.

  • Comment accéder à la i-ème lettre ? a[i].

  • Que pensez-vous du programme suivant ?

        char s[] = "abca";
        s[2] = 'b';

Il modifie la 2e lettre est on a la chaîne "abba".

  • Que pensez-vous du programme suivant ?
char* s = "abca";
s[2] = 'b';
Ca ne fonctionne pas car s est un pointeur vers une chaîne constante "abca" qui se trouve dans le segment de données en lecture seule. Il y a le segment de données normal qui est lecture/écriture, mais une zone de segment de données en lecture seule (rodata, *read-only data*).
  • Est-ce que ce programme est correct bien que le tableau ne finissent pas par \0 ?
char A[3];
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';

Oui, c'est un tableau classique de caractères. Par contre, il ne faut pas le donner aux fonctions que l'on va voir plus tard, de <string.h> qui gèrent les chaînes de caractères ! En effet, A[3] (qui sort de la zone allouée) n'est peut-être pas \0 et donc les fonctions vont continuer à lire des parties de la mémoire indéterminée jusqu'à trouver un \0.

  • Est-ce que ce programme est correct ?
char* A = malloc(2);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';

Clairement non car A[2] n'est pas alloué.

  • Est-ce que ce programme est correct ?
char* A = malloc(3);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';

Le malloc peut ne pas réussir ce n'est pas bien. Mais à part ça, ça fonctionne. Par contre, A n'est pas une chaîne de caractères, c'est juste un pointeur vers un tableau avec 3 caractères.

char* A = malloc(4);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[3] = `\0`;

Le malloc peut ne pas réussir ce n'est pas bien. Mais à part ça, ça fonctionne et A est une chaîne de caractères car ça termine par \0.

char* A = malloc(10);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[3] = `\0`;

Le malloc peut ne pas réussir ce n'est pas bien. Mais à part ça, ça fonctionne et A est une chaîne de caractères car ça termine par \0. Il y a des cases non utilisées mais ce n'est pas grave.

Le C c'est vraiment du bas niveau !

Considérons deux chaînes de caractères

char s[10], t[10];
  • Peut-on écrire s = "abc"; ? Non, car les tableaux ne sont pas assignables.

  • Peut-on écrire t = s; ? Pareil, non.

  • Peut-on initialiser char u[10] = "abc"; ? Oui, à l'initialisation c'est bon. C'est équivalent à char u[10] = {'a', 'b', 'c', '\0'};

  • Peut-on tester s == t ? Oui, on peut toujours tester l'égalité d'adresse mémoire de tableaux ou de pointeurs :).

  • Que teste s == t ? Que l'adresse mémoire s est égale à l'adresse mémoire t.

  • Que pensez-vous du programme suivant ?

char* donnerSalutation() {
    char s[50] = "Bonjour";
    return s;
}
Le code compile mais c'est **très grave**. On déclare un tableau de 50 cases mémoire sur la **pile**. On y écrit `"Bonjour"` puis on renvoie l'adresse (`s`) alors que la pile va être écrasée !
  • Que pensez-vous du programme suivant ?
char* donnerSalutation() {
    char* s = "Bonjour";
    return s;
}
On déclare un pointeur qui pointe vers la zone de données en lecture seule où il y a écrit `"Bonjour"`. On ne peut pas modifier la chaîne de caractères. Il ne faut pas appeler `free` sinon erreur (on ne peut pas libérer une zone de données en lecture seule !).
  • Que pensez-vous du programme suivant ?
char* donnerSalutation() {
    char* s = malloc(10*sizeof *s);
    s[0] = 'h';
    s[1] = 'i';
    s[2] = '!';
    s[3] = '\0';
    return s;
}
On déclare un pointeur qui pointe vers une zone du tas et on y écrit la chaîne `"hi!"`. On peut modifier la chaîne de caractères. Il ne faut pas oublier de faire `free` quelque part.

Longueur d'une chaîne de caractères

size_t strlen(const char *s);
  • Ecrire soi-même une fonction int len(char* s) qui renvoie la longueur de la chaîne s.

Parcours d'une chaîne de caractères

  • Ecrire une fonction qui teste d'appartenance d'un caractère à une chaîne.
  • Ecrire une fonction qui transforme une chaîne de caractères en sa version en minuscule.

Considérons :

char* s = "Bonjour tout le monde";
  • Que vaut strlen(s) ? 21 car il y a 21 caractères dans "Bonjour tout le monde".

  • Que vaut sizeof(s) ? 8 car il faut 8 octets pour stocker une adresse mémoire.

Considérons :

char[] s = "Bonjour tout le monde";
  • Que vaut strlen(s) ? 21 car il y a 21 caractères dans "Bonjour tout le monde".

  • Que vaut sizeof(s) ? 22 car il faut 22 octets pour mettre la chaîne de caractères "Bonjour tout le monde" de 21 caractères, puis \0.

Comparaison de chaînes

int strcmp(const char *s1, const char *s2)
strcmp("abricot", "chat")strcmp("chat", "chat")strcmp("chat", "abricot")
< 00> 0

Copie de chaînes

strdup

char * strdup( const char * source);

Cette fonction renvoie une copie de la chaîne de caractères source :

  • Elle alloue une nouvelle zone mémoire avec un malloc (caché dans l'appel strdup) de la même taille que source
  • En cas de succès, elle copie source vers cette nouvelle zone mémoire et renvoie un pointeur vers cette zone
  • En cas d'échec (hé oui, le malloc peut échouer), elle renvoie un pointeur nul.

⚠ Attention à libérer la mémoire avec free de la copie créée.

strcpy

strcpy copie src dans dst que l'on a déjà alloué préalablement. Attention, si ce n'est pas alloué suffisamment : .

char * strcpy(char * restrict dest, const char * restrict source);

strncpy fait la même chose mais dans la limite de len caractères.

size_t strncpy(char * restrict dst, const char restrict * src, size_t len);
size_t strlcpy(char * restrict dst, const char * restrict src, size_t dsze);

memcpy etc.

void * memcpy ( void * destination, const void * source, size_t nbOctetsACopier );

La même chose mais pour de la mémoire et pas de vérification de caractère nul ou autre. C'est fait pour copier n'importe quoi.

Concaténer deux chaînes

strcat de <string.h>

char * strcat(char* restrict dst, const char * restrict src);

Précondition :

  • il faut que dst soit suffisamment alloué pour contenir le contenu de la concaténation. Sinon .

Effet :

  • place src à la fin de dst
char[1000] s = "";
strcat(s, "Bienvenue ");
strcat(s, "à ");
strcat(s, "l'ENS de Lyon");

strcat renvoie :

  • dst ! C'est pour pouvoir faire des cascades d'appel

On peut donc chaîner les appels à strcat comme cela :

char[1000] s = "";
strcat(strcat(strcat(s, "Bienvenue "), "à "), "l'ENS de Lyon");

Variante efficace

Malheureusement, la complexité de strcat est en (O(|s1| + |s2|)). cf https://www.joelonsoftware.com/2001/12/11/back-to-basics/ On souffre du problème de Shlemiel le peintre.

  • Ecrire une version mystrcat qui fait la même chose que strcat mais renvoie un pointeur sur la fin de la chaîne.
char* mystrcat( char* dst, char* src )
{
    while (*dst) dst++;
    while (*dst++ = *src++);
    return --dst;
}

Variante à la Python

  • En utilisant memcpy, écrire une variante string_concat qui renvoie une nouvelle chaîne de caractères qui est la concaténation de s1 et s2.

La signature de memcpy est

void * memcpy( void * restrict dst, const void * restrict src, size_t nbBytesToCopy );
/*
return a new string that is the concatenation of s1 and s2
(in Python, s1 + s2)
**/

char * string_concat(char* s1, char* s2) {
    int l1 = strlen(s1);
    int l2 = strlen(s2);

    char* result = malloc(l1 + l2 + 1);

    if(result == NULL)
        return NULL;

    memcpy(result, s1, l1);
    memcpy(result + l1, s2, l2+1);
    
    return result;
}

Variante avec réallocation

  • Ecrire une fonction qui prend une chaîne s1 et qui lui concatène s2. Si pas assez de mémoire, on réalloue s1. La fonction renvoie la nouvelle chaîne. Dans tous les cas, la chaîne s1 initiale est perdue.
char* strcatrealloc(char* s1, char* s2) {
    int l1 = strlen(s1);
    int l2 = strlen(s2);
    char* result = realloc(s1, l1 + l2 + 1 * sizeof(*result));
    if(result == NULL)
        return NULL;

    memcpy(result + l1, s2, l2 + 1);
    return result;
}

Tableaux de chaînes de caractères

  • Dessiner un schéma mémoire de
#define NB_MAXCHAR 12

char planets[][NB_MAXCHAR] = {"Mercury", "Venus", "Earth",
                    "Mars", "Jupiter", "Saturn",
                    "Uranus", "Neptune", "Pluto"};
  • Même chose pour
char *planets[] = {"Mercury", "Venus", "Earth",
                    "Mars", "Jupiter", "Saturn",
                    "Uranus", "Neptune", "Pluto"};
  • Même chose pour
#define NB_PLANETS 9

char ** planets = malloc(NB_PLANETS*sizeof(*planets));
planets[0] = "Mercury";
planets[1] = "Venus";
planets[2] = "Earth";
planets[3] = "Mars";
planets[4] = "Jupiter";
planets[5] = "Saturn";
planets[6] = "Uranus";
planets[7] = "Neptune";
planets[8] = "Pluto";

Arguments en ligne de commande

int main(int argc, char *argv[]) {
    ...
}

Par exemple si notre programme est programme et l'utilisateur lance la commande

programme -l arf.txt

alors

  • argc vaut 3
  • argv[0] contient "programme"
  • argv[1] contient "-l"
  • argv[2] contient "arf.txt"
  • argv[3] est le pointeur NULL.

Exercices

  • Modifier les projets précédents pour qu'ils prennent en entrée les arguments en ligne de commande
  • Ecrire un programme qui prend en entrée un texte et qui renvoie sa version justifiée.

Aller plus loin

  • article philosophique sur les chaînes qui finissent par \0 : https://queue.acm.org/detail.cfm?id=2010365

Fichiers

Tout est dans

#include <stdio.h> 

Lecture

Ouverture d'un fichier

FILE *file;
file = fopen("tilemap.txt", "r");

if (file == NULL)
    return -1; // ou exit(-1)

Lire le contenu

while (!feof(file))
{
    char line[MAXLINELENGTH] = "";
    fgets(line, MAXLINELENGTH, file);
    puts(line);
}

feof : ai-je atteint la fin du fichier

feof(file)

fget : lire un caractère

int character = fget(file);
  • renvoie le caractère suivant lu dans le fichier
  • ou alors EOF si on a atteint la fin du fichier

fgets : lire une ligne

fgets(line, MAXLINEENGTH, file)

fscanf : lire des données formatées

fscanf(file, "%s", &variable);

Remarque : scanf(...); c'est la même chose que fscanf(stdin, ...);.

Fermeture du fichier

A la fin, comme on est poli, on ferme le fichier :

fclose(file);

Ecriture

Ouverture du fichier pour y écrire

FILE *file;
file = fopen("tilemap.txt", "w");

if (file == NULL) {
    printf("Impossible d'ouvrir le fichier. Voici le code d'erreur : %d\n", errno);
    return -1; // ou exit(-1)
}   

Attention, le fichier sera complètement écrasé.

En cas d'erreur, file est le pointeur nul, et la variable errno contient un code erreur.

fprintf : écrire des données formatées

 fprintf( file, "x = %d\n", i );

Remarque : printf(...); c'est la même chose que fprintf(stdout, ...);. On peut aussi écrire dans la sortie d'erreur fprintf(stderr, ...);

Fermeture du fichier

A la fin, comme on est bien éduqué, on ferme le fichier :

fclose(file);

Autres fonctions

fflush : être sûr que le fichier est à jour

On vous a menti. Si tu dis d'écrire quelque chose, a priori le système ne l'écrit pas tout de suite sur le disque :

Voici ce qui se passe : le système écrit dans un buffer (une "todo list"). Quand le buffer est plein, il écrit vraiment sur le disque, ou alors quand on ferme le fichier.

fflush est un fonction qui force l'écrire :

int fflush( FILE * stream );

Quand on écrit dans un fichier, en fait, le système n'écrit pas directement dans le fichier, mais dans un tampon. Il écrit alors le contenu du tampon quand il est plein ou quand on ferme fichier. La fonction fflush force à écrire le contenu du tampon effectivement dans le fichier.

Un cas d'usage est l'écriture dans un fichier de log.

  • Que fait l'instruction fflush(stdout); ?

Elle s'assure que tous les printf(..) et autres sur la sortie standard (la console) ont bien été affiché.

Se déplacer dans un fichier

La fonction fgetpos permet de sauvegarder la position courante dans une variable (on passe le pointeur en deuxième argument).

int fgetpos( FILE * stream, fpos_t * pos );
fpos_t pos;
if ( fgetpos(file, &pos) != 0 ) {
    fprintf(stderr, "Cannot access to the file position");
    fclose(file);
    exit(EXIT_FAILURE);
}

On peut ensuite revenir à cette position avec fsetpos :

int fsetpos( FILE * stream, fpos_t * pos );

Alternatives

On a aussi les fonctions :

long *ftell(FILE *stream)

donne la position courante depuis le début du fichier :

  • en nombre d'octets si le fichier est ouvert en mode binaire
  • en nombre de caractères si le fichier est ouvert en mode texte (ça dépend donc de l'encodage)
int *fseek(FILE *stream, long offset, int whence)
  • Si whence == SEEK_SET (0), alors on se déplace de offset >= 0 depuis le début du fichier
  • Si whence == SEEK_CUR (1) alors on se déplace de offset depuis la position courante
  • Si whence == SEEK_END (2) alors on se déplace de offset <= 0 depuis la fin du fichier

  • D'après vous que fait fseek(stdout, 2, SEEK_SET) ?

Ca marche pas si on écrit sur la console. Mais ça marche si la sortie standard écrit dans un fichier (avec une redirection...). Mais bon, c'est une mauvaise pratique.

Lecture/écriture de données

size_t
fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream)
  • fread lit nitems éléments de size octets du fichier stream. Elle les écrit à l'adresse ptr.
  • Elle renvoie le nombre d'éléments lus (a priorinitems, ou alors moins en cas de fin de fichier ou d'erreurs)

size_t
fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream)

Gestion d'erreurs en C

Exemples d'erreurs

Fichiers

FILE *file;
file = fopen("tilemap.txt", "r");

if (file == NULL) {
    //quelle erreur
    return -1; // ou exit(-1)
}

Avec un fichier on peut savoir un stream est dans un état d'erreur avec :

int ferror( FILE * stream );

qui renvoie en fait un booléen.

Problème de réseau

Cf. cours au 1er semestre

Problème de chargement d'une librairie dynamique

Numéro d'erreurs

Quand il y a une erreur dans une fonction, on a un accès un numéro d'erreurs avec la variable globale errno disponible avec #include <errno.h>.

  • errno == 0 : pas de soucis ✅
  • errno != 0 : il y a eu un problème ❌

Message d'erreur

Aussi, la fonction

char * strerror ( int errCode );

affiche le message d'erreur textuel compréhensible par un humain correspondant à l'erreur. La fonction strerror est disponible via #include <string.h>. Typiquement, on appelle :

puts(strerror(errno));

⚠ On ne libère pas la mémoire de la chaîne de caractères d'erreur : free(strerror(errno));

  • D'après vous pourquoi on ne libère pas la mémoire d'une chaîne de caractères renvoyés par strerror ?

Parce que ce sont des chaînes de caractères dans le segment de données en lecture seule (.rodata, read-only data).

Informer l'utilisateur sur la sortie d'erreur

void perror( const char * prefix );

Elle affiche un message d'erreur sur stderr. Comme stdin et stdout qui sont respectivement les flux standards pour l'entrée et la sortie, stderr est le flux standard pour les erreurs. stderr est généralement redirigé vers la console, comme stdout.

Utilisation mot-clé goto

https://github.com/openbsd/src/blob/master/sys/dev/ata/wd.c

setjmp et longjmp

Devoir gérer les erreurs en cas d'imbrication de fonctions est fastidieux.

En Java, Python, C++, et autres,on a des exceptions pour gérer les erreurs. En C, non, mais il y a setjmp et longjmp de <setjmp.h>.

  1. Tu poses un jalon pour y revenir plus tard
  2. Tu exécutes des choses (chargement d'un gros fichier par exemple)
  3. Tu tombes sur une erreur en croisant un ours
  4. Tu reviens au jalon et tu affiches un message d'erreur, tu libères la mémoire et tu reviens dans une situation stable
int setjmp(jmp_buf env);

setjmp pose un jalon 🚩. Elle écrit dans env les données relatives au contexte de la pile d'appel actuel. Cette fonction renvoie 0... sauf que...

void longjmp(jmp_buf env, int value);

longjmp renvoie au jalon avec le contexte de l'époque. On renvoie dans le code où il y avait l'appel à setjmp qui maintenant renvoie value (qui doit être différente de 0).

#include <stdio.h>
#include <setjmp.h>

static jmp_buf jmpBuffer;


void b() {
    ...
    if(error) {
        longjmp(jmpBuffer, 1);
    }
}


void a() {
    b();
}

void bigComputation() {
    a();
}

int main() {
    if (setjmp(jmpBuffer) == 0)
        bigComputation();
    else
        printf("désolé il y a eu une erreur\n"); 

    return EXIT_SUCCESS;
}



Exemple d'utilisation : https://sourceforge.net/p/libpng/code/ci/master/tree/example.c#l322

Fonctions variadiques

Une fonction est variadique si le nombre d'arguments est quelconque. Comme par exemple printf !

Motivation

On peut avoir envie de fonctions variadiques comme pour calculer le maximum :

maximum(1, 2) maximum(1, 2, 9, 5)

stdarg.h

#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#define SENTINEL -1

/**
 * \param the parameters are ints. They should be positive integers except the last number should be SENTINEL.
 * \result the maximum about the numbers
 * */
int maximum( int first, ... ) {
    int max = first;
    va_list parametersInfos;
    va_start( parametersInfos, first );

    while( true ) {
        int current = (int) va_arg( parametersInfos, int );
        if ( current ==  SENTINEL) break;
        if ( current > max )
            max = current;
    }    
    va_end( parametersInfos );
    return max;
}

int main() {
    printf( "%d\n", maximum( 2, 11, 5, SENTINEL ));
    return EXIT_SUCCESS;
}

Failles de sécurité

Débordements de tampons

int save() {
    FILE* file = NULL;
    char filename[256];

    do {
        printf("Entrez le nom de la sauvegarde : ");
        fflush(stdout);
    } while (gets(filename) == NULL);
    file = fopen(filename, "rw");
    ...
}

On suppose ici que le nom de fichier est au plus 256 caractères. Or en réalité rien n'empêche l'utilisateur d'entrer un chaîne de caractères avec plus de 256 caractères.

Le risque est gets aille écrire au delà de la zone réservée pour filename. Elle peut en particulier écrire sur l'adresse de retour de la fonction save.

Pire, un attaquant pourrait se débrouiller que cette adresse de retour soit le contenu du fichier file dans lequel il aura écrit les actions qu'il désire.

Débordement sur le tas

C'est la même idée mais maintenant, on va écrire au delà d'un bloc allouée. On peut alors toucher des informations de gestion de la mémoire (la liste doublement chaînée de la mémoire).

String-format vulnerabilities

int i;
printf("blip%n", &i);

D'habitude printf ne fait que écrire dans la console mais ne modifie pas de variables. En fait, si, avec %n on écrit dans un entier le nombre de caractères déjà écrit sur la console.

Supposons qu'un attaquant contrôle la chaîne de caractères de formatage. Soit trivialement avec :

int i;
printf(stringControléeParLeMéchant, &i);

soit parce qu'il a déjà écrit dans la mémoire à l'endroit de la chaîne de formatage.

Dans ce cas, il peut faire :

int i;
printf("%c %d %n %n %n %n", c, x);

Avec les %n surnuméraires, il écrit dans le contexte de la fonction appelante ou au delà dans la pile.

Conseils :

  • éviter gets, strcat, strcpy, sprintf
  • utiliser printf("%s", string) au lieu de printf(string) !
  • faire du fuzzing, c'est-à-dire du test avec des entrées aléatoires, à compris tester les fonctions accessibles à l'utilisateur par exemple avec une chaîne de caractères et un entier sensé être sa longueur, mais différente de sa longueur, etc.

Race conditions

Une race condition ou condition de course désigne l'examen d'un contexte puis la prise de la décision en fonction de cet examen. Par exemple : je regarde si j'ai les droits sur un fichier, si oui, j'écris dedans.

Le problème est que le contexte peutchanger entre l'examen et le moment où on agit. Ce problème arrive souvent avec plusieurs threads. Mais il arrive aussi en séquentiel, avec des fichiers notamment.

if(access(filename, W_OK) == 0) {
    if((fd = open(filename, O_WRONLY)) == NULL) {
        perror(filename);
        return 0;
    }
    /** écrire dans le fichier */
}

Débordements sur les nombres

longueur = ...;//qui dépend de l'utilisateur
char* buf = (char*) malloc(longueur*2);
if(buf == NULL) return -1;
...

Quel est le problème du code ci-dessus ?

Il se peut que longueur * 2 ne tiennent pas dans un size_t.

Supposons que size_t soit un unsigned char, dans ce cas, longueur = 129, que se passe-t-il ?

longueur * 2 vaut 2.

Résultat : on écrit en dehors de la zone allouée, et catastrophe. Avec OpenSSH <=3.3, un problème de ce type permettait à un attaquant d'être administrateur.

Remarque : calloc permet d'allouer n objets de taille m, et ça vérifie que (m \times n) ne déborde pas.

int nombre;

if(nombre < 1000) {
    char* buf = malloc(nombre * sizeof(char));
    ...
}

Est-ce que vous voyez un problème ?

nombre peut être négatif. On convertit en size_t non signé, on allouera souvent une place très grande. On peut alors écrire un très gros fichier, ou envoyer plein de données sur Internet...

Bibliographie

Programmation avancée en C. Sébastien Varette et Nicolas Bernard.

Threads

Qu'est ce que c'est ?

Jusqu'à présent, le programme était séquentiel (bon avec raylib c'était faux mais vous ne le saviez pas !).

Avec des threads, on est plusieurs à calculer. Chaque personne est un thread, par exemple, l'un est le thread principal et un thread secondaire que l'on a créé :

Le tas est partagé par tous les threads. Mais chaque thread possède sa propre pile d'appel, et bien sûr son propre program counter.

Motivation

Pourquoi utiliser plusieurs threads ?

  • Pour accélérer un calcul sur des architectures multi-coeurs
  • Pour lancer un calcul compliqué alors que l'interface graphique fonctionne encore
  • En JAVA, un thread ramasse-miettes, qui nettoie la mémoire (En Python, c'est en discussion https://peps.python.org/pep-0556/)

Librairie POSIX

Depuis C11, la gestion des threads est possible avec la librairie standard. Avant C11, on utilisait toujours des librairies qui dépendait des plates-formes. Là, on va faire comme dans le temps... on va utiliser la librairie de la norme POSIX qui s'appelle pthread. En effet :

  1. au moins on est sûr que ça même avec des anciennes versions de C sous Unix.
  2. La librairie standard n'est pas franchement différente.
  3. pthread est au programme de l'agrégation d'informatique.
#include <pthread.h>

Pour compiler il faut dire au compilateur d'utiliser la librairie pthread :

gcc -lpthread programme.c

Autrement dit, on effectue une liaison statique avec la librarie pthread.

Principe

But

Disons que l'on a une fonction

/**
@brief faire la plonge
**/
void faireLaVaisselle() {
    ...
}

que l'on aimerait déléguer.

Création d'une variante avec pointeurs

/**
@brief faire la plonge
**/
void* faireLaVaissellePourThread() {
    faireLaVaisselle();
    return NULL;
}

En fait, la communication avec l'extérieur (valeur de retour, et arguments) sont des pointeurs sur des données dans le tas.

Création du thread

Pour cela, on écrit :

pthread_t mon_thread;
int ok = pthread_create(&mon_thread, NULL, faireLaVaissellePourThread, NULL);
  • La fonction pthread_create place l'identifiant du thread créé dans mon_thread. C'est pourquoi on passe l'adresse de mon_thread.
  • Le deuxième paramètre est subtil et dans ce cours, on laisse à NULL.
  • Le troisième paramètre est la fonction f que le thread doit exécuter.
  • Le dernier paramètre sont un pointeur sur les paramètres de la fonction f.

Si ok vaut 0, tout s'est bien passé, on a réussi à créer un thread qui fait la vaisselle. Sinon, c'est qu'il n'y a pas assez de ressources, ou autre (voir doc pour la liste de tous les problèmes possibles).

Pendant que notre thread fait la vaisselle, nous, thread principal, on peut faire autre chose, comme passer la serpière.

  • Est-ce que chaque thread a sa pile d'appel ? Oui, encore heureux !

Attendre un thread

int ok = pthread_join(mon_thread, NULL);

ok vaut 0 si tout va bien. Mais autre chose en cas d'erreur :

  • pas de thread qui s'appelle mon_thread (dans le contenu de mon_thread ne désigne pas un thread)
  • le mon_thread a déjà été attendu
  • il y a deadlocks (i.e. les threads sont bloqués... on verra ça plus tard)

Création d'un thread avec un paramètre

Voici une fonction :

/**
@brief ajoute nbCrepes crêpes sur l'assiette.
@param nbCrepes
@param isRhum
*/
void preparerCrepes(int nbCrepes) {
    ...
}

On aimerait maintenant déléguer la tâche

    preparerCrepes(10);

à un autre thread.

Passage des paramètres

Heureusement, la bibliothèque pthread est générique. La fonction de création de threads ne dépend pas du type des paramètres. D'où l'utilisation de void* (pointeur sur n'importe quoi).

On crée une fonction pour la lecture du paramètre :

void* preparerCrepesPourThread(void* p) {
    preparerCrepes(*(int*) p);
    return NULL;
}

Puis :

pthread_t mon_thread;
int *p = malloc(sizeof(int));
if(p == NULL) exit(EXIT_FAILURE);
*p = 10;
int ok = pthread_create(&mon_thread, NULL, (void*) preparerCrepesPourThread, p);
  • Que pensez-vous de la fonction suivante :
void creerThreadCrepes() {
    pthread_t mon_thread;
    int *p = malloc(sizeof(int));
    if(p == NULL) exit(EXIT_FAILURE);
    *p = 10;
    int ok = pthread_create(&mon_thread, NULL, (void*) preparerCrepesPourThread, p);
}

Pourquoi pas... mais le thread courant perd le contrôle sur le thread créé.

  • Que pensez-vous de la fonction suivante :
void creerThreadCrepes(pthread_t* p_mon_thread) {
    int *p = malloc(sizeof(int));
    if(p == NULL) exit(EXIT_FAILURE);
    *p = 10;
    int ok = pthread_create(p_mon_thread, NULL, (void*) preparerCrepesPourThread, p);
}

Ca va (sauf si il y a des choses que je n'ai pas vu !).

  • Que pensez-vous de la fonction suivante :
void creerThreadCrepes(pthread_t* p_mon_thread) {
    int i = 10;
    int ok = pthread_create(p_mon_thread, NULL, (void*) preparerCrepesPourThread, &i);
}
Ce va pas car on donne une adresse de la pile du thread courant comme argument. C'est dangereux car la fonction `jetefaistonthread` et on perd les données, on lit dans une zone non allouée...
  • Que pensez-vous de ce code là ?
for (size_t i = 0; i < NUM_FRIENDS; i++)
    pthread_create(&friends[i], NULL, meetup, &i);
for (size_t j = 0; j < NUM_FRIENDS; j++)
    pthread_join(friends[j], NULL);

Il y a un problème. Non, pas de soucis sur le fait que la valeur soit sur la pile du thread appelant. Le problème est que la valeur est modifiée pour la boucle, donc tous les threads finissent par avoir la valeur NUM_FRIENDS comme paramètre au bout d'un moment.

Création d'un thread avec plusieurs paramètres

Ajoutons du rhum dans l'histoire. Voici une fonction qui prend deux paramètres :

/**
@brief ajoute nbCrepes crêpes sur l'assiette.
On utilise la pâte avec du rhum ssi isRhum est vrai.

@param nbCrepes
@param isRhum
*/
void preparerCrepes(int nbCrepes, bool isRhum) {
    ...
}

On aimerait maintenant déléguer la tâche

    preparerCrepes(10, true);

à un autre thread.

Passage des paramètres

  • Comment pour passer plusieurs arguments à une tâche à sous-traiter à un thread alors que pthread_create ne prend qu'un seul argument pour ces arguments justement ?

C'est un cas typique où on utilise une une struct pour représenter les paramètres. Démonstration !

Premièrement on définit le struct pour grouper les arguments.

/**
* @brief the struct that groups the parameters of preparerCrepes together
*/
struct mon_thread_args
{
   int nbCrepes;
   bool isRhum;
};

Ensuite, on écrit la fonction qui s'occupe de rediriger vers notre fonction existante :

void *preparerCrepesPourThread(void *pointeurArguments)
{
    struct mon_thread_args *p = pointeurArguments;
    preparerCrepes(p->nbCrepes, p->isRhum);
}

Puis on délègue la préparation des crêpes en créant un thread :

pthread_t mon_thread;
struct mon_thread_args *p = malloc(sizeof(*p));
if(p == NULL)
    exit(EXIT_FAILURE);
p->nbCrepes = 12230;
p->isRhum = true;
pthread_create(&mon_thread, NULL, preparerCrepesPourThread, p);

Application : accélérer l'affichage de l'ensemble de Mandelbrot

Problème de l'affichage de l'ensemble de Mandelbrot

On présente ici le problème d'afficher une comme celle-là.

  • Est-ce que vous savez ce que représente cette image ?

Il s'agit de la fractale de Mandelbrot.

La couleur d'un pixel est donnée par :

/**
 * \fn Color computeColor(complex z)
 * \param z a complex
 * \return the color that should be displayed at z
 */
Color computeColor(complex z)
{
    complex u = 0;

    for (int i = 0; i < NBINTER; i++)
    {
        u = u*u + z;
        if (cabs(u) > 2)
            return ColorFromHSV(i * 255 / NBINTER, 1.0, 1.0);
    }
    return BLACK;
}

Un programme mono-thread est lent ; le déplacement et le zoom dans la fractale râme. Pour cela, on se propose deux solutions.

Découpe du problème : fork and join

Voici la première solution. Au lieu de dessiner l'image en entier, on découpe l'image en zone et chaque thread va calculer sa zone.

  • On démarre les calculs des 4 zones
  • Puis on attend que les calculs soient finis

C'est le principe du fork and join.

  • Est-ce que vous voyez des qualités à cette approche ?

Simple à mettre en oeuvre. On ne partage pas de données. Pas besoin de mutex. Pas d'interblocages. Chaque thread travaille sur sa zone.

  • Est-ce que vous voyez un défaut à cette approche ?

Il se peut que la charge de travail ne soit pas bien réparti. Certains threads travaillent longtemps alors que d'autres peuvent partir en vacances très tôt !

File de tâches

On considère un bassin de threads. Chaque thread vient se servir d'une tâche à effectuer dans une file et s'arrête quand la file est vide.

  • Est-ce que vous voyez des qualités à cette approche ?

Jamais un thread ne s'ennuie. On ne part en vacances que s'il y a plus rien à faire.

  • Est-ce que vous voyez un défaut à cette approche ?

Malheureusement, la file est partagée. Il peut y avoir des problèmes de concurrences. Il faut utiliser un mutex, ce que nous allons voir dans la partie suivante.

Canaux

Il existe des architectures de threads qui ressemblent aux pipes. On ne va pas aborder ça ici.

Concurrence

Deux personnes ensemble ne savent pas compter

  • Qu'écrit ce programme ?
#include <stdio.h>
#include <pthread.h>

int x = 0;

void* f(void*) {
    for(int i = 0; i<50000; i++)
        x++;   
}

int main()
{
    pthread_t t1, t2;
    pthread_create(&t1, NULL, f, NULL);
    pthread_create(&t2, NULL, f, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("%d",x);
    return 0;
}

Il écrit assez souvent 100000 mais pas tout le temps, des fois c'est un autre nombre !

  • Est-ce que une valeur inférieure à la valeur attendue ?

Oui, en fait, par exemple t1 lit 10, fait son calcul 11, t2 lit.... 10, t2 fait son calcul 11, t1 écrit 11, t2 aussi...

Solution : les mutex

Un mutex (pour Mutual exclusion, Exclusion mutuelle) est une sorte de loquet/verrou. On déclare un mutex comme ça :

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

On donne l'adresse du mutex pour que pthread_mutex_init puisse y inscrire l'ID. Le paramètre NULL c'est pour des choses plus compliquées.

On verouille ou déverouille comme ça :

pthread_mutex_lock(&mutex);
...
pthread_mutex_unlock(&mutex);

Le morceau de programme entre le verrouillage du mutex et son déverouillage s'appelle la section critique.


  • En image pthread_mutex_lock(&m42);, c'est une porte labélisé m42, ouverte par défaut, suivi d'une dalle/bouton qui ferme toutes les portes de type m42.

  • En image, pthread_mutex_unlock(&m42);, est une dalle qui ouvre les portes de type m42.

A la fin, on détruit le mutex :

pthread_mutex_destroy(&mutex);

Exemple

#include <stdio.h>
#include <pthread.h>


pthread_mutex_t mutex;
int x = 0;

void* f(void*) {
    for(int i = 0; i<50000; i++) {
        pthread_mutex_lock(&mutex);
        x++;
        pthread_mutex_unlock(&mutex);
    }
           
}



int main()
{
    pthread_t t1, t2;
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&t1, NULL, f, NULL);
    pthread_create(&t2, NULL, f, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&mutex);
    printf("%d",x);
    return 0;
}

Deadlocks

Des fois, avec des mutex, on peut arriver à des situations bloquantes. Comme :

Thread 1
lock(mutex)
lock(mutex)
...

ou

Thread 1Thread 2
lock(mutex1)lock(mutex2)
lock(mutex2)lock(mutex1)
......

Exercices

  • Multiplication de matrices
  • Tri fusion et autres
  • FFT

Références

Programmer avec Rust. Jim Blandy et Jason Orendorff. (même si on fait du C et pas du Rust, le principe des threads est bien expliqué dans ce livre, avec l'exemple de la fractale de Mandelbrot et les différentes méthodes)

Bindings Python et C

But

Nous allons créer un module crazymodule contenant une fonction add qui ajoute deux entiers, fonction implémentée en C. Ainsi, en Python, on écrit :

    import crazymodule
    result = crazymodule.add(1, 2)

Mise en place

sudo apt-get install python3-dev

Ca installe la librairie Python, notamment python3.9/python.h.

Ecriture du fichier C

Nous allons écrire le fichier crazymodule.c.

Compilation

gcc -Wall  -lpython3 crazymodule.c

Pour aller plus loin

https://docs.python.org/3/extending/extending.html

Python

Conditionnals

x = 1
if x == 2:
   a = 3
else:
   a = 2
a

One-line ternary condition

In languages with a C-syntax, it is possible to write an conditional ternary expression: a = (x == 2) ? 3 : 2. It Python, we can do the same with a more understandable syntax:

a = 3 if x == 2 else 2
a

Easy-to-read conditions

In Python, conditions can be written for normal people.

age = 5
print("young" if 0 < age < 18 else "old")
invitationList = ["Alan", "Bob", "Christine", "Dylan", "Emeline", "Franck"]
me = "John"
if me not in invitationList:
    print("you cannot enter")

Assignments in conditions

According to https://peps.python.org/pep-0572/, it is possible to make an assignment in a condition, like in C. To do that, we use the Walrus operator :=.

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
if (n := len(a) > 10):
    print(f"List is too long ({n} elements, expected <= 10)")

No switch, sorry

There is no switch in Python, contrary to C. See https://peps.python.org/pep-3103/

Pattern matching

Here is an example of pattern matching (available in Python 3.10, https://docs.python.org/3/reference/compound_stmts.html#match).

i=0
match i:
    case 0: print("zero")
    case _: print("non zero")

While loops

x = 0
while x < 10:
    x += 1

For loops

In Python, it is possible to write for loops. They take the form of for i in BLOUP where BLOUP is an iterable. It matches somehow the theory of primitive recursive functions: we know that for loops terminates if the iterable object produces a finite number of elements.

for i in range(10):
    print(i)

Generators

A generator is a special kind of function that contains the keyword yield. It returns an iterable object.

def squares(start, stop):
    for i in range(start, stop):
        yield i * i
for i in squares(1, 6):
    print(i)
1
4
9
16
25

Comparing C and Python

CPython
Use caseSystemPrototyping, scripting, data science, etc.
UsersComputer scientistsComputer scientists, Biologists, etc. 
UsageCompiled to machine codeCompiled to bytecode, interpreted
TypingStatic, weakDynamic, strong (somehow static with mypy)
Memory usageBy hand (malloc/free)Automatic via a garbage collector
import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

History

WhenWhat
1991Creation of Python by Guido van Rossum
as a command-line interpreter for the OS Amoeba (research project)
1994Python 1.0: lambda, map, filter and reduce from LISP
2000Python 2.0: list comprehensions from SETL (SET Language) and Haskell
2001Creation of the Python Software Foundation
dec 2008Python 3.0 First standardization of C, locales (taking into account user's languages)
2014Python package manager pip by default

Pythonic constructions

Accessing last elements

In Python, we can access the last elements of a list with index len(L) - 1, len(L) - 2, etc. This is not Pythonic.

L = [1, 2, 1, 3, 4, 5, 8, 42]
L[len(L)-1]

This is the Pythonic option:

L = [1, 2, 1, 3, 4, 5, 8, 42]
L[-1]

Of course, the same trick works for tuples:

t = (1, 2, 1, 3, 4)
t[-1]

Slices

We can obtain slices of a list like this:

L = [1, 2, 1, 3, 4, 5, 8, 42]
L[2:5]
[1, 4]

Quiz

  • What are the outputs of the following programs?
L = [1, 2, 1, 3, 4]
L[2] = 500
L
[1, 2, 500, 3, 4]
L = [1, 2, 1, 3, 4]
M = L[2:4]
M[0] = 500
M
[500, 3]
L = [1, 2, 1, 3, 4]
M = L[2:4]
M[0] = 500
L
[1, 2, 1, 3, 4]

We can obtain a slice of a tuple like this:

T = (1, 2, 8, 45, 2)
T[2:5]
(8, 45, 2)
T[:2]
(1, 2)
T[2:]
(8, 45, 2)

More generally, the syntax is L[start:stop:step], where the element at index stop is excluded.

L = list(range(0, 21))
L[1:8:2]
L = list(range(0, 21))
L[-3::-3]
L = list(range(0, 21))
L[-3:0:-3]

Sum of elements

Python provides sum to sum elements of a list, a tuple:

sum([1, 2, 5])
8
sum((1, 2, 5))
8
sum({1, 1, 2, 5})

List/set comprehensions

This code is super ugly:

squares = []
for i in range(10):
    squares.append(i**2)
squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Instead, we can use list comprehension. See https://peps.python.org/pep-0202/

[i*2 for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

List comprehension comes from the language SETL (SET Language) in 1969, where we can write:

{x: x in {2..n} | forall y in {2..x-1} | x mod y /= 0}

It is also present in Haskell:

s = [x | x <- [0..], x^2 > 3].
A = [[0 for i in range(10)] for j in range(5)]
A
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
A = [[0 for i in range(10)] for j in range(5)]
A[0].append(1)
A
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
[i for i in range(20) if i%2 == 0]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Be careful with *!

[[0]*10]*5
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
A = [[0]*10]*5
A[0][0] = 1
A
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

Here is an example of set comprehension.

{c for c in "Hello world!"}
{' ', '!', 'H', 'd', 'e', 'l', 'o', 'r', 'w'}

Here is an example of dictionary comprehension.

s = "hello world!"
{c: s.count(c) for c in s}
{'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1, '!': 1}

Generators by comprehension

We can also write a sort of list by comprehension but the list itself is never created.

(i*2 for i in range(100))
<generator object <genexpr> at 0x7fd100179eb0>

They can even contain an infinite number of objects:

import itertools
(i*2 for i in itertools.count())
<generator object <genexpr> at 0x7fd100127120>

Unpacking tuples

point = (42, 2)
x, y = point
print(x)
42
x, y = [10, 2]
x
10
x, y = [1, 2, 3]
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

Cell In[28], line 1
----> 1 x, y = [1, 2, 3]


ValueError: too many values to unpack (expected 2)
x, _, y = [1, 2, 3]
y
3

Pattern matching

Here is an example of real pattern matching.

i=0
match i:
    case 0: print("zero")
    case _: print("non zero")

Functional programming

Python has filter, map, etc. even if they are not very useful because of list/set comprehension.

filter(lambda x: x % 2 == 0, [1, 2, 4, 6])
<filter at 0x7fd1002eafa0>
for x in filter(lambda x: x % 2 == 0, [1, 2, 4, 6]):
    print(x)
2
4
6
map(lambda x: 2*x, [1, 2, 3])
<map at 0x7fd0e37be6d0>
list(map(lambda x: x**2, [1, 2, 3]))
[1, 4, 9]

Variables

A variable in Python is a pointer to some content.

Address

Technically, a variable in Python is a pointer, and it contains a memory address to an allocated memory zone containing an object.

x = 42
id(x)
139907129622096

In Python, when numbers are small (between -5 and 255 let say), the memory address are equal.

x = 42
y = 42
print(id(x))
print(id(y))
139907129622096
139907129622096

x = 4398473209847023984723981029836012983
y = 4398473209847023984723981029836012983
print(id(x))
print(id(y))
139906654312272
139906654314288

L = [1, 4]
print(id(L))
139906654305152

L = [1, 4]
M = [1, 4]
print(id(L))
print(id(M))
139907016431104
139906654343488

Types

Python is dynamically typed: each variable x has a type type(x).

type(2)
int
type([1, 2])
list
type((1, 2))
tuple
type((1,))
tuple
type({1, 2})
set
type(frozenset((1, 2)))
frozenset

Mutability vs Immutability

A variable is mutable when the content can be changed, and immutable otherwise.

MutableImmutable
int 42, bool True, float 37.5 
str "hello"
list [1, 2, 3]tuple (1 2 3)
dict {"x": 5, "y":2}from collections import namedtuple
namedtuple('ImmutableRobot', ['name', 'brandname']) (prefer typing.NamedTuple)
object of a given custom class
setfrozenset
x = 2

x = 2
x += 1

x = (1, 4)

x = (1, 4)
x[0] = 2
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[16], line 2
      1 x = (1, 4)
----> 2 x[0] = 2


TypeError: 'tuple' object does not support item assignment
x = [1, 4]
  • What do you think about this picture for the datastructure for a list?

The problem is about resizing the list. The pointer may change. The solution in CPython is to add a new level of indirection.

x = [1, 4]
x[1] = 5

Functions

For the function, it is the same.

def f(x):
    return x+1
type(f)
function

Attributes and methods

Each type, e.g. int is a class has attributes and methods obtained with dir(int). dir(2) gives attributes and methods of 2.

dir(int)
['__abs__',
 '__add__',
 '__and__',
 '__bool__',
 '__ceil__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__divmod__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floor__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__index__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__invert__',
 '__le__',
 '__lshift__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__round__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__trunc__',
 '__xor__',
 'as_integer_ratio',
 'bit_length',
 'conjugate',
 'denominator',
 'from_bytes',
 'imag',
 'numerator',
 'real',
 'to_bytes']
dir(2)
['__abs__',
 '__add__',
 '__and__',
 '__bool__',
 '__ceil__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__divmod__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floor__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__index__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__invert__',
 '__le__',
 '__lshift__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__round__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__trunc__',
 '__xor__',
 'as_integer_ratio',
 'bit_length',
 'conjugate',
 'denominator',
 'from_bytes',
 'imag',
 'numerator',
 'real',
 'to_bytes']
dir(f)
['__annotations__',
 '__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__globals__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__kwdefaults__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']
f.__code__
<code object f at 0x7f3ea9d110e0, file "/tmp/ipykernel_100995/1304018138.py", line 1>

Reference counters

import sys
lst = []
extra_ref = lst
sys.getrefcount(lst)
3

import sys
sys.getrefcount([])

Quiz: same object or not?

L = [[] for i in range(5)]
L[0].append(1)
L
[[1], [], [], [], []]
L = [[]] * 5
L[0].append(1)
L
[[1], [1], [1], [1], [1]]

Functions in functions

In Python, we can define functions in functions.

Variables are bounded by reference.

def f():
    i = 1
    def g():
        return i+1
    i = 2
    return g()

f()
3

Do not worry. For bounding by value it is sufficient to just pass the value as a default parameter. So we can also do the bounded by value.

def f():
    i = 1
    def g(x,i=i):
        return i+x
    i = 2
    return g(20)

f()
21
def f():
    A = []
    for i in range(10):
        def g(i=i):
            return i+1
        A.append(g)
    return A

f()[5]()
6

The same ideas work for lambda expressions.

def f():
    return [lambda : i+1 for i in range(10)]

f()[5]()
10
def f():
    return [lambda i=i: i+1 for i in range(10)]

f()[5]()
6

Parameter passing

Parameter passing is somehow standard in Python. For instance:

def fact(n):
    if n == 0:
        return 1
    else:
        return n * fact(n-1)

fact(50)
30414093201713378043612608166064768844377641568960512000000000000

Default values

Parameters with default values should end the signature:

def f(a, b=0):
    ...
def f(a, b=1):
    return a+b

f(5)
6
Parameters with default values should end the signature.
def f(a, b=0, c=1):
    return a+b+c

Warning when using mutable default values

Mutable default values can lead to unexpected behavior:

def f(b=[]):
    b.append(1)
    return b

f()
f()
[1, 1]

The solution is to replace the mutable default argument by an immutable one as follows.

def f(b=None):
    if b == None:
        b = []
    b.append(1)
    return b

f()
f()
[1]

Positional VS keyword parameters

A positional parameter is characterized by its position. A keyword parameter is characterized by its name.

f(1, b=4)
f(b=4)
etc.
def f(a, b):
    return a-b

f(b=2, a=5)
3

Unpacking arguments

On the calling side, it is possible to unpack tuples/lists and dictionnaries.

Unpacking a tuple/list

For instance,

f(*[1, 2, 3, 4])

is the same as:

f(1, 2, 3, 4)

def f(a, b,c, d):
    return a+b+c+d

f(*[1, 2, 3, 4])
10

Warning: it is impossible to use * elsewhere than in a call.

*[1, 2]
  Cell In[8], line 1
    *[1, 2]
    ^
SyntaxError: can't use starred expression here

Unpacking a dictionnary

With **, you unpack a dictionnary.

f(**{'y': 5, 'z': 6}) is the same as f(y = 5, z= 6).

def f(z, y):
    print(f"z = {z} and y = {y}")

f(**{'y': 5, 'z': 6})
z = 6 and y = 5

You can use both at the same time:

def f(a, b,c, d, y, z):
    return a+b+c+d+y+z

f(*[1, 2, 3, 4], **{'y': 5, 'z': 6})
21

But you cannot give several arguments:

def f(a, b,c, d, y, z):
    return a+b+c+d+y+z

f(*[1, 2, 3, 4], **{'a': 42, 'y': 5, 'z': 6})
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[9], line 4
      1 def f(a, b,c, d, y, z):
      2     return a+b+c+d+y+z
----> 4 f(*[1, 2, 3, 4], **{'a': 42, 'y': 5, 'z': 6})


TypeError: f() got multiple values for argument 'a'

Unpacking in Javascript

In Javascript, unpacking is performed with ...: Math.min(...[2, 3, 1]) is the same as Math.min(2, 3, 1).

Packing arguments

It is possible to get all (remaining) arguments at once. This is called packing. It is possible to get:

  • all (remaining) positional parameters *args as the tuple args
  • all (remaining) keyword parameters **kwargs as a dictionary kwargs
def f(a, b=0, *args, **kwargs):
    return a + b + sum(args) + sum(kwargs.values())

f(1, 2, 3, 4, y=5, z=6)
21

Enforcing parameters to be keyword

A stand-alone * means that the parameters that follows * should be given with keywords.

def f(a, b, *, operator):
    if operator == "+":
        return a+b
    else:
        return a-b

f(3, 5, '+')
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[12], line 7
      4     else:
      5         return a-b
----> 7 f(3, 5, '+')


TypeError: f() takes 2 positional arguments but 3 were given
def f(a, b, *, operator):
    if operator == "+":
        return a+b
    else:
        return a-b

f(3, 5, operator='+')
8

Enforcing parameters to be positional

The / enforces a and b not to be specified by keywords.

def f(a, b, /):
    return a+b

f(3, 5)
8
def f(a, b, /):
    return a+b

f(a=3, b=5)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[11], line 4
      1 def f(a, b, /):
      2     return a+b
----> 4 f(a=3, b=5)


TypeError: f() got some positional-only arguments passed as keyword arguments: 'a, b'

Immutability in Python

What is exactly immutability?

Tuples are immutable. It means we cannot change the size and/or reassign another element to a given cell.

A = (1, 2, [42])
A[1] = 56
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[1], line 2
      1 A = (1, 2, [42])
----> 2 A[1] = 56


TypeError: 'tuple' object does not support item assignment

However, if some element at some cell in a tuple is mutable, we can modify it.

L = [42]
A = (1, 2, L)
A[2].append(3)
print(L)
[42, 3]

Use in dictionnaries and sets

When a whole object is immutable (e.g. tuples of integers, tuples of tuples, etc. but not tuples of lists), it can be used as a key in dictionnaries, or an element in sets. This is because they are hashable.

Technical remark. Actually, some mutable objects could be hashable in principle. But it is a terrible idea. Indeed, the hash of such an element could be just defined in terms of the id (address of the pointer) while equality may be defined from the inner values that may change.

S = {1, 2, 3}
S
{1, 2, 3}
S = {"Bonjour", 1}
S = {(1, 2), (3, 0)}
(132).__hash__()
132
(13200000).__hash__()
13200000
(1320000000000000000000).__hash__()
1057798729767060028
(132,).__hash__()
-2719358537048335948
((1, 2)).__hash__()
-3550055125485641917
[1, 2].__hash__()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[21], line 1
----> 1 [1, 2].__hash__()


TypeError: 'NoneType' object is not callable
S = {(1, 2), (3, 0), (1, 2)}
S
{(1, 2), (3, 0)}
{[], [1]}
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[2], line 1
----> 1 {[], [1]}


TypeError: unhashable type: 'list'
([]).__hash__()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[9], line 1
----> 1 ([]).__hash__()


TypeError: 'NoneType' object is not callable
D = {}
type(D)
dict
D = {(1, 2): 3, (5, 67): None}
D[(1, 2)]
3

Creation of a new object or not?

When a type is mutable, Python creates new objects, because we never know... you may modify it later on:

A = [1, 2]
A is A[:]
False

When the type is immutable, Python does not create a new object if not necessary.

A = (1, 2)
A is A[:]
True

Strings in Python

Strings in Python are immutable arrays of characters.

Accessing characters

s = "Hello"
s[1]
'l'
s = "Hello"
s[-1]
'o'

Immutability

Immutability means that the content of the object cannot change.

s = "Hello"
s[1] = "a"
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[3], line 2
      1 s = "Hello"
----> 2 s[1] = "a"


TypeError: 'str' object does not support item assignment

However, a variable can be reassigned.

s = "Hello "
s = s + "ENS Lyon!"
print(s)
Hello ENS Lyon!

Multiple assignments

s = ""
for i in range(10000000):
    s += f"entier {i};"
L = [f"entier {i};" for i in range(10000000)]
''.join(L)
import io
ss = io.StringIO()
for i in range(10000000):
    ss.write(f"entier {i};")
s = ss.getvalue()

Equality in Python

== (Same value)

== checks that two variables have the same value.

For standard objects

a = [1, 2]
b = []
b.append(1)
b.append(2)
a == b
False
123456789 == 123456789
True
a = 123456789
b = 123456788+1
a == b
False
a = (1, 2, 3)
b = [1, 2, 3]
a == b
False

For custom objects

For custom objects, == has to specified via the magic method __eq__.

class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

User("francois", "francois@ens-lyon.fr") == User("francois", "francois@ens-lyon.fr")
False
class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

    def __eq__(self, other):
        if isinstance(other, User):
            return self.username == other.username and self.email == other.email
        return NotImplemented
User("francois", "francois@ens-lyon.fr") == User("francois", "francois@ens-lyon.fr")
True
class User2:
    def __init__(self, username, email):
        self.username = username
        self.email = email
User2("francois", "francois@ens-lyon.fr") == User2("francois", "francois@ens-lyon.fr")
False

is (Same object)

is means the same object. It means the same pointer, the same address because we point to the very same object.

class Person:
    pass

a = Person()
b = Person()
a is b
False
class Person:
    pass

a = Person()
b = a
a is b
True
a = 123456789
b = 123456789
a is b
False
a = 123456789
b = a
a is b
True
a = 123456789
b = 123456788+1
a is b
False
a = 2
b= 1+1
a is b
True
a = [1,2]
b = a
a is b
True
a = (1, 2, 3)
b = (1, 2, 3)
a is b
False

Summary

In German:

  • is is das selbe
  • == is das gleiche

Classes

Motivation

We aim at avoiding code like this:

import math

x = 2
y = 3
xList = [1, 2, 3, 4]
yList = [1, 2, 3, 4]

def norm2(x, y):
    return math.sqrt(self.x ** 2 + self.y ** 2)
  • Are variables x and y?
  • Are xList and yList together mean list of points?

A first answer: struct

Making a struct like in C by grouping data together.

import math

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

v = Vector2D(2, 3)
L = [Vector2D(1, 1), Vector2D(2, 2), Vector2D(3, 3), Vector2D(4, 4)]

def norm(v: Vector2D):
    return math.sqrt(v.x ** 2 + v.y ** 2)

norm(v)
3.605551275463989

A class = struct + methods

A class mixes both data (fields and attributes) and methods (= functions applied to data).

Advantages:

  • Functions are no more lost and the code is more organized (kind of namespace)
  • More obvious to know on what the method is applied on

This is called encapsulation.

import math

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @property
    def norm(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)
    
    def scale(self, scalar):
        self.x *= scalar
        self.y *= scalar
v = Vector2D(2, 1)
v.scale(0.5)
print(v.norm)
v.scale(4)
print(v.norm)
print(v.x)
print(v.y)
1.118033988749895
4.47213595499958
4.0
2.0



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[8], line 8
      6 print(v.x)
      7 print(v.y)
----> 8 v.norm = 3


AttributeError: can't set attribute

v is (points to) an object. We say that v is an instance of the class* Vector2D. Of course, we can have several instances:

v = Vector2D(2, 1)
w = Vector2D(1, 1)

The concept of class (and of object-oriented programming) appears in many languages: C++, Java, Javascript, C#, etc.

Dataclasses

Problems

When we defined a class Vector2D, equalities is naïve.

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

Vector2D(1, 2) == Vector2D(1, 2) # oh no!!
False

Also printing a Vector2D is not very informative.

print(Vector2D(1, 2))
<__main__.Vector2D object at 0x7f69bc14aac0>

Cumbersome solution...

A cumbersome solution would be to write the magic method __eq__ ourselves.

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return (self.x == other.x) and (self.y == other.y)
    
Vector2D(2, 3) == Vector2D(2, 3)
True

In the same way, we can define the magic method __repr__ for having pretty printing.

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return (self.x == other.y) and (self.y == other.y)
    
    def __repr__(self):
        return f"(x={self.x}, y={self.y})"
    
print(Vector2D(1, 2))
(x=1, y=2)

Pythonic solution

Python provides dataclasses for that. See https://peps.python.org/pep-0557/. We do not have to write __eq__, but actually we do not have to write __init__, __repr__, etc. and many other standard magic functions. Look:

from dataclasses import dataclass

@dataclass
class Vector2D:
    """Class for representing a point in 2D."""
    x: float
    y: float
    
v = Vector2D(1, 2) 
w = Vector2D(1, 2)
print(v == w)
print(v)
True
Vector2D(x=1, y=2)

@dataclass is a class decorator.

Methods

A method is a function applied on an object and defined in a class. In object-oriented object, the goal is to have different objects interacting via message-passing. For instance speak asks the cat to speak.

class Cat():
    name = "Garfield"
    def speak(self):
        print(f"{self.name} says miaou.")

c = Cat()
c.speak()

c.name = "Felix"
c.speak()
Garfield says miaou.
Felix says miaou.

Of course, methods can have parameters than the object itself.

class Cat():
    name = "Garfield"
    def speak(self, strength):
        print(f"{self.name} says {'miaou' if strength < 5 else 'MIAOU'}.")

c = Cat()
d = Cat()
c.speak(2)
d.speak(5)
Garfield
graou says miaou.
Garfield says MIAOU.

Abstraction

Abstraction consists in hidding the low-level details of an object from the user of the object.

  • In some languages, e.g. C++ and Java, we have keywords like private VS public to explicitely say whether a field/method is private/public.
  • In Python, we implicitely inform the user that an attribute/method is private by starting its name with __ (and not finishing with __ otherwise you get a magic function 😉). See https://peps.python.org/pep-0008/
class Animal:
    def __init__(self, health):
        self.__health = health

    def showHealth(self):
        print(self.__health)

a = Animal(5)
a.showHealth()
a.__health
5



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[2], line 10
      8 a = Animal(5)
      9 a.showHealth()
---> 10 a.__health


AttributeError: 'Animal' object has no attribute '__health'

Actually, it creates a field _Animal__health. This is super ugly!

a._Animal__health
5

Exercice: Write a class for a grid graph created with an image.

Inheritance

Definition

A class A inherits from B if all A are B.

Examples:

  • all cats are animals
  • all integers are numbers
  • in a video games, all enemies are movable entities

Why inheritance?

  • Because we can easily reuse the common shared code for animals for cats. (this is a bit fake)
  • It allows abstraction. Some part of the program just consider animals, whether a given object is a cat, dog, etc. is not important. This is called polymorphism.

UML diagrams

We use UML class diagrams that are graphs: nodes are classes and an edge from class A to B means "A is a subclass of B". For instance:

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([('Cat', 'Animal')])
dot

svg

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("Dog", "Animal"), ("Cat", "Animal"), ("Fish", "Animal")])
dot

svg

How to inherit in Python

A subclass of a class is a class that inherits from it. This is abstraction!

  • Any cat is an animal.
  • Any integer is a number.
class Animal():
    lifepoints = 10
    def speak(self):
        pass
    
class Cat(Animal):
    def speak(self):
        print("miaou")

class MagicCat(Cat):
    def speak(self):
        print("MiAoU")
class Animal():
    lifepoints = 10
    def speak(self):
        pass
    
class Cat(Animal):
    def speak(self):
        print("miaou")

class MagicCat(Cat):
    def speak(self):
        print("MiAoU")
9
10

isinstance

isinstance(Cat(), Cat)
True
garfield = Cat()
isinstance(garfield, Animal)
True
garfield = Cat()
isinstance(garfield, object)
True
isinstance(Cat(), list)
False
isinstance(Cat, Animal)
False
isinstance(Cat, type)
True
isinstance(Cat, object)
True
isinstance(type, object)
True
isinstance(object, type)
True
isinstance(2, type)
False

issubclass

issubclass(Animal, Animal)
True
issubclass(Cat, Animal)
True
issubclass(list, Animal)
False

(Inheritance) Polymorphism

The goal of inheritance is also to treat several objects of different types in the same way. This is called polymorphism. An animal speaks whatever it is.

class Animal():
    lifepoints = 10
    
class Cat(Animal):
    def speak(self):
        print("miaou")

class Dog(Animal):
    def speak(self):
        print("waouf")

class Duck(Animal):
    def speak(self):
        print("coin")

L = [Cat(), Dog(), Cat(), Cat(), Duck()]

for animal in L:
    animal.speak()
miaou
waouf
miaou
miaou
coin
for animal in L:
    if animal.type == Cat:
        ...
    elif animal.type == Dog:
        ...

Duck typing

In Python, actually, we do not need inheritance in that case. As long an object looks like a duck, it is a duck. Here, as long an object can speak, it is an animal.

class Cat:
    def speak(self):
        print("miaou")

class Dog:
    def speak(self):
        print("waouf")

L = [Cat(), Dog(), Cat(), Cat()]

for animal in L:
    animal.speak()
miaou
waouf
miaou
miaou

Examples

Example of the number class hierarchy in Python

Here is the class hierarchy for the numbers in Python. See https://peps.python.org/pep-3141/ where you may read the rejected alternatives.

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([('Complex', 'Number'), ('complex', 'Complex'), ('Real', 'Complex'), ('float', 'Real'), ('Rational', 'Real'), ('Integral', 'Rational'), ('int', 'Integral'), ('bool', 'int')])
dot

svg

import numbers
isinstance(5, numbers.Number)
True

Example of datastructures used in Dijkstra's algorithm

Dijkstra's algorithm needs a graph and a priority queue. But the details on how the graph and the priority queue is implemented does not matter for Dijkstra's algorithm. Inheritance enables abstraction. The most obvious way is to declare interfaces:

  • an interface Graph and then we can for instance implement a grid graph stored as a PNG image file;
  • an interface PriorityQueue and then we could implement for instance a binary heap.
from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("PNGGridGraph", "Graph"), ("VoxelGraph", "Graph"), ("ExplicitGraph", "Graph")])
dot

svg

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("ArrayPriorityQueue", "PriorityQueue"), ("BinaryHeap", "PriorityQueue"), ("FibonacciHeap", "PriorityQueue")])
dot

svg

In a video game, we could imagine characters that are animals of different types.

Exercice

  • Write A* algorithm and adequate classes for representing a graph and a priority queue (e.g. a binary heap).

Overriding

Overriding (spécialisation or redéfinition in french) consists in redefining a method in a subclass.

class Animal:
    def speak(self):
        print("...")

class Cat(Animal):
    def speak(self):
        print("miaou")

a = Animal()
a.speak()

c = Cat()
c.speak()
...
miaou

Delegation to superclass methods

Problem

If we override a method, the previous one can a priori not be called anymore.

class Animal:
    def __init__(self):
        self.number_of_times_I_spoke = 0

    def speak(self):
        self.number_of_times_I_spoke += 1

class Cat(Animal):
    def speak(self):
        print("miaou")

a = Animal()
a.speak()
print(a.number_of_times_I_spoke)

c = Cat()
c.speak()
print(c.number_of_times_I_spoke)
1
miaou
0

(Not so great) solution

We can explicitely mention the class name Animal to have access to the method defined in Animal.

class Animal:
    def __init__(self):
        self.number_of_times_I_spoke = 0

    def speak(self):
        self.number_of_times_I_spoke += 1

class Cat(Animal):
    def speak(self):
        Animal.speak(self) # yes
        print("miaou")

a = Animal()
a.speak()
print(a.number_of_times_I_spoke)

c = Cat()
c.speak()
print(c.number_of_times_I_spoke)
1
miaou
1

Example with the initializer

class Animal:
    def __init__(self):
        self.health = 3

class Cat(Animal):
    def __init__(self):
        self.mustache = True

garfield = Cat()
garfield.health
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[4], line 10
      7         self.mustache = True
      9 garfield = Cat()
---> 10 garfield.health


AttributeError: 'Cat' object has no attribute 'health'

The solution is again to explicitely mention the class name Animal to have access to the method defined in Animal.

class Animal:
    def __init__(self):
        self.health = 3

class Cat(Animal):
    def __init__(self):
        Animal.__init__(self) # yes
        self.mustache = True

garfield = Cat()
garfield.health
3

The super function

Problem

Writing Animal.speak(self) is not so robust. It may fail if we add a new subclass etc.

Solution

The function super creates a wrapper object of self for accessing methods as we were in the next superclass of the current one.

class Animal:
    def __init__(self):
        self.number_of_times_I_spoke = 0

    def speak(self):
        self.number_of_times_I_spoke += 1

class Cat(Animal):
    def speak(self):
        super().speak() # yes
        print("miaou")

a = Animal()
a.speak()
print(a.number_of_times_I_spoke)

c = Cat()
c.speak()
print(c.number_of_times_I_spoke)
1
miaou
1
class Animal:
    def __init__(self):
        self.health = 3

class Cat(Animal):
    def __init__(self):
        super().__init__()
        self.mustache = True

garfield = Cat()
garfield.health
10

What is exactly this super()?

Actually, super() is syntaxic sugar for super(Cat, self). It gives a wrapper object of self which calls the methods as if we were in the next superclass of Cat with the very instance.

class Animal:
    def __init__(self):
        self.health = 3

class Cat(Animal):
    def __init__(self):
        super(Animal, self).__init__()
        self.mustache = True

garfield = Cat()
garfield.health
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[9], line 11
      8         self.mustache = True
     10 garfield = Cat()
---> 11 garfield.health


AttributeError: 'Cat' object has no attribute 'health'

More precisely, it is syntactic sugar for super(self.__class__, self) where __class__ is a magic method that returns the class of an object.

Quiz

class Animal:
    def speakTwice(self):
        self.speak()
        self.speak()

    def speak(self):
        pass

class Cat(Animal):
    def speak(self):
        print("miaou")
        
class Dog(Animal):
    def speak(self):
        print("waouf")
        
for a in [Cat(), Dog()]:
    a.speakTwice()
miaou
miaou
waouf
waouf
class Animal:
    def speakTwice(self):
        self.speak()
        self.speak()

    def speak(self):
        print("no sound")

class Cat(Animal):
    def speak(self):
        print("miaou")
        
class Dog(Animal):
    def speak(self):
        print("waouf")
        
for a in [Cat(), Dog()]:
    a.speakTwice()
miaou
miaou
waouf
waouf
class Animal:
    def speakTwice(self):
        self.speak()
        self.speak()

    def speak(self):
        print("no sound")

class Cat(Animal):
    def speak(self):
        print("miaou")
        
class Dog(Animal):
    def speak(self):
        super().speak()
        print("waouf")
        
for a in [Cat(), Dog()]:
    a.speakTwice()
miaou
miaou
no sound
waouf
no sound
waouf
class Animal:
    def __init__(self):
        self.sound = "no sound"
        
    def speakTwice(self):
        self.speak()
        self.speak()

    def speak(self):
        print(self.sound)

class Cat(Animal):
    def __init__(self):
        self.sound = "miaou"

        
class Dog(Animal):
    def __init__(self):
        self.sound = "waouf"

    def speak(self):
        super(Dog, self).speak()
        print("tss tss")
        
for a in [Cat(), Dog()]:
    a.speakTwice()
miaou
miaou
waouf
tss tss
waouf
tss tss
class Animal:
    def speakTwice(self):
        self.speak()
        self.speak()

    def speak(self):
        print("no sound")

class Cat(Animal):
    def speak(self):
        print("miaou")
        
class Dog(Animal):
    def speak(self):
        print("waouf")
        
for a in [Cat(), Dog()]:
    super(Cat, a).speak()
no sound



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[16], line 18
     15         print("waouf")
     17 for a in [Cat(), Dog()]:
---> 18     super(Cat, a).speak()


TypeError: super(type, obj): obj must be an instance or subtype of type
class Animal:
    def speakTwice(self):
        self.speak()
        self.speak()

    def speak(self):
        print("no sound")

class Cat(Animal):
    def speak(self):
        print("miaou")
        
class Dog(Animal):
    def speak(self):
        print("waouf")
        
for a in [Cat(), Cat()]:
    super(Cat, a).speak()
no sound
no sound
class Animal:
    def speakTwice(self):
        print(type(self))
        self.speak()
        self.speak()

    def speak(self):
        print("no sound")

class Cat(Animal):
    def speak(self):
        print("miaou")
        
class Dog(Animal):
    def speak(self):
        print("waouf")
        
for a in [Cat(), Cat()]:
    super(Cat, a).speakTwice()
<class '__main__.Cat'>
miaou
miaou
<class '__main__.Cat'>
miaou
miaou

Properties/methods on a class

Class variable

A class variable is a field/attribute of a class. The value is for the class itself, but can also be accessed via objects. This is called static field in JAVA/C++, but it is almost the same.

However, it can be modified from an object and the value is for that partcular object only. If modified for the class, then it is modified for the class (of course) but also for the objects of that class.

class Animal:
    nbLegs = 4

a = Animal()
b = Animal()
print(a.nbLegs, b.nbLegs, Animal.nbLegs)
Animal.nbLegs = 5
print(a.nbLegs, b.nbLegs, Animal.nbLegs)
a.nbLegs = 4
print(a.nbLegs, b.nbLegs, Animal.nbLegs)
Animal.nbLegs = 3
print(a.nbLegs, b.nbLegs, Animal.nbLegs)
4 4 4
5 5 5
4 5 5
4 3 3
class Animal:
    def __init__(self):
        self.nbLegs = 4

a = Animal()
print(a.nbLegs)
Animal.nbLegs
4



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[5], line 7
      5 a = Animal()
      6 print(a.nbLegs)
----> 7 Animal.nbLegs


AttributeError: type object 'Animal' has no attribute 'nbLegs'

Quiz

What is the answer of that program?

class A:
    list = []

a = A()
b = A()
a.list.append(1)
b.list
[1]
class A:
    def __init__(self):
        self.list = []

a = A()
b = A()
a.list.append(1)
b.list
[]

Static methods

Static methods are functions declared inside a class. They behave like functions (no special pameters like self). This is usually used group functions under a class name. It used for utilility functions.

class Calculator:
    @staticmethod
    def sum(x, y):
        return x + y
    
Calculator.sum(1, 2)
3
class Animal:
    nbLegs = 4

    @staticmethod
    def nbLegsForHello(proportion):
        return Animal.nbLegs * proportion
    
    def speak(self):
        print(f"I have {self.nbLegsForHello(2)}")

print(Animal.nbLegsForHello(0.5))

a = Animal()
print(a.nbLegsForHello(0.5))
a.speak()
2.0
2.0
I have 8

Class methods

A class method is a method that is attached to the class and takes the class itself as the first argument. Usually, they are used for providing alternative constructors.

class Cat():
    def __init__(self, catAge):
        self.age = catAge

    @classmethod
    def createByEqHumanAge(cls, humanAge):
        return cls(humanAge / 7)
    
garfield = Cat.createByEqHumanAge(21)
garfield.age
3.0

Class methods have nice behavior when inheritance is involved.

class Cat():
    def __init__(self, catAge):
        self.age = catAge

    def speak(self):
        print("classical miaou")

    @classmethod
    def createByEqHumanAge(cls, humanAge):
        print(cls)
        return cls(humanAge / 7)
    
class AngoraCat(Cat):
    def __init__(self, catAge):
        print("feu d'artifice")
        self.age = catAge

    def speak(self):
        print("mIaOu")

a = AngoraCat.createByEqHumanAge(21)
print(a.age)
a.speak()

<class '__main__.AngoraCat'>
feu d'artifice
3.0
mIaOu





type

Python object model

Everything is an object

In many languages, and also Python, everything is an instance of the class object. None is an object, a number is an object, a function is an object etc. In other words, we have a superclass called object and any class (implicitely) inherits from that superclass object.

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([(X,'object') for X in ["NoneType", "numbers.Number", "types.FunctionType"]])

dot

svg

  • In Java, also every class is a subclass of Java.lang.Object. However, a class, e.g. Animal itself is not an object but Animal.class is and inherits from java.lang.Class.
  • In Python, the class Animal itself is an object too.
  • In C++, there is no superclass.
dir(object)
['__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']
type(1)
int
type(int)
type

True

Number class hierarchy in Python

Here is the class hierarchy for the numbers in Python. See https://peps.python.org/pep-3141/ where you may read the rejected alternatives.

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([('Complex', 'Number'), ('complex', 'Complex'), ('Real', 'Complex'), ('float', 'Real'), ('Rational', 'Real'), ('Integral', 'Rational'), ('int', 'Integral'), ('bool', 'int')])
dot

svg

from numbers import Complex 
dir(Complex)
['__abs__',
 '__abstractmethods__',
 '__add__',
 '__bool__',
 '__class__',
 '__complex__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmul__',
 '__rpow__',
 '__rsub__',
 '__rtruediv__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '_abc_impl',
 'conjugate',
 'imag',
 'real']
import numbers
isinstance(5, numbers.Number)
True

Class hierarchy for Collections in Python

Here is the class hierarchy for collections in Python.

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("Collection", "Container"),
           ("Collection", "Iterable"),
           ("Collection", "Sized"),
           ("Sequence", "Collection"),
           ("Set", "Collection"),
           ("Mapping", "Collection"),
           ("MutableSequence", "Sequence"),
           ("MutableSet", "Set"),
           ("MutableMapping", "Mapping"),
           ("tuple", "Sequence"),
           ("str", "Sequence"),
           ("bytes", "Sequence"),
           ("list", "MutableSequence"),
           ("bytearray", "MutableSequence"),
           ("frozenset", "Set"),
           ("set", "MutableSet"),
           ("dict", "MutableMapping")])

dot

svg

In Java, https://docs.oracle.com/javase/8/docs/api/java/lang/package-tree.html

from collections import Sized
dir(Sized)
/tmp/ipykernel_646363/3389889515.py:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
  from collections import Sized





['__abstractmethods__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__len__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 '_abc_impl']
class Farm:
    def __len__(self):
        return 4
    
f = Farm()
len(f)
4
from collections import Sized

class Farm(Sized):
    def __len__(self):
        return 4
    
f: Sized = Farm()
len(f)
4

Exceptions

The language C does not provide any mechanism for handling errors. That is a pity because in C, we must always handle error propagation by writing functions that returns 0 in general, and a value different from 0 in case of an error. The code becomes horrible.

We also have nasty setjmp and longjmp which are dangerous!. Many languages, like C++, Python, Javascript, Java, C# etc. offer exceptions to handle errors. They work like setjmp and longjmp, but they take care about the memory.

3/0
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

Cell In[1], line 1
----> 1 3/0


ZeroDivisionError: division by zero
import logging

y = 0
try:
    x = 3/y
except ZeroDivisionError:
    logging.error("You are crazy to try a division by zero!")
ERROR:root:You are crazy to try a division by zero!

This is particularly useful when the error occurs deep in the program.

def division(x, y):
    return x/y

def f(x, y):
    return division(x, y) + 1

try:
    print(f(3, 0))
except ZeroDivisionError:
    print("You are crazy to try a division by zero!")
You are crazy to try a division by zero!

Syntax of try-except-else-finally

try:
    print("write here the code what may cause an exception")
except ZeroDivisionError:
    print("write here what to do in case of a division by zero")
except AssertionError:
    print("write here what to do in case of an assert condition was false")
else:
    print("write here some code to execute when no error occurred")
finally:
    print("write here code which is always executed (error or not)")
write here the code what may cause an exception
write here some code to execute when no error occurred
write here code which is always executed (error or not)
def f():
    try:
        x = 3/0
    except ZeroDivisionError:
        print("write here what to do in case of a division by zero")
        return
    except AssertionError:
        print("write here what to do in case of an assert condition was false")
    else:
        print("write here some code to execute when no error occurred")
    finally:
        print("write here code which is always executed (error or not)")

f()
write here what to do in case of a division by zero

Hierarchy of exceptions

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("Exception", "BaseException"),
           ("KeyboardInterrupt", "BaseException"),
           ("SystemExit", "BaseException"),
           ("ZeroDivisionError", "Exception"),
           ("AssertionError", "Exception"),
           ("ValueError", "Exception"),
           ])
dot

svg

try:
    3/0
except BaseException:
    print("All base exceptions are handled")
NEVER catch a BaseException. The program should STOP in that case

All exceptions are BaseException. Normally, you do not catch KeyboardInterrupt, SystemExit because you prevent your program from stopping if the user wants so!

The following code is bad:

try:
    print("my program")
except BaseException:
    print("NEVER catch a BaseException. The program should STOP in that case")
try:
    print("my program")
except KeyboardInterrupt:
    print("NEVER catch a BaseException. The program should STOP in that case")

However, you can catch exceptions that inherits from Exception

User-defined exceptions

You can define your own type of exceptions.

class DenominatorZeroError(Exception):
    pass

def f(x,y):
    if y == 0:
        raise DenominatorZeroError
    return x/y

try:
    d = f(3, 0)
except DenominatorZeroError:
    print("Denominator should be non-zero!")

Denominator should be non-zero!
class DenominatorZeroError(Exception):
    pass

def g(x):
    return DenominatorZeroError if x == 0 else ValueError

def f(x,y):
    if y == 0:
        raise g(0)
    return x/y

try:
    d = f(3, 0)
except DenominatorZeroError:
    print("Denominator should be non-zero!")

Denominator should be non-zero!
class OddNumberOfVertexError(Exception):
    def __init__(self, nbVertices):
        super().__init__(f"should give a graph with an even number of vertices: {nbVertices} given")

def computePerfectMatching(G):
    if G["nbVertices"] % 2 != 0:
        raise OddNumberOfVertexError(G["nbVertices"])


try:
    computePerfectMatching({"nbVertices": 3})
except OddNumberOfVertexError as e:
    print(e)


should give a graph with an even number of vertices: 3 given

Ressource management

Bad practice

In Python, it is highly not-recommended to write:

for line in open('example.txt'): # bad
    ...
---------------------------------------------------------------------------

FileNotFoundError                         Traceback (most recent call last)

Cell In[14], line 1
----> 1 for line in open('example.txt'): # bad
      2     ...


File /usr/local/lib/python3.9/site-packages/IPython/core/interactiveshell.py:310, in _modified_open(file, *args, **kwargs)
    303 if file in {0, 1, 2}:
    304     raise ValueError(
    305         f"IPython won't let you open fd={file} by default "
    306         "as it is likely to crash IPython. If you know what you are doing, "
    307         "you can use builtins' open."
    308     )
--> 310 return io_open(file, *args, **kwargs)


FileNotFoundError: [Errno 2] No such file or directory: 'example.txt'

The issue is that... we do not close the file! Also do not write (even many websites say it):

f = open("example.txt")
print(file.read()) 
file.close()

In case of an error in the treatment, the file may not be closed.

Solution

Instead we should a construction which has the following form:

file = EXPR
file.__enter__()
try:
    BLOCK
finally:
    file.__exit__()

This is cumbersome. So thanks to https://peps.python.org/pep-0343/ we have a with-as construction:

with EXPR as file:
    BLOCK
with open('example.txt') as file:
    content = file.read()
---------------------------------------------------------------------------

FileNotFoundError                         Traceback (most recent call last)

Cell In[1], line 1
----> 1 with open('example.txt') as file:
      2     content = file.read()


File /usr/local/lib/python3.9/site-packages/IPython/core/interactiveshell.py:310, in _modified_open(file, *args, **kwargs)
    303 if file in {0, 1, 2}:
    304     raise ValueError(
    305         f"IPython won't let you open fd={file} by default "
    306         "as it is likely to crash IPython. If you know what you are doing, "
    307         "you can use builtins' open."
    308     )
--> 310 return io_open(file, *args, **kwargs)


FileNotFoundError: [Errno 2] No such file or directory: 'example.txt'

We can also use several ressources as follows.

with open('input.txt') as fInput, open("output.txt", "w") as fOutput:
    for line in fInput:
        fOutput.write(line)

SOLID principles

SOLID is an acronym for the five object-oriented design (OOD) principles by Robert C. Martin (also known as Uncle Bob). They provide a guide to writing maintainable, scalable, and robust software. They are high-level principles.

Single responsibility principle

A class should have a single responsibility.

Bad example

class Hero:
    lp = 50
    x = 2
    y = 3
    vx = 0
    vy = 0
    ax = 0
    ay = 0


Drawbacks:

  • The class is huge so difficult to understand
  • Difficult to test: any modification of the class would need to test the whole class again.
  • Difficult to extend/modify.

Good

  • Seperate the responsabilities into different classes
class Life:
    lp = 50

    def is_dead(self):
        return self.lp <= 0

class PhysicalPoint:
    x = 2
    y = 3
    vx = 0
    vy = 0
    ax = 0
    ay = 0

class Hero:
    def __init__(self):
        self.life = Life
        self.point = PhysicalPoint()

Open/closed principle

Prefer extensions via adding new classes than modification of existing classes.

Bad example

It is bad to modify existing classes, add/remove fields, modify methods etc.

Drawbacks:

  • Need for retest code that was already tested
  • Need to check that the modified class is still usable from other classes

For example, suppose you want to add a new type of ennemy would need to modify existing code.

def get_damage(enemy):
    if enemy.type == DRAGON:
        return enemy.force + 3
    elif enemy.type == WIZARD:
        return enemy.magic ** 2
    elif enemy.type == HUMAN:
        return 2
    else:
        raise NotImplementedError

Good example

  • do new classes that inherits from abstract classes, assign a behavior to a class by modifying a field/method which is abstract

class Enemy:
    def get_damage(self):
        raise NotImplementedError
    
class Dragon(Enemy):
    def get_damage(self):
        return self.force + 3
    
class Wizard(Enemy):
    def get_damage(self):
        return self.magic ** 2

class Human(Enemy):
    def get_damage(self):
        return 2
    
class SuperHuman(Enemy):
    def get_damage(self):
        return 10000

Liskov substitution principle

Barbara Liskov - 2008 Turing Award

Wrong example

Suppose there exists a class Square. We might be tempted to create a class Rectangle as a subclass of Square because we reuse the field width, and just add a field height.

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("Rectangle", "Square")])
dot

svg

from dataclasses import dataclass

@dataclass
class Square():
    width: float

    def area(self):
        return self.width * self.width
    
@dataclass
class Rectangle(Square):
    height: float

    def area(self):
        return self.width * self.height
    
L = [Square(2), Rectangle(2, 3)]

for shape in L:
    print(shape.area())
4
6

This causes problems. Indeed, the developer reads "Any rectangle is a square" which is false. Some invariants written in the class Square may no longer be true for a Rectangle.

Solution

Please do:

from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("Square", "Rectangle")])
dot

svg

from dataclasses import dataclass

@dataclass
class Rectangle():
    width: float
    height: float


    def area(self):
        """_summary_

        Returns:
            _type_: _description_
        """
        return self.width * self.height
    
class Square(Rectangle):
    def __init__(self, width):
        super().__init__(width, width)

    
L = [Square(2), Rectangle(2, 3)]

for shape in L:
    print(shape.area())
4
6

Interface segregation principle

Clients should not be forced to depend on methods that they do not use.

  • Build small devoted abstract classes specific to some clients

Instead of implementing Dijkstra's algorithm that takes a GridGraph, please implement it that takes an abstract Graph. Dijkstra's algorithm does not care about method like G.grid_width or G.grid_height.

Dependency inversion principle

Please always depend on abstraction, not on concrete classes.

Bad example

For instance, the class Hero here depends on the class Sound. The class Sound is specific and you create an object of type Sound.

class Hero:
    def __init__(self):
        self.soundAttack = Sound("attack.ogg")
    def attack(self):
        self.soundAttack.play()

Drawbacks

  • If the implementation if Sound changes (for instance, it does not take .ogg files anymore but .wav)
  • handling deaf persons would require to traverse all the classes using Sound and make the modification

Solution

Decide an abstract interface for the game first and stick to it (except if it is bad!).

class IGame:
    sound: ISound
    graphics: IGraphics

class Hero:
    def __init__(self, game: IGame):
        self.game = game

    def attack(self):
        self.game.sound.play("attack")

Advantages:

  • Robust to the change of a library/framework. If the implemntation of the very concrete Sound class changes, we just change the implementation of ISound. The modification are confined.
  • Robust for adding new feature. Suppose you want to be inclusive and target deaf persons. Just add a new subclass to ISound that also displays a subtitle.

Iterable objects and iterators

Motivation

The objective is to write more elegant code that the for(int i = 0; i < n; i++) style of code. For instance, the following code is ugly.

A = [1, 7, 9, 3, 4]
for i in range(len(A)):
    print(f"We have {A[i]} in the array")
On a 1 dans le tableau
On a 7 dans le tableau
On a 9 dans le tableau
On a 3 dans le tableau
On a 4 dans le tableau

Instead, write this:

A = [1, 7, 9, 3, 4]
for el in A:
    print(f"We have {el} in the array")
We have 1 in the array
We have 7 in the array
We have 9 in the array
We have 3 in the array
We have 4 in the array

Definition

Iterators

An iterator is an object that gives you elements when pressing the next button. Like a coffee machine or a food dispenser:

Iterable objects

An iterable object is an object that can be transformed into an iterator. In for x in BLOUP, BLOUP should be an iterable object.

List

[1, 6, 2]
[1, 6, 2]

In Python:

  • An iterable object can be transformed into an interator via __iter__()
  • An iterator is an object implementing __next__().
it = [1, 6, 2].__iter__()
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__()) # raise a StopIteration error
1
6
2



---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

Cell In[12], line 5
      3 print(it.__next__())
      4 print(it.__next__())
----> 5 print(it.__next__()) # raise a StopIteration error


StopIteration: 

Range

range(4)
range(0, 4)
for i in range(4):
    print(i)
0
1
2
3
it = range(4).__iter__()
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())  # raise a StopIteration error
0
1
2
3



---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

Cell In[17], line 6
      4 print(it.__next__())
      5 print(it.__next__())
----> 6 print(it.__next__())  # raise a StopIteration error


StopIteration: 

Many iterable objects and iterators are lazy data structures. For instance, range(100000000) will not create a list of 100000000 elements! It is inspired from constructions in Haskell.

Generators

A generator is a special kind of function that contains the keyword yield. It returns an iterable object.

def squares(start, stop):
    for i in range(start, stop):
        yield i * i
it = squares(1, 5).__iter__()
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())
1
4
9
16



---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

Cell In[10], line 6
      4 print(it.__next__())
      5 print(it.__next__())
----> 6 print(it.__next__())


StopIteration: 

slots and dict


for i in squares(1, 6):
    print(i)
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

Cell In[1], line 1
----> 1 for i in squares(1, 6):
      2     print(i)


NameError: name 'squares' is not defined
def all_integers():
    i = 0
    while True:
        yield i
        i += 1
N = all_integers()
iter = N.__iter__()
print(next(iter))
print(next(iter))
0
1

Generators can also be defined with a functional construction, using parenthesis.

all_squares = (a ** 2 for a in all_integers())
N = all_squares
iter = N.__iter__()
print(next(iter))
print(next(iter))
print(next(iter))
print(next(iter))
0
1
4
9

Nest generators

Generators can be nested but not in naive way:

def f():
    yield 1
    yield 2

def g():
    f()
    yield 3

for i in g():
    print(i)
3

You may write:

def f():
    yield 1
    yield 2

def g():
    for j in f():
        yield j
    yield 3

for i in g():
    print(i)
1
2
3

Or more elegantly:

def f():
    yield 1
    yield 2

def g():
    yield from f()
    yield 3

for i in g():
    print(i)
1
2
3

Converting iterable objects into whatever you want

list(ITERABLE) takes the iterable, get an iterator, and then press the next button until no more food gets out. All the items are then collected into a list.

list((1, 2, 3))
[1, 2, 3]
list(squares(1, 6))
[1, 4, 9, 16, 25]
list(all_integers())
---------------------------------------------------------------------------

KeyboardInterrupt                         Traceback (most recent call last)

Cell In[20], line 1
----> 1 list(all_integers())


Cell In[19], line 4, in all_integers()
      2 i = 0
      3 while True:
----> 4     yield i
      5     i += 1


KeyboardInterrupt: 
list({"a": 0, "b": 1})
['a', 'b']
set((1, 2, 3))
{1, 2, 3}
frozenset([1, 2, 3])
frozenset({1, 2, 3})
tuple([1,2, 3])
(1, 2, 3)

Enumerate

Sometimes you need the index and the element and you are stuck with: The enumerate function transforms any iterable object into a new iterable object that incorporates a counter.

fruits = ['banana', 'kiwi', 'strawberry']
for fruit in fruits:
    print(f"Fruit number ?? is a {fruits[i]}")
le fruit numéro ?? est une pomme
le fruit numéro ?? est une banane
le fruit numéro ?? est une orange

So you are lazy and you write:

fruits = ['banana', 'kiwi', 'strawberry']
for i in range(len(fruits)):
    fruit = fruits[i]
    print(f"Fruit number {i+1} is a {fruits[i]}")
le fruit numéro 1 est une pomme
le fruit numéro 2 est une banane
le fruit numéro 3 est une orange

But you can use enumerate that creates an interable object.

fruits = ['banana', 'kiwi', 'strawberry']
enumerate([fruits], start=1)
<enumerate at 0x7f87a626d200>
fruits = ['banana', 'kiwi', 'strawberry']

for i, fruit in enumerate(fruits, start=1):
    print(f"Fruit number {i+1} is a {fruit}")

1 pomme
2 banane
3 orange

Zip

Zip consists in transforming several iterables into a single one.

In order to see the result, we put list(....) to transform the produced iterable into a list.

fruits = ['banana', 'kiwi', 'strawberry']
prices = [2, 4, 8]

for i in len(fruits):
    print(f"{fruits[i]} and costs {prices[i]}")

The fruits[i] and prices[i] are ugly. The solution is to zip fruits and prices.

fruits = ['banana', 'kiwi', 'strawberry']
prices = [2, 4, 8]

for (fruit, price) in zip(fruits, prices):
    print(f"{fruit} and costs {price}")
banana and costs 2
kiwi and costs 4
strawberry and costs 8
fruits = ['banana', 'kiwi', 'strawberry']
prices = [2, 4, 8]

for i, (fruit, price) in enumerate(zip(fruits, prices), start=1):
    print(f"{i}: {fruit} and costs {price}")
1: banana and costs 2
2: kiwi and costs 4
3: strawberry and costs 8
zip(["a", "b", "c", "d"], (1, 2, 3, 4))
<zip at 0x7fda498d3b40>
list(zip(["a", "b", "c", "d"], (1, 2, 3, 4)))
[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
zip(["a", "b", "c", "d"], (1, 2, 3, 4), ("alpha","beta", "gamma", "delta"))
<zip at 0x7fda498c3e40>

zip peut être défini comme ça :

def myZip(A, B):
    map(lambda x, y: (x, y), A, B)
list(zip(["a", "b", "c", "d"], (1, 2, 3, 4), ("alpha","beta", "gamma", "delta")))
[('a', 1, 'alpha'), ('b', 2, 'beta'), ('c', 3, 'gamma'), ('d', 4, 'delta')]
from itertools import zip_longest
list(zip_longest(["a", "b", "c"],(1, 2, 3, 4), ("alpha","beta", "gamma", "delta"), fillvalue=0))
[('a', 1, 'alpha'), ('b', 2, 'beta'), ('c', 3, 'gamma'), (0, 4, 'delta')]

Dictionnaries

costs = {"banana": 2, "kiwi": 4, "straberry": 8}
for key in costs.keys():
    print(f"{key} costs {costs[key]}")
banana costs 2
kiwi costs 4
straberry costs 8
costs = {"banana": 2, "kiwi": 4, "straberry": 8}
for key in costs:
    print(f"{key} costs {costs[key]}")
banana costs 2
kiwi costs 4
straberry costs 8
costs = {"banana": 2, "kiwi": 4, "straberry": 8}
for key, cost in costs.items():
    print(f"{key} costs {cost}")
banana costs 2
kiwi costs 4
straberry costs 8

Modifing a dictionnary during a loop

costs = {"banana": 2, "kiwi": 4, "straberry": 8}
for key in costs:
    costs[f"{key}'"] = costs[key]*2

print(costs)

---------------------------------------------------------------------------

RuntimeError                              Traceback (most recent call last)

Cell In[39], line 2
      1 cost = {"banana": 2, "kiwi": 4, "straberry": 8}
----> 2 for key in cost:
      3     cost[f"{key}'"] = cost[key]*2
      5 print(cost)


RuntimeError: dictionary changed size during iteration
costs = {"banana": 2, "kiwi": 4, "straberry": 8}
for key in list(costs):
    costs[f"{key}'"] = costs[key]*2
    
print(costs)
{'banana': 2, 'kiwi': 4, 'straberry': 8, "banana'": 4, "kiwi'": 8, "straberry'": 16}

Counting

import itertools
itertools.count(start=10, step=2)
count(10, 2)
import itertools
itertools.cycle(["player1", "player2"])
<itertools.cycle at 0x7fda4a716d00>
import itertools
itertools.repeat("X", 4)

Permutations

itertools.permutations(ITERABLE, k) returns an iterable object that enumerates all k-length tuples which are all possible orderings with no-repeated elements.

import itertools
list(itertools.permutations(range(4), 2))
[(0, 1),
 (0, 2),
 (0, 3),
 (1, 0),
 (1, 2),
 (1, 3),
 (2, 0),
 (2, 1),
 (2, 3),
 (3, 0),
 (3, 1),
 (3, 2)]
import itertools
list(itertools.permutations(range(4), 4))
[(0, 1, 2, 3),
 (0, 1, 3, 2),
 (0, 2, 1, 3),
 (0, 2, 3, 1),
 (0, 3, 1, 2),
 (0, 3, 2, 1),
 (1, 0, 2, 3),
 (1, 0, 3, 2),
 (1, 2, 0, 3),
 (1, 2, 3, 0),
 (1, 3, 0, 2),
 (1, 3, 2, 0),
 (2, 0, 1, 3),
 (2, 0, 3, 1),
 (2, 1, 0, 3),
 (2, 1, 3, 0),
 (2, 3, 0, 1),
 (2, 3, 1, 0),
 (3, 0, 1, 2),
 (3, 0, 2, 1),
 (3, 1, 0, 2),
 (3, 1, 2, 0),
 (3, 2, 0, 1),
 (3, 2, 1, 0)]

The following program computes all $n \times n$ chess configurations of $n$ non-attacking queens.

from itertools import permutations

"""
We represent a board of n-queens by an array board such that 
board[r] is the column index such that there is a queen at row r and column board[r]
"""

def printBoard(board):
  """
  print a board in the terminal
  """
  n = len(board)
  print('_' * (n+2))
  for r in board:
    print('|' + ' ' * r + '♛' + ' ' * (n-r-1) + '|')
  print('\n')
 
def nqueenboards(n):
  """
  A generator that generates all configurations of non-attacking n queens on a n*n boards
  """
  columns = range(n)
  for board in permutations(columns):
    s1 = set(board[i] + i for i in columns)
    s2 = set(board[i] - i for i in columns)
    if n == len(s1) == len(s2):
      yield board

def printAllBoards(N):
  for board in nqueenboards(N):
    printBoard(board)

printAllBoards(5)

_______
|♛    |
|  ♛  |
|    ♛|
| ♛   |
|   ♛ |


_______
|♛    |
|   ♛ |
| ♛   |
|    ♛|
|  ♛  |


_______
| ♛   |
|   ♛ |
|♛    |
|  ♛  |
|    ♛|


_______
| ♛   |
|    ♛|
|  ♛  |
|♛    |
|   ♛ |


_______
|  ♛  |
|♛    |
|   ♛ |
| ♛   |
|    ♛|


_______
|  ♛  |
|    ♛|
| ♛   |
|   ♛ |
|♛    |


_______
|   ♛ |
|♛    |
|  ♛  |
|    ♛|
| ♛   |


_______
|   ♛ |
| ♛   |
|    ♛|
|  ♛  |
|♛    |


_______
|    ♛|
| ♛   |
|   ♛ |
|♛    |
|  ♛  |


_______
|    ♛|
|  ♛  |
|♛    |
|   ♛ |
| ♛   |

Cartesian product

import itertools
list(itertools.product(["a", "b", "c"], [1, 2, 3, 4]))
[('a', 1),
 ('a', 2),
 ('a', 3),
 ('a', 4),
 ('b', 1),
 ('b', 2),
 ('b', 3),
 ('b', 4),
 ('c', 1),
 ('c', 2),
 ('c', 3),
 ('c', 4)]

Références

En C pour faire un swap, on écrit

void swap(int* p, int* q) {
    int tmp = *p;
    *p = *q;
    *q = tmp;
}

int x, y;
swap(&x, &y)

Le C++ veut sortir de l'esprit "pointeur". Le soucis des pointeurs c'est :

  • c'est moche *p + *q au lieu du naturel x + y
  • on peut modifier p et *p donc trop de bugs

Pour cela, il propose les références.

Définition d'une référence

Une référence est un nouveau nom pour désigner un objet existant.

void swap(int& i, int& j)
{
    int tmp = i;
    i = j;
    j = tmp;
}

int x, y;
swap(x, y);

L'implémentation derrière la scène est la même que les pointeurs. Mais c'est une mauvaise vision.

Voir aussi

[https://isocpp.org/wiki/faq/references]

Move semantics

En C++, les objets sont copiés par défaut. Et c'est très bien car ça évite les effets de bords.

string s("Bonjour");
vector<string> V;
V.push_back(s);
... on continue à utiliser
s[1] = "c"; // s est modifé mais pas l'élément dans V

Parfois copier c'est débile

V.push_back(getStringFromClient())
  • getStringFromClient() renvoie un objet string que l'on note A (il n'y a pas de variable A dans le programme)
  • On crée une copie A' que l'on donne à push_back
  • Puis A est supprimé

C'est dommage de copier A pour le supprimer juste après.

Idée générale de la move semantics

Depuis C++11, il y a la move semantics. En reprenant l'exemple précédent, voici comment cela fonctionne :

  • getStringFromClient() renvoie un objet string que l'on note A
  • On crée A' qui reçoit les données de A
  • On met A dans un état de sorte de coquille vide
  • On donne A' à push_back
  • A est supprimé

std::move

Voici des situations où C++ fait une copie alors qu'un move aurait été plus efficace, mais le compilateur ne peut pas le deviner.

Exemples

  • Situation où on a un nom car on utilise l'objet plusieurs fois :

      {
          string s("Bloup");
    
          V.push_back(s);
          V.push_back(s); // là on aurait du faire move semantics car s est de toute façon détruit après
      }
    
  • Situation où on a un paramètre

      void reinit(string& s) {
          history.push_back(s); // là on aimerait avoir move semantics car s n'est plus utilisé après
          s = getDefaultValue();
      }
    

Solution

La solution est d'utiliser std::move, que l'on note ici move :

    {
        string s("Bloup");

        V.push_back(s);
        V.push_back(move(s)); // :)
    }

et

    void reinit(string& s) {
        history.push_back(move(s)); // :)
        s = getDefaultValue();
    }

Recap

move(s) signifie :

  • s n'est plus utile ici

  • tu me déplaces au lieu de me copier

  • après l'appel s est toujours un objet valide mais sa valeur est quelconque (souvent vide a priori). On peut réutiliser la variable s.

      void swap(string& a, string& b) {
          string tmp(move(a));
          a = move(b);
          b = move(tmp);
      }
    

En fait, move(s) c'est équivalent à static_cast<string&&>(y).

Implémentation côté vector

template <typename T>
class vector {
    public:

        //copy elem into the vector
        void push_back(const T& elem);

        //**move** elem into the vector
        void push_back(T&& elem);

}
  • void push_back(const T& elem) is called in old version of C++ ;) or in C++11 when there is a variable name in the call (e.g. V.push_back(s))
  • In C++11, void push_back(T&& elem) is called when there is no name (e.g. V.push_back(getName())) or it is explicitely a moved object (e.g. V.push_back(move(s))).

Implémentation côté string

class string {
    private:
        int len;
        char* data;

    public:
        // copy constructor
        string(const string& a) : len(s.len) {
            data = new char(len+1);
            memcpy(data, s.data, len+1);
        }

        //move constructor
        string(string&& s) : len(s.len), data(s.data) {
            s.data = nullptr;
            s.len = 0;
        }

}

Pour aller plus loin

Template

Héritage

Exemple de déclaration

    #include <iostream>
    #include <vector>

    using namespace std;

    class Animal
    {
    public:
        void parler()
        {
            cout << "je sais pas parler car je suis un animal générique" << endl;
        }
    };

    class Chat : public Animal
    {
    private:
        int i = 0;
    public:
        void parler()
        {
            cout << "miaou" << i << endl;
            i++;
        }
    };

    class Crapaud : public Animal
    {
    public:
        void parler()
        {
            cout << "crooaaaa" << endl;
        }
    };

Programme qui ne fonctionne pas

    int main()
    {
        vector<Animal> S;

        S.push_back(Chat());
        S.push_back(Crapaud());

        for (Animal &a : S)
        {
            a.parler();
        }
    }

Programme qui fonctionne mais bon...

int main()
{
    vector<Animal*> S;

    S.push_back(new Chat());
    S.push_back(new Crapaud());

    for (Animal*& a : S)
    {
        a->parler();
    }
}

Programme chouette

#include <memory.h>

int main()
{
    vector< shared_ptr<Animal> > S;

    S.push_back(shared_ptr<Animal>(new Chat()));
    S.push_back(shared_ptr<Animal>(new Crapaud()));

    for (shared_ptr<Animal>& a : S)
    {
        a->parler();
    }
}

Rust

Rust est un langage bas-niveau comme C, mais plus sûr et qui permet la programmation générique.

Immutabilité par défaut

#![allow(unused)]
fn main() {
    let x = 1;
    x = 2; // erreur
}
#![allow(unused)]
fn main() {
    let mut x = 1;
    x = 2; //ok
}

Types

Les types sont inférés. Pas obligé de tout dire.

#![allow(unused)]
fn main() {
    let x = 1;
}

Mais on peut aussi dire :

#![allow(unused)]
fn main() {
    let x: i32 = 1;
}

Contrairement au C, les noms des types sont clairs.

  • Qu'est ce que i8 ? Un entier sur 8 bits. Un char en C.

  • Qu'est ce que u8 ? Un entier non signé sur 8 bits. Un unsigned char en C.

  • Qu'est ce que f64 ? Un flottant sur 64 bits. Un double en C.

Fonctions

#![allow(unused)]
fn main() {
fn addition(a: i32, b: i32) -> i32 {
    return a + b;
}

println!("{:?}", addition(1, 2));
}

Slices

Un slice un morceau de la mémoire. En gros, c'est un pointeur sur une zone mémoire et une taille.

#![allow(unused)]
fn main() {
let A = [0, 1, 2];
println!("{:?}", A);
let slice = &A[1..];
println!("{:?}", slice);
}

Le & veut dire qu'on met l'adresse mémoire (et la taille) dans slice.

TODO: pourquoi on a pas besoin de déréfencer à la ligne 4 ?

Les conditionnelles sont des expressions

#![allow(unused)]
fn main() {
    println!("{:?}", if 1 < 2 {3} else {4});
}

Pattern-matching

Le pattern maching ou filtrage par motif permet de faire une conditionnelle sur expression.

Mais attention ce code ne marche pas :

#![allow(unused)]
fn main() {
let s = "hello";

match s {
    "bonjour" => {println!("tu parles français")}
    "hello" => {println!("tu parles anglais")}
    "guten tag" => {println!("tu parles allemand")}
}
}

Pourquoi ne fonctionne-t-il pas d'après vous ? Car Rust est "sûr". Il veut être que vous traitiez tous les cas.

On peut écrire :

#![allow(unused)]
fn main() {
let s = "hello";

match s {
    "bonjour" => {println!("tu parles français")}
    "hello" => {println!("tu parles anglais")}
    "guten tag" => {println!("tu parles allemand")}
    _ => {println!("tu dis n'importe quoi")}
}
}
#![allow(unused)]
fn main() {
let age: u8 = 25;

match age {
    0..=3 => println!("bébé"),
    4..=18 => println!("mineur"),
    19.. => println!("grande personne de {:?} ans", age)
}
}

Boucles

#![allow(unused)]
fn main() {
let mut i: i32 = 0;
while i < 10 {
    println!("coucou !");
    i += 1;
}
}

Les boucles for s'appuie sur des objets (comme 0..4) qui peuvent être converti en itérateur :

#![allow(unused)]
fn main() {
for i in 0..4 {
    println!("{}", i);
}
}

Enum

enum Item {
    Book(f64, String, i64),
    Mug(f64, String),
    Shirt(f64, String, i32, i32)
}

fn main() {
    let item = Item::Shirt(10.0, "miaou".to_string(), 0, 2);

    match item {
        Item::Book(price, title, nb_pages) => &println!("The book {} is available!", title),
        Item::Mug(price, design) => &println!("Mug with {} available!", design),
        Item::Shirt(price, design, color, size) => &println!("Shirt with {} of size {} available!", design, size),
    };
}

Dans un jeu vidéo au tour par tour, on peut représenter une action avec un enum :

#![allow(unused)]
fn main() {
enum Action {
    Bouger(piece: Piece, nouvelle_position: Position),
    Passer
}
}

Les structures en Rust

Des structures tuple

#![allow(unused)]
fn main() {
struct EntierASelectionner(i32, bool);
let i = EntierASelectionner(5, false);
}

Des structures classiques

#![allow(unused)]
fn main() {
struct Point {
    x: i32,
    y: i32
}

let p = Point {x: 5, y: 2};
}

Accès aux champs

#![allow(unused)]
fn main() {
struct Point {
    x: i32,
    y: i32
}
let p = Point {x: 5, y: 2};
println!("{}", p.x);
}

Méthodes

#![allow(unused)]
fn main() {
struct Point {
    x: i32,
    y: i32
}


impl Point {
    fn unitX() -> Point {
        return Point(1, 0)
    }
    fn norm2(&self) -> i32 {
        return self.x ** 2 + self.y ** 2;
    }
}

let u = Point::unitX();
let p = Point {x: 5, y: 2};
println!("{}", p.norm2());
}

Erreurs en Rust

Rappel en C

#include <stdio.h>

int main() {
    FILE *f = fopen("fichier.txt","rt");

    if(f == NULL) {
        /** gestion de l'erreur mais
        si tu veux faire n'importe
        quoi et utiliser f en essayant
        de lire le fichier car tu es bornée tu peux **/
    }
    ...
}

Python

    x = 0

En Rust

En Rust, File::open("fichier.txt") renvoie une valeur de type Result qui est un enum comme ça :

#![allow(unused)]
fn main() {
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

T est le type de l'objet attendu (ici un fichier) et E est le type de l'erreur.

Ainsi on fait du pattern matching :

#![allow(unused)]
fn main() {
use std::fs::File;

match File::open("fichier.txt") {
    Ok(f) => {
        /* l'ouverture s'est bien déroulée 
        on fait mumuse avec le fichier*/
    }
    Err(e) => {
        /* il y a eu une erreur et on
         ne peut pas faire trop de 
         bêtise avec le fichier car on 
         ne l'a même pas sous la main ! */
    }
}
}

Traits

Un trait est une interface abstraite que des types peuvent implémenter (en gros c'est une liste de méthodes).

Pouvoir être additionné

On peut additionner des i32. Le type i32 implémente le trait Add.

#![allow(unused)]
fn main() {
struct Point {
    x: i32;
    y: i32;
}

impl Add for Point {
    type Output = Self;

    fn add(self, other: Self) -> Self::Output {
        x: self.x + other.x,
        y: self.y + other.y,
    }
}

println!({}, Point { x: 1, y: 0 } + Point { x: 2, y: 3 });
}

En fait, on peut "dériver" que implémenter le trait Add se fait en utilisant le trait Add déjà implémenté sur chaque champ :

#![allow(unused)]
fn main() {
#[derive(Add)]
struct Point2D {
    x: i32,
    y: i32,
}

}

Définir un trait

p. 75

Ownership

En Rust, une donnée sur le tas a un unique propriétaire. C'est lui qui se charge de libérer la mémoire.

Transfert d'ownership

Cette fonction crée un vecteur avec 5 et 12 dedans. C'est v qui est le propriétaire. En faisant, v2 = v, on met les données dans v2 qui devient le propriétaire des données. v ne possède plus rien.

fn main() {
   let v = vec![5, 12];
   let v2 = v;
   println!("{}", v[0]); // non
}

Pareil, si on appelle une fonction sur v, c'est l'argument dans f qui est le propriétaire.

#![allow(unused)]
fn main() {
fn f(argv: Vec<i32>) {
   ...
}
}
fn main() {
   let v = vec![5, 12];
   f(v);
   println!("{}", v[0]); // non
}

Une solution pourrait être que la fonction renvoie v pour que v redevienne propriétaire des données :

fn main() {
   let v = vec![5, 12];
   v = f(v);
   println!("{}", v[0]); // non
}

Mais ça peut être lourdingue, surtout si une fonction a plusieurs paramètres...

Références et emprunt

#![allow(unused)]
fn main() {
fn f(argv: &Vec<i32>) {
   ...
}
}

Le & permet d'emprunter des données, les utiliser, mais sans en être propriétaire. La fonction f ici ne va pas détruire le vecteur. C'est une référence. Penser une référence Rust comme un pointeur C (et pas comme une référence C++), comme une adresse mémoire.

Contrairement à C, le pointeur nul n'existe pas dans le langage Rust. (il existe dans son implémentation)

Déréférencement

Comme en C, *argv signifie les données à l'adresse argv.

fn f(argv: &Vec<i32>) {
   println!("{}", (*argv)[1]);
}

fn main() {
   let v = vec![5, 12];
   f(&v);
    println!("{}", v[0]);
}

Mais en fait, il y a du déférencement automatique. Donc l'étoile * peut être omise :

fn f(argv: &Vec<i32>) {
   println!("{}", argv[1]);
}

fn main() {
   let v = vec![5, 12];
   f(&v);
    println!("{}", v[0]);
}

Règles d'emprunt

  • Une référence ne peut pas vivre plus longtemps que la variable qu'elle référence. Ce code est faux :
#![allow(unused)]
fn main() {
let reference: &i32;
{
   let x = 5;
   reference = &x;
}
println!("{}", reference);
}
  • Autant de références immutables que l'on veut
  • Au plus une référence mutable sur une variable mutable
  • Impossible d'avoir une référence mutable et une autre immutable sur une même variable

Vecteurs et chaînes de caractères

VecteursChaînes de caractères
Type pour la grosse structure où on peut changer la tailleVec<T>String
Type vue mémoire (taille, pointeur)slice<T>str

str

#![allow(unused)]
fn main() {
let x: &str = "Bonjour";
println!("{}", s);
}

Attention, on ne peut pas écrire *s car *s n'a pas de taille connue à la compilation (à la compilation on ne sait pas sur combien de caractères le pointeur pointe).

String

  • Pour transformer une str en String : String::from
  • Pour avoir la str totale d'un String : on déréférence !
#![allow(unused)]
fn main() {
let x: &str = "Bonjour";
let s: String = String::from(x);
let y: &str = &s;
println!("{}", s);
}

Quiz

Questions sur C

  • Est-ce que c'est int *p ou int* p ?
  • Est-ce que les tableaux dans une fonction sont stockés sur la pile ?
  • Peut-on avoir un tableau A[4] dans un struct ?
  • passage par valeur tableau, struct. Est-ce que ça change la valeur ?
  • &A et &A[0] c'est pareil ?
  • &*p = p ?
  • *p et p[0] c'est pareil ?
  • **p a du sens ?
  • &&x a du sens ?
  • type de fonctions ?
  • const char * VS char * const ?
  • combien de mémoire prend un pointeur ?
  • pourquoi pas de tableau de taille qcq avant C99 ?
  • pourquoi les arguments sont posés dans l'ordre inverse sur la pile ?
  • pourquoi la pile est à l'envers dans la mémoire ?
  • Est-ce que la mémoire d'un processus est limité à 4Go même pour un processus 64bits ?
  • Comment coder un dictionnaire en C ? (rien dans la librairie standard, mais des choses dans glib, la librairie de GNOME)
  • Pourquoi les adresses mémoire commencent par 0x ? (0 c'est pour dire que c'est une constante et x pour dire que c'est de l'hexadécimal.)
  • Est-ce que &&ptr a du sens ?
  • Idées d'application de variables static ?
    • mutex de protection de ta fonction contre des appels concurrents
    • compteur d'appels
    • compteur pour debug
    • compteur pour comportement execptionnel (un truc à faire une fois sur dix)
    • initialisation d'une constante à calculer qu'une fois (je crois que l'expression est faite/évaluée qu'au premier passage dans la fonction)
    • drag and drop de souris

Questions sur Python

  • Comment s'appelle les méthodes de la forme __XXXX__ comme __init__, __add__, etc. ? ("Dunder methods" or "magic methods", "méthodes spéciales")
  • En Python, à quoi peut servir une fonction avec yield ?
    • Pour des raisons de mémoire, par exemple, lire un énorme fichier texte ligne par ligne => on fait yield
    • Simuler une séquence infini
  • Est-ce qu'un générateur est un itérateur ?
    • Oui, mais pas l'inverse ! Un itérateur peut être plus compliqué (comme un objet où il y a aussi une méthode pour avoir l'élément courant, changer d'état etc. et pas juste next(.) ; mais s'il y a juste next(.) un générateur suffit !)
  • Comment est codé le type list en Python ? Comment le tableau est dynamiquement redimensionné ?
  • Comment est représenté une table de hachage pour un dictionnaire ? C'est quoi le hash d'un str, d'un tuple ?
  • Est-ce que le type "immutable" existe bien ? Ou c'est juste qu'il y a un hash pour mettre dans un set ou utiliser comme key d'un dict ?
  • Y-a-t-il des génériques en Python ? Oui on écrit Sequence[Employee] par exemple.

Questions concept

  • encapsulation

Questions culture générale

  • En quoi était codé Super Mario Bros ? Doom ?
  • En quoi est codé Firefox ?
  • En quel langage est codé le noyau Linux ?
  • Nom du système d'exploitation écrit en Rust ?
  • En quoi est codé TeX ?
  • C'est quoi big endian et little endian ? C'est utilisé où ?

Reviews de code - TP 1

#include <stdio.h>

int somme(int tableau[], int taille) {
	int i;
	int accumulateur;
	accumulateur = 0;
	for (i = 0; i < taille; i++) {
		accumulateur = accumulateur + tableau[i];
	}
	return accumulateur;
}

void affiche(int tableau[], int taille) {
	for (int i = 0; i < taille; ++i) {
		printf("La case %d contient la valeur : %d.\n", i, tableau[i]);
	}
}

int main() {
	int notes[5] = { 12,18,15,13,20 };
	int total = somme(notes, 5);
	printf("Le total est : %d. Au revoir.\n", total);
	affiche(notes, 5);
	return 0;
}
#include <stdio.h>

/* I */

int somme(int tableau[], int taille) {
    int i;
    int accumulateur;
    accumulateur=0;
    for (i=0;i<taille;i++) {
        accumulateur=accumulateur+ tableau[i];
    }
    return accumulateur;
}


void affiche(int tableau[], int taille){
    printf("[|");
    int i;
    for (i=0; i<taille-1;i++){
        printf(" %d ,",tableau[i]);
    }
    printf(" %d |]",tableau[taille-1]);
}


/* II - 1) */

void crible(int tab[],int taille,int p){
    int i;
    for (i=2; p*i<taille;i++){
        tab[p*i]=0;
    }
}


void affichage(int tab[],int taille){
    int i;
    int compt = 0;
    for (i=0;i<taille;i++){
        if (tab[i]!=0) {
            printf("%d ",tab[i]);
            compt++;
        }
    } 
    printf("\n %d",compt) ;
}


void erathosthene(int n){
    int tab[n];
    int i;
    for (i=0;i<n;i++){
        tab[i]=i;
    }
    tab[1]=0;
    for (i=0;i<n;i++){
        if (tab[i]!=0) crible(tab,n,i);
    }
    affichage(tab,n);
}


/* II - 2) */

void calc_sum(char expression[]){                           /* ATTENTION : En ASCII, les chiffres sont de 48 à 57 */
    int out=0;
    int temp=0;
    int valide=1;
    int dec=0;
    int c;
    int i;
    for (c=0 ; expression[c]!= '\0' ;c++) {
        printf("%c",expression[c]);
        if (expression[c]=='+') {
            if (temp==0)
                valide=0;
            else {
                for (i=0;i<dec-1;i++)
                    temp=temp/10;
                out+=temp;
                temp=0;
            }
        }
        else {
            if ('0'<=expression[c] && expression[c]<='9') {
                temp=temp*10 + (expression[c]-'0');
                if (dec!=0)
                    dec++;
            }
            else
                if (expression[c]=='.')
                    dec=1;
                else
                    valide=0;
        }
    }
    if (temp!=0)
        out+=temp;
    else
        valide=0;
    if (valide==1)
        printf("\n%d",out);
    else
        printf("\nBye");
}


int main(){
    /* int notes[5] = {12,18,15,13,20};
    int total = somme(notes,5);
    printf("Le total est : %d. Au revoir \n",total);
    return 0; 
    affiche(notes,5); 
    erathosthene(100);*/
    calc_sum("45+3+22+1.2");
}

#include <stdio.h>
#include <stdlib.h>
//#include <bool.h>
#include <math.h>

void mult(int k, int* tab, int n, int nb) {
    if (nb>=n) return;
    tab[nb-1] = k;
    mult(k, tab, n, nb+k);
}

int main() {
    int n;
    scanf("%d", &n);
    //printf("%d\n", n);

    int* tab = malloc((n-1)*sizeof(int));
    for (int i=0; i<n-1; ++i) tab[i] = 0;

    for (int i=2; i<n; i++) {
        mult(i, tab, n, i+i);
    }
    //for (int i=0; i<n-1; ++i) printf("%d ", tab[i]);
    //printf("\n");
    int pi = 0;
    for (int i=1; i<n-1; ++i) {
        if(tab[i] == 0) {printf("%d ", i+1); pi++;}
    }
    printf("\n");
    printf("%d\n", pi);

    free(tab);

    return EXIT_SUCCESS;

}
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int parser(char* str){
    int n = strlen(str);
    float* res = malloc(n);
    for (int i = 0; i < n; i++){
        res[i] = 0;
    }

    int malParsee = 0;

    int j = 0; //vérifie le parenthésage (+) des expressions
    int actuel = 0;
    char enCours[200];
    enCours[0] = '\0';
    for (int i = 0; i < n; i++){
        char c = (char)str[i];
        if (j == 1){
            if (c == '+'){
                res[actuel] = atof(enCours);
                enCours[0] = '\0';
                actuel++;
                j = 0;
            }else{
                if (c == '\n'){
                    res[actuel] = atof(enCours);
                    enCours[0] = '\0';
                    actuel++;
                }
                char new_c[2] = "a";
                new_c[0] = c;
                strcat(enCours, new_c);
            }

        }else{
            if (c == '+' || c == '\n'){
                malParsee = 1;
            }else{
                char new_c[2] = "\0";
                new_c[0] = c;
                strcat(enCours, new_c);
                j = 1;
            }
        }
    }

    if (malParsee){
        printf("Bye. \n");
        return 0;
    }else{
        float sum = 0;
        for (int i = 0; i < n/2 +1; i++){
            sum = sum + res[i];
        }
        printf("%f\n", sum);
        return 1;
    }

}

void main(){
    char entree[200];
    int continuer = 1;
    while(continuer){
        printf("entrez un calcul : ");
        scanf("%s", entree);
        continuer = parser(strcat(entree, "\n"));
    }
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

bool* erat(int n){
    bool* res = malloc(n*sizeof(bool));
    for(int i = 0; i < n; i+=1){
        res[i] = false;
    }
    for(int i = 1; i < n; i+=1){
        if(!res[i]){
            for(int j = 2*i + 1 ; j < n ; j+= i+1 ){
                res[j] = true;
            }
        }
    }
    return res;
}

void eratosthene(int n){
    bool* crible = erat(n);
    int compteur = 0;
    for(int i = 1; i<n ; i+=1){
        if(!crible[i]){
            printf("%i\n", i+1);
            if(i+1!=n) compteur += 1;
        }
    }
    free(crible);
    printf("pi(n) : %i\n", compteur);
}

int main(int argc, char* argv[]){
    assert(argc == 2);
    int n = atoi(argv[1]);
    eratosthene(n);

}
#include <stdio.h>

int somme(int tableau[], int taille){
	int i;
	int accumulateur;
	accumulateur=0;
	for (i=0; i<taille; i++){
		accumulateur=accumulateur+tableau[i];
	}
	return accumulateur;
}






void affiche(int tableau[], int taille){
	printf("{");
	for(int i=0; i<taille;i++){
		if (i>0) {
			printf(", ");
		}
	
		printf("%d", tableau[i]);
		}
	printf("}\n");
}





int main(){
	int notes[5]={12,18,15,13,20};
	int total=somme(notes, 5);
	printf("Le total est : %d. Au revoir.\n", total);
	affiche(notes, 5);
	return 0;
}
#include <stdio.h>
#include <stdlib.h>
/*
 * Initializes an eratosthene sieve
 *
 * @arg primes the pointer to the location in memory where the sieve will be initialized
 * @arg size the size of the allocated memory
 * @return nothing
 */
void init(char* primes, int size){
	primes[0]=primes[1]=0;
	for(int i=2; i<size; i++){
		primes[i]=1;
	
	}
}


/*
 * Displays a table
 *
 * @arg table a table of characters
 * @arg size the size of the table
 * @return nothing
 */
void display(char tableau[], int size){
	printf("{");
	for(int i=2; i<size;i++){
		if (isPrime[i]) {
			printf("%d\n", i);
		}
}

/*
 * Unmark non-prime numbers
  *
  * @arg table a table of characters representinf wether their index is a prime number or not
  * @arg size the size of the table
  * @return nothing
  */
void sieve(char isPrime[], int size){
	for(int p=2; p<size; p++){
		if(isPrime[p]){
			int i=2*p;
			while(i<size){
				isPrime[i]=0;
				i+=p;
			}
		}
	}
}







int main(int argc, char* *argv){
	int size=atoi(argv[1]);
	char isPrime[size];
	init(isPrime,size);
	sieve(isPrime[size];
	display(isPrime, size);
}
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 11
#define M 15

char A[N][M];

void init_just_in_case(void) {
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
      A[i][j] = 'X';
    }
  }
}

typedef enum {
  DFS_UNSEEN,
  DFS_OPEN,
  DFS_CLOSED,
} DFS_MARK;

DFS_MARK dfs_marks[N][M];
bool cyclical;
bool connex;

// there must be a problem somewhere in there, because
// cycles don't seem to be detected properly...
void dfs(int i, int j, int parent_i, int parent_j) {
  if (dfs_marks[i][j] == DFS_CLOSED)
    return;
  if (dfs_marks[i][j] == DFS_OPEN) {
    cyclical = true;
    return;
  }
  dfs_marks[i][j] = DFS_OPEN;

  // issue found: we are not going up or left
  if (i + 2 < N - 1 && (j != parent_j || i + 2 != parent_i) && A[i + 1][j] == ' ')
    dfs(i + 2, j, i, j);
  if (j + 2 < M - 1 && (i != parent_i || j + 2 != parent_j) && A[i][j + 1] == ' ')
    dfs(i, j + 2, i, j);
  dfs_marks[i][j] = DFS_CLOSED;
}

void update_graph_knowledge(void) {
  cyclical = false;
  connex = true;
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
      dfs_marks[i][j] = DFS_UNSEEN;
    }
  }
  dfs(1, 1);
  for (int i = 1; i < N - 1; i += 2) {
    for (int j = 1; j < M - 1; j += 2) {
      if (dfs_marks[i][j] == DFS_UNSEEN) {
        connex = false;
      }
    }
  }
}

void generate(void) {
  init_just_in_case();

  for (int i = 1; i < N - 1; i += 2) {
    for (int j = 1; j < M - 1; j += 2) {
      A[i][j] = ' ';
    }
  }

  printf("\n");

  update_graph_knowledge();
  while (cyclical || !connex) {
    // remove a random edge
    int i = 1 + 2 * (rand() % ((N) / 2));
    int j = 1 + 2 * (rand() % ((M) / 2));
    if (i == N || j == M)
      continue;

    if (rand() % 2 && i + 2 < N - 1) {
      A[i + 1][j] = ' ';
      update_graph_knowledge();
      if (cyclical)
        A[i + 1][j] = 'X';
    } else if (j + 2 < M - 1) {
      A[i][j + 1] = ' ';
      update_graph_knowledge();
      if (cyclical)
        A[i][j + 1] = 'X';
    }
    update_graph_knowledge();
  }
}

void print_labyrinth(void) {
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
      printf("%c", A[i][j]);
    }
    printf("\n");
  }
}

int main(void) {
  srand(time(NULL));
  generate();
  print_labyrinth();
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef struct cell_s
{
    int val;
    struct cell_s* next;
} cell;

cell* cons(int x, cell* l)
{
    cell* new = (cell*)malloc(sizeof(cell));
    new->val = x;
    new->next = l;
    return new;
}

void eratosthene(int n)
{
    int p = 1;
    cell** prems = (cell**)malloc(sizeof(cell*));
    printf("2 ");
    *prems = cons(2, NULL);
    for(int i = 3; i<=n; i++)
    {
        bool prem = true;
        for(cell* temp = *prems; temp != NULL; temp = temp->next) if(i%temp->val==0) prem = false;
        if(prem) 
        {
            *prems = cons(i, *prems);
            printf("%d ", i);
            p++;
        }
    }
    printf("\n%d\n", p);
    return;
}

void eratosthene2(int n)
{
    //A FAIRE AVEC DES BOOL DE TAILLE 4
}

bool is_digit(char c)
{
    return(c>47 && c<58);
}

int read_int(char* s, int* i)
{
    int res = 0;
    while(is_digit(s[*i]))
    {
        res *= 10;
        res += ((int)s[*i] - 48);
        (*i)++;
    }
    return res;
}

int expo(int a, int n)
{
    if(n==1) return a;
    int x = expo(a, n/2);
    if(n%2 == 0) return(x*x);
    else return(a*x*x);
}

double read_float(char*s, int* i)
{
    double res = (double)read_int(s, i);
    if(s[*i] == ',' || s[*i] == '.')
    {
        (*i)++;
        int i0 = *i;
        double frac = (double)read_int(s, i)/ (double)expo(10, *i - i0);
        return(res + frac);
    }
    return res;
}

void calc_sum(void)
{
    while(1)
    {
        char in[256];
        scanf("%s", in);
        bool plus_ok = false; //Est-ce qu'avoir un plus à cet endroit pose un pb
        int s = 0;
        int i;
        for(i = 0; i<256 && in[i] != '\0'; i++)
        {
            if(in[i]=='+')
            {
                if(plus_ok)
                {
                    plus_ok = false;
                    continue;
                }
                printf("Bye\n");
                return;
            }
            s += read_int(in, &i);
            plus_ok = true;
            if(in[i] != '+')
            {
                if(in[i] != '\0')
                {
                    printf("Bye\n");
                    return;
                }
                i--;
                continue;
            }
        }
        printf("%d\n", s);
    }
}

void unite(int* p, int i, int j, int n)
{
    if(p[i] != p[j])
    {
        for(int k = 0; k<n; k++)
        {
            if(p[k] == p[j]) p[k] = p[i];
        }
    }
    return;
}

bool** genere_laby(int n, int m)
{
    assert(n%2 * m%2 == 1);
    //true : 1 mur
    int* part = (int*)malloc(sizeof(int)*(n*m/4));
    for(int i = 0; i<n*m/4; i++) part[i] = i;

    bool** lab = (bool**)malloc(sizeof(bool*)*n);
    for(int i = 0; i<n; i++)
        lab[i] = (bool*)malloc(sizeof(bool)*m);
    for(int i = 0; i<n/2; i++)
    {
        lab[2*i][0] = true;
        lab[2*i+1][0] = true;
        lab[2*i][m-1] = true;
        lab[2*i+1][m-1] = true;
        for(int j = 0; j<m/2; j++)
        {
            if(i==0)
            {
                lab[0][2*j] = true;
                lab[0][2*j+1] = true;
                lab[n-1][2*j] = true;
                lab[n-1][2*j+1] = true;
            }
            lab[2*i][2*j] = true;
            lab[2*i+1][2*j+1] = false;
            int rd = RAND()%2;
            if(rd && (part[m*i + j] != part[m*(i+1) + j])) //Si on essaie de faire un trou dans (2*i+1, 2*j+1), (2*i+3, 2*j+1)
            {
                unite(part, m*i + j, m*(i+1) + j, n/2 * m/2);
                lab[2*(i+1)][2*j+1] = false;
            }
            else lab[2*(i+1)][2*j+1] = true;
            rd = RAND()%2;
            if(rd && (part[m*i + j] != part[m*i + j+1])) //Si on essaie de faire un trou dans (2*i+1, 2*j+1), (2*i+1, 2*j+3)
            {
                unite(part, m*i + j, m*i + j+1, n/2 * m/2);
                lab[2*i+1][2*(j+1)] = false;
            }
            else lab[2*i+1][2*(j+1)] = true;;
            
        }
    }
    return lab;    
}

void print_lab(bool** lab, int n, int m)
{
    for(int i = 0; i<n; i++)
    {
        for(int j = 0; j<m; j++)
        {
            if(lab[i][j]) printf("X");
            else printf(" ");
        }
        printf("\n");
    }
}

void free_lab(bool** lab, int n)
{
    for(int i = 0; i<n; i++)
    {
        free(lab[i]);
    }
    free(lab);
    return;
}


int main(void)
{
    //eratosthene(256);
    //printf("%d\n", eratosthene(256));
    //int i = 0;
    //printf("%f\n", read_float("98456,97845afdeaeafez", &i));
    bool** lab = genere_laby(15, 15);
    print_lab(lab, 15, 15);
    free_lab(lab, 15);
    return 0;
}
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>

typedef struct cc {
	int val; //Ordre croissant
	struct cc* next;
} chaine;

bool est_premier(int n, chaine* t){
	chaine* prem = t;
	int root = sqrt( (float)n ) + 1;
	bool b = true;
	while( prem != NULL && prem->val <root && b){
		b = (n % prem->val != 0);
		prem = prem->next;
		
		}
	return b;
	}
	
chaine* init_chaine(int n){
	chaine* new = malloc(sizeof(chaine));
	new->val = n; new->next = NULL;
	return new;
}

void add(int n, chaine* t){
	
	chaine* new = init_chaine(n);
	chaine* tmp = t;
	while (tmp->next !=NULL) {tmp = tmp->next;}
	tmp->next =new;
}

chaine* erastot( int n){
	chaine* start = malloc(sizeof(chaine));
	start->val = 2;
	for(int i = 3; i< n+1; i++){
		if (est_premier(i, start)){
			add(i, start);
		}
	}
	return start;
}
	
int somme(int tableau[], int taille){
	int acc = 0;
	for (int i = 0; i<taille; i++){
		acc += tableau[i];
	}
	return acc;
}



void calc_somme(char* s ){
	float c_t_f(char* s, int n){
	assert(n>0);
	char* nw = malloc(sizeof(char)*(n+1)); nw[n] = '\0';
	for(int i = 0; i<n; i++){
		nw[i] = s[i];
	}
	return atof(nw);
}

	int i = 0;
	chaine* symbols = init_chaine(-1);
	while(s[i] != '\0'){
		if( s[i] == '+' || s[i] == '-'){
			add(i,symbols);
		}
		i++;
	}
	add(i,symbols);
	
	float somme = c_t_f(s, symbols->next->val - symbols->val -1);
	chaine* tmp = symbols->next;
	while( tmp->next != NULL){
		if( s[tmp->val] == '+'){
			somme += c_t_f(&(s[tmp->val +1]), tmp->next->val - tmp->val -1);
		}
		
		else{
			somme -= c_t_f(&(s[tmp->val +1]), tmp->next->val - tmp->val -1);
		}
		tmp = tmp->next;
	}
	printf("La somme est %f\n",somme);
}


//int** labyrinthe(int n, int m){
	//}

int main(){
	int notes[5] = {12,18,15,13,20};
	int total = somme(notes,5);
	printf("Le total est %d. \n", total);
	
	char buffer[100];
	scanf("%s", buffer);
	calc_somme(buffer);

	return 0;
	
}

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

int somme(int tableau[], int taille){
    int accumulateur;
    accumulateur = 0;
    for (int i=0; i<taille; i++){
        accumulateur = accumulateur + tableau[i];
    }
    return accumulateur;
}

void afficher(int* tableau, int taille){
    printf("{");
    for (int i=0; i<taille-1; i++){
        printf("%d, ", tableau[i]);
    }
    printf("%d}\n",tableau[taille-1]);
}

void eratostene(int n){
    int * premiers = malloc(sizeof(int)*(n+1));
    int acc;
    acc =0;
    for(int i=0;i<n+1;i++) premiers[i] = i;
    for(int i=2;i<n+1;i++){
        if (premiers[i] == i){
            printf("%d ;",i);
            acc++;
            for(int k=2;(i)*k<=n;k++){
                premiers[(i)*k] = 0;
            }
        }
    }
    printf("\n pi(%d) = %d. \n",n,acc);
    free(premiers);
}

void calc(void){
    char buffer[100];
    printf("Entrez une ligne de calcul:\n");
    scanf("%[^\n]s",&buffer);
    bool b = true;
    int cursor = 0;
    int res = 0;
    int dec = 0;
    bool mode = true;
    while(buffer[cursor] != '\0'&&b){
        int compt = 0;
        int acc = 0;
        int i = buffer[cursor] - '0';
        while(0 <= i && i <= 9){
            acc *= 10;
            acc += i;
            cursor++;
            i = buffer[cursor] - '0';
            compt++;
        }
        if (buffer[cursor] == '+'&&compt>0){
            cursor++;
            if (mode) res += acc;
            else dec +=acc;
            mode = true;
        }
        else if(buffer[cursor] == '.'&&mode){
            res += acc;
            mode = false;
        }
        else if(buffer[cursor] == '\0'){
            if (mode) res += acc;
            else dec +=acc;
        }
        else{
            b = false;
        }
    }
    if (b||buffer[cursor] == '\0'){
        printf("%d.%d\n",res,dec);
    }
    else{
        printf("Bye.");
    }
}

int main(){
    /*
    int notes[5] = {12, 18, 15, 13, 20};
    int total = somme(notes, 5);
    printf("le total est : %d. Au revoir. \n", total);
    afficher(notes, 5);
    eratostene(100);
    */
    calc();
    return 0;
}
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

void premier (bool* tab, int n){
    int i=2;
    while (i<n+1)
    {
        if (tab[i])
        {
            for (int k = 2; i*k <= n; k++)
        {
            tab[k*i]=false;
        }
        }        
        i++;
    }
    
}

int pi (int n){
    bool* tab=malloc(sizeof(bool)*(n+1));
    for (int i = 0; i < n+1; i++)
    {
        tab[i]=true;
    }
    premier(tab, n);
    int acc=0;
    for (int i = 2; i < n+1; i++)
    {
        if (tab[i])
        {
            acc++;
        }
    }
    free(tab);
    return acc;
}

void eratosthene (int n){
    bool* tab=malloc(sizeof(bool)*(n+1));
    for (int i = 0; i <= n; i++)
    {
        tab[i]=true;
    }
    
    premier(tab, n);
    for (int i = 2; i <= n; i++)
    {
        if (tab[i])
        {
            printf("%d est premier\n",i);
        }
        
    }
    free(tab);
}

int main(){
    eratosthene(100);
    printf("%d nb premier\n",pi(100));
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

int somme(int tableau[], int taille) {
   int i;
   int accumulateur;
   accumulateur = 0;
   for (i= 0; i < taille; i++) {
          accumulateur = accumulateur + tableau[i];
   }
   return accumulateur;
}

void pi(int n) {
    int tableau[n];
    for(int i = 0; i<n; i++){
            tableau[i] = 0;
    }
    for (int i=2; i <n ; i++) {
        for (int j = 2; j*i<=n; j++){
              tableau[j*i] = 1; 
        }
    }
    int compteur = 0;
    for (int i=2; i <n ; i++){
         if (tableau[i] == 0) {
              printf("%d\n", i);
              compteur = compteur + 1;}
    }
    printf("%d",compteur);        
}
void calc_sum(char* s){
      int plus = 0;
      for (int i = 0; s[i] != 0; ++i) {
      		
      } 
}
 
int main(){
    int notes[5] = {12,18,15,13,20};
    int total = somme(notes, 5) ;
    printf("le total est : %d. AU revoir. \n", total);
    pi(100);
    return 0;
}


// Retourne dans (x2, y2) les coordonnées d'une case voisine à (x,y) qui n'a pas déjà été explorée
void find_next_direction(int* x2, int* y2, int x, int y, int n, int m, bool** filled) {
  // DIRECTION :
  //
  //       ^
  //       |
  //       |
  //       1
  // <-- 0   2 -->
  //       3
  //       |
  //       |
  //       v

  while(true){
    int dir = rand() % 4;
    switch(dir) {
      case 0:
        *x2 = x - 1;
        *y2 = y;
        break;

      case 1:
        *x2 = x;
        *y2 = y - 1;
        break;

      case 2:
        *x2 = x + 1;
        *y2 = y;
        break;

      case 3:
        *x2 = x;
        *y2 = y + 1;
        break;
    }

    if(!is_filled(n,m,*x2,*y2,filled)) break;
  }
}
#include <stdio.h>
#include <stdbool.h>

int somme(int tableau[], int taille){
	int i;
	int accumulateur;
	accumulateur = 0;
	for (i = 0; i < taille; i++){
		accumulateur = accumulateur + tableau[i];
	}
	return accumulateur;
}


void affiche(int tableau[], int taille){
	int i;
	for(i = 0; i < taille; i++){
		printf("%d \n", tableau[i]);
	}
}

int main(){
	int notes[5] = {12,18,15,13,20};
	int total = somme(notes, 5);
	affiche(notes, 5);
	return 0;
}


#include <stdio.h>
#include <stdbool.h>

void afficheLabyrinthe(int n, int m, bool A[][m]) {
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (A[i][j]) printf("X");
      else printf(" ");
    }
    printf("\n");
  }
}

void creerLabyrinthe(int n, int m) {
  bool A[n][m];
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (i == 0 || i == n-1 || j == 0 || j == m-1) {
	A[i][j] = true;
      } else if (i % 2 == 0 && j % 2 == 0) {
	A[i][j] = true;
      } else if (i % 2 == 1 && j % 2 == 1) {
	A[i][j] = false;
      } else {
	A[i][j] = false;
      }
    }
  }
  afficheLabyrinthe(n, m, A);
}

int main(void) {
  int n = 21;
  int m = 21;
  creerLabyrinthe(n, m);
  return 0;
}
#include <stdio.h>
#include <stdlib.h>

/*
*Cette fonction effectue le crible d'eratosthene sur un tableau
*@return nothing
*/
int eratosthene(int size, int isPrime[]){
	for (int i=2; i<size; i++){
		for (int j = 2*i;j<size; j+=p){
				isPrime[j] = -1
			}
		}
	}
	return isPrime[]
	}
	
	
/* cete fonction affiche le tableau donné en argument
*@return nothing
*/
void affiche(int isPrime[])
{ for (i=0;i<size;++){
	if isPrime[]=0;
		printf(i)
	}
}

/*
*cette fonction initialise le tableau isPrime en attribuant la valeur 1 par defauts à tous les entiers sauf 0 et 1 dont on sait qu'ils sont premiers 
*@return nothing
*/
void init(char isPrime[],int size){
	isPrime[0]=isPrime[1]=0
	for (i=2;i<size;i++) {
		isPrime[i] = 1
		}
	}
	

int main(int argc,char* *argv)
{
int size = atoi(arrgv[1]);
int isPrime[size];
init(isPrime)
eratosthene(size,isPrime)
affiche(isPrime)
}
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

void premier (bool* tab, int n){
    int i=2;
    while (i<n+1)
    {
        if (tab[i])
        {
            for (int k = 2; i*k <= n; k++)
        {
            tab[k*i]=false;
        }
        }        
        i++;
    }
    
}

int pi (int n){
    bool* tab=malloc(sizeof(bool)*(n+1));
    for (int i = 0; i < n+1; i++)
    {
        tab[i]=true;
    }
    premier(tab, n);
    int acc=0;
    for (int i = 2; i < n+1; i++)
    {
        if (tab[i])
        {
            acc++;
        }
    }
    free(tab);
    return acc;
}

void eratosthene (int n){
    bool* tab=malloc(sizeof(bool)*(n+1));
    for (int i = 0; i <= n; i++)
    {
        tab[i]=true;
    }
    
    premier(tab, n);
    for (int i = 2; i <= n; i++)
    {
        if (tab[i])
        {
            printf("%d est premier\n",i);
        }
        
    }
    free(tab);
}

int main(){
    eratosthene(100);
    printf("%d nb premier\n",pi(100));
    return 0;
}

Reviews de code - TP 2

/// Addition de deux entier de gauss
/// Premier entier de gauss z_1 
/// Deuxième entier de gauss z_2 
/// return : z_1 + z_2 
gi addition(gi z_1, gi z_2)
{
    gi z;
    z.re = z_1.re + z_2.re;
    z.im = z_1.im + z_2.im;
    return z;
}
// Première version de somme moyenne
// utilise la classe en_fl qui permet de renvoyé un entier et un flottant. 
struct en_fl somme_moyenne_bad (int t[], int n) {
    int somme ;
    for (int i= 0; i < n; i++){somme += t[i]; }
    struct en_fl rep ;
    rep.en = somme ;
    rep.fl = (float) somme / n ;
    return rep ;
}


void somme_moyenne(int t[] , int taille, int* ptr_somme, float* ptr_moyenne)
{
    int somme ;
    for (int i= 0; i < taille; i++){somme += t[i]; }
    *ptr_somme = somme ;
    *ptr_moyenne = (float) somme / taille ;

}

Game of life

// a few helper functions
int liveliness(CellState s) {
  if (s == ALIVE) {
    return 1;
  } else {
    return 0;
  }
}

void swap_live(CellState* s) {
  if (*s == ALIVE) {
    *s = DEAD;
  } else {
    *s = ALIVE;
  }
}


// in C, the "%" infix operator is the remainder operation, not the mathematical
// modulus. The latter is what we need and is implemented by this function.
int mod(int x) {
  return ((x % N) + N) % N;
}

// timer to update the simulation every 0.1 seconds
typedef struct {
  double start;
  double length;
} Timer;

void Timer_start(Timer* timer, double length) {
  timer->start = GetTime();
  timer->length = length;
}

bool Timer_finished(Timer* timer) {
  return GetTime() - timer->start >= timer->length;
}



...

 // rendering
    BeginDrawing();
  	ClearBackground(BLACK);
    for (int x = 0; x < N; x++) {
      for (int y = 0; y < N; y++) {
				if ((*current)[AT(x, y)] == ALIVE) {
					DrawRectangle(x * 10, y * 10, 10, 10, WHITE);
				}
      }
    }
    EndDrawing();


...

Brainfuck interpreter

/**
* @file main.c
* @brief A simple Brainfuck interpreter.
* -----
* @created Tu Sep 2024
* @author Alexandre DOUARD
* -----
* @last modified Sun Sep 22 2024
* @modified by Alexandre DOUARD
* -----
* @copyright (c) 2024 Ecole Normal Superieur Lyon
*/

#include "brainfuck.h"

/**
 * @brief Prints the usage instructions for the Brainfuck interpreter.
 *
 * This function displays the correct usage of the program when incorrect
 * arguments are passed by the user.
 *
 * @param av[] The array of arguments passed to the program. The first element
 *             is the program's name.
 */
void print_usage(char *av[])
{
    printf("USAGE %s <filename.bf>\n", av[0]);
}

/**
 * @brief Main entry point for the Brainfuck interpreter.
 *
 * This function checks if the correct number of arguments is passed, and
 * either proceeds to interpret the Brainfuck file or prints the usage
 * instructions. If the number of arguments is incorrect, it returns a failure
 * code.
 *
 * @param ac The number of arguments passed to the program.
 * @param av[] The array of arguments passed to the program.
 * @return EXIT_FAILURE if the incorrect number of arguments is passed;
 *         otherwise, it returns the result of the brainfuck function.
 */
int main(int ac, char *av[])
{
    if (ac != 2) {
        print_usage(av);
        return EXIT_FAILURE;
    }
    return brainfuck(av) ? FAILURE_EXIT : SUCCESS_EXIT;
}
/**
 * @struct Ptr
 * @brief Represents a cell in the Brainfuck memory tape.
 *
 * This structure is used to represent each memory cell in the Brainfuck interpreter.
 * Each cell has a value and pointers to the previous and next cells.
 */
typedef struct Ptr {
    struct Ptr *next;
    struct Ptr *prec;
    char val;
} Ptr;

// ptr_handling.c
Ptr *create_ptr(void);
Ptr *add_to_next(Ptr *ptr, Ptr *nxt);
Ptr *add_to_prec(Ptr *ptr, Ptr *prec);
void destroy_ptr(Ptr **ptr);

// operations.c
Ptr *incr_ptr(Ptr *ptr);
Ptr *decr_ptr(Ptr *ptr);
Ptr *incr_ptr_v(Ptr *ptr);
Ptr *decr_ptr_v(Ptr *ptr);
Ptr *read_ptr(Ptr *ptr);
Ptr *write_ptr(Ptr *ptr);
/**
 * @brief Creates a new memory cell for the Brainfuck pointer.
 *
 * This function allocates memory for a new pointer and initializes its value to 0,
 * with no previous or next cells linked.
 *
 * @return A pointer to the newly created memory cell, or NULL if memory allocation fails.
 */
Ptr *create_ptr(void)
{
    Ptr *tmp = malloc(sizeof(Ptr));
    if (tmp == NULL)
        return NULL;
    tmp->next = NULL;
    tmp->prec = NULL;
    tmp->val = 0;
    return tmp;
}


/**
 * @brief Recursively destroys the next linked memory cells.
 *
 * This function recursively frees all memory cells linked to the given pointer via the `next` pointer.
 *
 * @param ptr The pointer to the current memory cell.
 */
static void destroy_ptr_next(Ptr *ptr)
{
    if (ptr->next != NULL) {
        destroy_ptr_next(ptr->next);
        free(ptr);
    }
}

/**
 * @brief Recursively destroys the previous linked memory cells.
 *
 * This function recursively frees all memory cells linked to the given pointer via the `prec` pointer.
 *
 * @param pt The pointer to the current memory cell.
 */
static void destroy_ptr_prec(Ptr *ptr)
{
    if (ptr->prec != NULL) {
        destroy_ptr_prec(ptr->prec);
        free(ptr);
    }
    if (ptr != NULL)
        free(ptr);
}


/**
 * @brief Moves the pointer to the next memory cell.
 *
 * This function moves the Brainfuck pointer to the next memory cell to the right.
 * If the next cell does not exist, it creates a new cell and links it to the current one.
 *
 * @param ptr The current memory pointer.
 * @return The pointer to the next memory cell, or NULL if an error occurs.
 */
Ptr *incr_ptr(Ptr *ptr)
{
    Ptr *tmp = NULL;

    if (ptr == NULL)
        return NULL;
    if (ptr->next != NULL)
        return ptr->next;
    tmp = create_ptr();
    if (tmp == NULL || add_to_next(ptr, tmp) == NULL || ptr->next == NULL)
        return NULL;
    return ptr->next;
}


/**
 * @brief Increments the value at the current memory cell.
 *
 * This function increases the value stored at the current memory cell by 1.
 *
 * @param ptr The current memory pointer.
 * @return The updated memory pointer, or NULL if the pointer is NULL.
 */
Ptr *incr_ptr_v(Ptr *ptr)
{
    if (ptr == NULL)
        return NULL;
    ptr->val++;
    return ptr;
}


/**
 * @def CLEANUP(X)
 * @brief Automatically calls the specified cleanup function when the variable goes out of scope.
 *
 * This attribute is used for automatic resource cleanup by specifying a function `X` to call when the variable goes out of scope.
 *
 * @param X The function to be called for cleanup.
 */
#define CLEANUP(X) __attribute__((cleanup(X)))


/**
 * @brief Reads the content of a file into a dynamically allocated string.
 *
 * This function opens the file at the specified path, reads its content into
 * memory, and returns it as a null-terminated string. If the file cannot be
 * opened or read, the function returns NULL.
 *
 * @param path The path to the file to read.
 * @return A pointer to the dynamically allocated string containing the file content,
 *         or NULL if the file could not be opened or read.
 */
char *contentFile(char *path)
{
    struct stat s = {0};
    int fd = 0;
    char *content = NULL;
    int tmp = 0;

    if (stat(path, &s))
        return NULL;
    fd = open(path, O_RDONLY);
    if (fd < 0)
        return NULL;
    content = malloc(sizeof(char) * (s.st_size + 1));
    tmp = read(fd, content, s.st_size);
    content[s.st_size] = 0;
    if (tmp <= 0)
        free(content);
    close(fd);
    return content;
}

/**
 * @brief Executes Brainfuck code by processing each instruction.
 *
 * This function iterates through the Brainfuck code and executes each instruction,
 * handling loops and performing the necessary pointer manipulations. It ensures
 * correct memory management and error handling during execution.
 *
 * @param str The Brainfuck code as a string.
 * @param pc A pointer to the program counter.
 * @param ptr The memory pointer.
 * @return SUCCESS_RETURN on successful execution; FAILURE_RETURN on error.
 */
int exec(char *str, int *pc, Ptr *ptr)
{
    int tmp = 0;

    while (str[*pc]) {
        if (str[*pc] == '[') {
            tmp = (*pc);
            (*pc)++;
            if (exec_on_loop(str, pc, &ptr) == FAILURE_RETURN)
                return FAILURE_RETURN;
            if (ptr->val)
                *pc = tmp;
            continue;
        }
        if (exec_ope(str[*pc], &ptr) == FAILURE_RETURN)
            return FAILURE_RETURN;
        (*pc)++;
    }
    return SUCCESS_RETURN;
}
/**
 * @brief Executes Brainfuck code by processing each instruction.
 *
 * This function iterates through the Brainfuck code and executes each instruction,
 * handling loops and performing the necessary pointer manipulations. It ensures
 * correct memory management and error handling during execution.
 *
 * @param str The Brainfuck code as a string.
 * @param pc A pointer to the program counter.
 * @param ptr The memory pointer.
 * @return SUCCESS_RETURN on successful execution; FAILURE_RETURN on error.
 */
int exec(char *str, int *pc, Ptr *ptr)
{
    int tmp = 0;

    while (str[*pc]) {
        if (str[*pc] == '[') {
            tmp = (*pc);
            (*pc)++;
            if (exec_on_loop(str, pc, &ptr) == FAILURE_RETURN)
                return FAILURE_RETURN;
            if (ptr->val)
                *pc = tmp;
            continue;
        }
        if (exec_ope(str[*pc], &ptr) == FAILURE_RETURN)
            return FAILURE_RETURN;
        (*pc)++;
    }
    return SUCCESS_RETURN;
}

https://gitlab.aliens-lyon.fr/adouard/brainfuck_interpreter

Commentaires



struct en_fl rep = {.en = somme, .fl = (float) somme / n};


typedef struct en_fl enfl;


return (struct en_fl) {.en = somme, .fl = (float) somme / n};


if (s == ALIVE) {
    return 1;
  } else {
    return 0;
  }
  
  
return (s == ALIVE) ? 1 : 0;


inline bool Timer_finished(Timer timer) {
  return GetTime() - timer.start >= timer.length;
}


Ptr *incr_ptr(Ptr *ptr);


Cell *get_cell_right(Cell* cell);

Cell *incr(Cell *cell);




int *p = malloc(sizeof(int);
if(!p)
{
    printf("Out of memory!\n");
    exit(EXIT_FAILURE);
}
/*opération que l'on doit pouvoir faire sur T:
    empiler dessus
    trouver le pivot
    le trier par angle croissant
    savoit si un de ses elément est a droite d'un segment
    
    sur S:
    le depiler*/

struct point {
    int x;
    int y;
};
typedef struct point point;
point p; //global variable (c'est le mal mais il faut que la fonction comp ait seulement deux arguments pour l'utiliser dans qsort)
/*Find the pivot of a table( which is the point with the lowest ordonnée (and lowest absisse if egality))
*@param table T
*@param size of T*/
point pivot(T[],size_t size){
    piv = T[0];
    for (int i=1;i<size,i++){
        if (piv.y>T[i].y) piv = T[i]
        if (piv.y=T[i].y) {
            if (piv.x=T[i].x) piv = T[i]
        }
    }
}

int main() {
  size_t n = 1000;
  size_t elem_size = sizeof(int);
  int* tab = malloc(elem_size * n);

  // Create an array of random integers
  printf("initial = [ ");
  for(int i = 0; i < n; i++)  {
    tab[i] = rand() % n;
    printf("%d ", tab[i]);
  }
  printf("]\n");

  // Then Sort it
  qsort(tab, n, elem_size, compareInt);

  printf("sorted = [ ");
  for(int i = 0; i < n; i++)  {
    printf("%d ", tab[i]);
  }
  printf("]\n");
}
/**
 * @brief Affiche les points ainsi que l'nveloppe convexe
 * 
 * @param T L'ensemble des points
 * @param S Les points dans l'ordre de l'enveloppe convexe
 */
void draw_point(dynarray* T, dynarray* S) {
    ClearBackground(WHITE);
    for (int i = 0; i < T->size; ++i) {
        DrawCircle(T->begin[i].x, T->begin[i].y, 3, BLUE);
    }
    for (int i = 0; i < S->size; ++i) {
        DrawLineV(S->begin[i], S->begin[(i+1)%S->size], RED);
    }
}
/**
 * @brief Ajoute un élément à la fin du tableau
 * 
 * @param array Le tableau
 * @param x L'élément à ajouter
 */
void push(dynarray* array, element x) {
    if (array->capacity < array->size + 1) {
        int new_capacity = 9 * array->capacity / 8 + 4;
        element* new_begin = malloc(sizeof(*new_begin) * new_capacity);
        for (int i = 0; i < array->capacity; ++i) {
            new_begin[i] = array->begin[i];
        }
        free(array->begin);
        array->begin = new_begin;
        array->capacity = new_capacity;
    }
    array->begin[array->size++] = x;
}
void exo1(){
	float div(int a, int b){return (float) a / b;}
	float x=7;
	/*int n=3.5;*/
	int* px=(int*) &x;
	/*float* pn=(float*) &n;*/
	const int n=7;
	const int* p=&n;}
float theta_point(point p){
    float theta;

    theta = acosf(p.x/(norm(p)));
    
    if (p.y < 0){
        theta = 2*M_PI -theta;
    }
    return theta;
}
void trie_tab_points(point* tab, int len, point pivot){
    PIVOT = pivot;
    qsort(tab, len, sizeof(point), comparePoints);
}
void tri(point tab[],int n, point p){
    qsort(tab,n,sizeof(point),compare_angle);
}
void push(dynarray* tab, int x){
    if ((*tab).capacity==0){ //si le tableau est vide on lui alloue ses premières cases mémoire
        (*tab).begin = malloc(1);
        (*tab).capacity=1;
        (*tab).size=1;
        *((*tab).begin)=x;
    }
    else{
        if ((*tab).size+1>(*tab).capacity){ // si il est rempli on en construit un plus grand et on recopie les valeurs déjà présentes
            int n = 9*((*tab).capacity)/8+4;
            int* p = malloc(n);
            for (int i = 0;i<(*tab).capacity;i++){
                *(p+i)=*((*tab).begin+i);
            }
            (*tab).begin=p;
            (*tab).capacity=n;
            (*tab).size=(*tab).size+1;
        }
        else{ // sinon on rajoute simplement une valeur au bout
            *((*tab).begin+(*tab).size)=x;
            (*tab).size=(*tab).size+1;
        }
    }
}

/**
 * @brief remove the top element from the stack and free the memory
 * 
 * @param the current (top of) stack 
 * @return the new (top of) stack
 */
tStack pop(tStack s)
{
    tStack next = s->next;
    free(s);
    return s->next;
}


VS

/**
 * @brief remove the top element from the stack and modify in place the stack to point the next element
 * 
 * @param a pointer (to the top of) the stack
 */
void pop(tStack * const s)
{
    tStack next = (*s)->next;
    free(*s);
    *s = next;
}

Reviews 4

#include <stdio.h>
#define NB_MAXCHAR 10
#define TAILLE_TABLE 50

struct cell{
    char key[50];
    int val;
};



int hash(char s[]){
    int sum = 0;
    int i = 0;
    while(s[i]!=0){
        sum = sum + (i+1)*s[i];
        sum = sum%TAILLE_TABLE;
    }
    return sum;
}

void addHash(char x[], struct cell table[]){
    int i = hash(x);
    while(table[i]!=NULL){
        i = (i+1)%TAILLE_TABLE;
    }
    struct cell a;
    


}

int estMin(char c){
    if (c>=97 &&c<=122){
        return 1;
    } 
    else {
        return 0;
    }
}

char majusculeChar(char c){
    if (estMin(c)){
        return c-32;
    }
    else{
        return c;
    }
}

void afficher_autour(char chaine[]){
    int i = 0;
    for(i;i<=50;i++){
        printf("%d\n",chaine[i]);
    }
    printf("\n");
}
int const TAILLE_TABLE = 1000;
int const TAILLE_MAX=30;

struct cellule
{
   char tab_hash[30];
   int val;
   int key;
};

typedef struct cellule cellule;

struct table
{
   cellule **tab;
   int taille;
};



typedef struct table table;

int hash (char s[], table* tab){
   int i=0;
   int som=0;
   while (s[i]!='\0')
   {
       som+=(i+1)*( (unsigned int) s[i]);
       i+=1;
   }
   return (som % tab->taille);
}

table* init (int taille){
   table* tab=malloc(sizeof(table));
   tab->taille=taille;
   tab->tab=malloc(sizeof(cellule*)*taille);
   for (int i = 0; i < taille; i++)
   {
       tab->tab[i]=malloc(sizeof(cellule));
       tab->tab[i]->key= -1;
       tab->tab[i]->val=0;
   }
   return tab;
}


void delete (table* tab){
   for (int i = 0; i < tab->taille; i++)
   {
       free(tab->tab[i]);
   }
   free(tab->tab);
   free(tab);
}

void add (char s[], table *tab){
   /*A utilser que si s n'est pas dans tab*/
   int ind=hash(s,tab);
   int key=ind;
   int k=0;
   while ((tab->tab[ind]->key != -1)&& (k != tab->taille))
   {
       k+=1;
       ind=(ind+1) % tab->taille;
   }
   printf("%d,%d,%d\n",k,key,ind);
   tab->tab[ind]->key=key;
   int i=0;
   while (s[i]!='\0')
   {
       tab->tab[ind]->tab_hash[i]=s[i];
       i+=1;
   }
   tab->tab[ind]->tab_hash[i]=s[i];

}

int recherche (char s[], table *tab){
   int ind=hash(s,tab);
   int key=ind;
   int k=0;
   bool trouve=false;
   int i=0;
   while ((k != tab->taille)&&trouve)
   {
       if (tab->tab[ind]->key==key)
       {
           trouve=true;
           i=0;
           while ((s[i]!='\0')&&(tab->tab[ind]->tab_hash[i] != '\0'))
           {
               trouve=trouve && (tab->tab[ind]->tab_hash[i]==s[i]);
               i+=1;
           }
           trouve=trouve && (tab->tab[ind]->tab_hash[i]==s[i]);
       }
       
       k+=1;
       ind=(ind+1) % tab->taille;
   }
   if (k==tab->taille)
   {
       return -1;
   }
   else
   {
       return ind;
   }
   
   
}

void occurence (char s[], table *tab){
   int ind=recherche(s,tab);
   if (ind==-1)
   {
       add(s,tab);
   }
   else
   {
       tab->tab[ind]->val+=1;
   }
   
}



int main(int argc, char *argv[])
{
   table * tab=init(TAILLE_TABLE);
   assert(argc>=2);
   char *fichier =argv[1];
   char mot[51];
   FILE* fd= fopen(fichier, "r");
   while (fscanf(fd, "%50s ", mot)==1)
   {
       occurence(mot,tab);
   }
   fclose(fd);
   delete(tab);
   return 0;
}

#ifndef HASHTABLE_H
#define HASHTABLE_H

#include <stdbool.h>

typedef struct cell {
    int val;
    char* key;
    struct cell* next;
}* Cell;

typedef struct table {
    int size;
    int used;
    Cell* cells;
} HashTable;

int hash(const char* key, const int size_tableau);
Cell create_cell(const char* key, const int val, const Cell next);
HashTable create_hash_table(const int size);
void free_hash_table(HashTable t);
void insert(HashTable* t, const char* key, const int val);
bool mem(const HashTable t, const char* key);
Cell find(const HashTable t, const char* key);
void double_size(HashTable* t);
void pop(HashTable* t, const char* key);

#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "HashTable.h"

/**
 * @brief Hash une chaine de caractère modulo size_table
 * 
 * @param key La chaine à hasher
 * @param size_table La taille de la table de hashage
 * @return Le hash de la chaine
 */
int hash(const char* key, const int size_table) {
    int h = 0;
    for (int i = 0; key[i] != 0; ++i)
        h = (h + (i + 1) * (unsigned int)key[i]) % size_table;
    return h;
}

/**
 * @brief Créer une cellule
 * 
 * @param key Le champ key (qui sera copier)
 * @param val Le champ val
 * @param next Le cellule suivante
 * @return La cellule créée
 */
Cell create_cell(const char* key, const int val, const Cell next) {
    Cell c = malloc(sizeof(*c));
    c->val = val;
    c->key = malloc(sizeof(*c->key) * (strlen(key) + 1));
    c->next = next;
    strcpy(c->key, key);
    return c;
}

/**
 * @brief Créer une table de hashage vide
 * 
 * @param size La taille initiale de la table
 * @return La table de hashage créée
 */
HashTable create_hash_table(const int size) {
    return (HashTable){.size = size, .used = 0, .cells = calloc(size, sizeof(Cell))};
}

/**
 * @brief Libère un table de hashage
 * 
 * @param t La table de hashage à liberer
 */
void free_hash_table(HashTable t) {
    for (int i = 0; i < t.size; ++i)
        for (Cell c = t.cells[i]; c != NULL;) {
            Cell mem = c->next;
            free(c->key);
            free(c);
            c = mem;
        }
    
    free(t.cells);
}

/**
 * @brief Insère un couple clef valeur dans une table de hashage
 * 
 * @param t La table
 * @param key La clef (qui sera copier)
 * @param val La valeur
 */
void insert(HashTable* t, const char* key, const int val) {
    if (100 * t->used > 75 * t->size) double_size(t);
    int i = hash(key, t->size);
    t->cells[i] = create_cell(key, val, t->cells[i]);
    ++t->used;
}

/**
 * @brief Cherche si une clef est présente dans une table
 * 
 * @param t La table
 * @param key La clef
 * @return true Si présent
 * @return false Sinon
 */
bool mem(const HashTable t, const char* key) {
    int i = hash(key, t.size);
    for (Cell c = t.cells[i]; c != NULL; c = c->next)
        if (strcmp(key, c->key) == 0) return true;
    return false;
}

/**
 * @brief Trouve un élément dans une table et le renvoie (si plusieur occurence renvoie l'ajouter le plus récent)
 * 
 * @param t Le table
 * @param key La clef
 * @return La cellule trouvée, NULL sinon
 */
Cell find(const HashTable t, const char* key) {
    int i = hash(key, t.size);
    for (Cell c = t.cells[i]; c != NULL; c = c->next)
        if (strcmp(key, c->key) == 0) return c;
    return NULL;
}

/**
 * @brief Retourne une liste de chainée de Cell (conserve les cellules mais retourne leurs liens)
 * 
 * @param c La liste de cellule
 * @return Le premier élément de la liste retournée
 */
Cell reversed(Cell c) {
    Cell r1 = c;
    Cell r2 = NULL;
    while (r1 != NULL) {
        Cell mem = r1->next;
        r1->next = r2;
        r2 = r1;
        r1 = mem;
    }
    return r2;
}

/**
 * @brief Double la taille d'une table de hashage (en place)
 * 
 * @param t La table
 */
void double_size(HashTable* t) {
    int new_size = t->size != 0 ? 2 * t->size : 7;
    Cell* cells = calloc(new_size, sizeof(Cell));

    for (int i = 0; i < t->size; ++i) {
        Cell c = reversed(t->cells[i]);
        for (Cell r = c; r != NULL;) {
            Cell mem = r->next;
            int j = hash(c->key, new_size);
            r->next = cells[j];
            cells[j] = r;
            r = mem;
        }
    }

    free(t->cells);
    t->cells = cells;
    t->size = new_size;
}

/**
 * @brief Supprime un élément du tableau par sa clef (si plusieurs éléments, supprime le plus récents)
 * 
 * @param t La table
 * @param key La clef
 */
void pop(HashTable* t, const char* key) {
    int i = hash(key, t->size);
    if (t->cells[i] == NULL) return;
    if (strcmp(key, t->cells[i]->key) == 0) {
        Cell mem = t->cells[i];
        t->cells[i] = t->cells[i]->next;
        free(mem->key);
        free(mem);
        --t->used;
        return;
    }
    for (Cell c = t->cells[i]; c->next != NULL; c = c->next)
        if (strcmp(key, c->next->key) == 0) {
            Cell mem = c->next;
            c->next = c->next->next;
            free(mem->key);
            free(mem);
            --t->used;
            return;
        }
}

#ifndef READ_FILE_H
#define READ_FILE_H

int first_example(char*);

#endif
int first_example(char* fichier) {
    char mot[51]; // on laisse une case pour le '\0' final
    FILE* fd  = fopen(fichier, "r"); // fopen ferme le fichier comme le nom l'indique
    while (fscanf(fd, "%50s ", mot) == 1) {
        printf("%s ", mot);
    }
    fclose(fd); // fclose ouvre le fichier comme son nom l'indique (c'est bizare qu'il ne soit pas placer avant fopen)
    return EXIT_SUCCESS;
}
#ifndef INTRODUCTION_H
#define INTRODUCTION_H

#define NB_MAXCHAR 10

int is_lowercase(char);
char upper(char);
void majuscule(char*);

#endif

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "introduction.h"

// --------- EXERCICE 1 : Caractères ---------

/**
 * @brief Détermine si un caractère est une lettre minuscule
 * 
 * @param c Le caractère
 * @return 1 si lettre minuscule, 0 sinon
 */
int is_lowercase(char c) {
    return ('a' <= c && c <= 'z') ? 1 : 0;
}

/**
 * @brief Passe en majuscule un caractère si celui ci est une lettre minuscule
 * 
 * @param c Le caractère
 * @return La lettre en majuscule, sinon le caractère initial
 */
char upper(char c) {
    return is_lowercase(c) ? (c - 'a' + 'A') : c;
}

// character too large for enclosing character literal type

// --------- EXERCICE 2 : Chaines de caractères ---------

/**
 * @brief Passe tous les lettres minuscules en majuscule en place dans la chaine de caractère
 * 
 * @param chaine La chaine à passer en majuscule
 */
void majuscule(char* chaine) {
    for (int i = 0; chaine[i] != 0; ++i) chaine[i] = upper(chaine[i]);
}

// Ce n'est pas étrange, seulement on a enlevé la sentinelle à la fin de la chaine donc il affiche tant qu'il trouve un nouveau 0 dans la suite, mais très mauvais car lis dans de la mémoire non initialisée


// --------- EXERCICE 3 : Tableaux de chaines de caractères ---------
#include <stdio.h>
#include <stdlib.h>
#define TAILLE_TABLE 5000
#define TAILLE_CHAR 101 // On limite la taille des mots avec lesquels on va jouer

typedef struct
{
    char *key;
    int val;
} cell;

typedef cell table[TAILLE_TABLE];




/*
Affiche le string s, en changeant les minuscules en majuscules
*/
void je_hurle(char s[])
{
    for (int i = 0; s[i] != '\0'; i++)
    {
        if (s[i] >= 'a' && s[i] <= 'z')
        {
            printf("%c", 'A' - 'a' + s[i]);
        }
        else
        {
            printf("%c", s[i]);
        }
    }
}


/*Exercice 5 : création table de hachage*/


int hash(char s[])
{
    int r = 0;
    for (int i = 0; s[i] != '0'; i++)
    {
        r += ((i + 1) * s[i]) % TAILLE_TABLE;
    }
    return r;
}

void add_word(char s[], table t)
{
    int h = hash(s);
    while (t[h].val != 0 && t[h].key != s)
    { // On ne traite pas le cas : table remplie.
        h = (h + 1) % TAILLE_TABLE;
    }
    t[h].key = s;
    t[h].val += 1;
}

cell *find_word(char s[], table t)
{
    int h = hash(s);

    while (t[h].val != 0 && t[h].key != s)
    {
        h = (h + 1) % TAILLE_TABLE;
    }
    if (t[h].key == 0)
        return NULL;
    return &(t[h]);
}

void free_hash(table t)
{
    for (int i = 0; i < TAILLE_TABLE; i++)
    {
        free(t[i].key);
    }
    free(t);
}

cell *init_hash()
{
    cell *t = malloc(sizeof(cell) * TAILLE_TABLE);
    for (int i = 0; i < TAILLE_TABLE; i++)
    {
        t[i].key = NULL;
        t[i].val = 0;
    }
    return t;
}

cell *cell_max(table t)
{
    int max = 0;
    for(int i = 1; i< TAILLE_TABLE; i++){
        if( t[max].val < t[i].val) max = i;
    }
    return &(t[max]);
}

/*Exercice 4*/
void lecture_fichier(char file[], table t)
{
    char mot[TAILLE_CHAR];
    FILE *fd = fopen(file, "r"); // ouvre fichier filer en mode 'read', existe aussi en w et x

    while (fscanf(fd, "%100s ", mot) == 1)
    { // lis un motif spécifique, l'injecte dans mot.
        // fais quelque chose avec le fichier
        printf("%s\n", mot);
        add_word(mot, t);
    }
    fclose(fd); // on ferme une fois qu'on est passé, question de respect
}


int main()
{

    char s[] = "Salut t'a bien dormi ? :')\n ";
    printf("%s", s);
    je_hurle(s);

    // Exercice 4
    char donne_moi_un_nom_de_fichier[50];
    int res = scanf("%s", donne_moi_un_nom_de_fichier);

    // Exercice 5
    cell* t = init_hash();
    if (res == 1){
        lecture_fichier(donne_moi_un_nom_de_fichier, t);
        cell* p = cell_max(t);
        printf(" Le mot le plus récurrent est %s, avec %d apparition\n", p->key, p->val);
    }


    return 0;
}
#ifndef HASHMAP_H_
#define HASHMAP_H_

#include <stdbool.h>
#include <stddef.h>

typedef char* K;
typedef int V;

/// The "liveness" state of a cell in a map
typedef enum {
  __CellState_EMPTY,
  __CellState_FILLED,
  /// Used to indicate cells that have been deleted from the map
  __CellState_TOMBSTONE
} __CellState;

/// An entry in a map
typedef struct {
  __CellState state;
  K key;
  V value;
} __HashMapRecord;

/// A `HashMap` implements a mutable non-persistend dictionary/associative
/// array/map
typedef struct {
  size_t cap;
  /// the number of occupied cells
  size_t len;
  __HashMapRecord* table;
} HashMap;

/// Allocate a new HashMap. If `initial_capacity` is set to 0, no allocation is
/// performed. It is recommended, for hashing performance reasons, to choose a
/// power of 2 as the capacity.
HashMap HashMap_new(size_t initial_capacity);
/// Free the memory used by map. Takes ownership.
void HashMap_free(HashMap map);
/// Insert a new association for `key`. If one already existed, it is
/// overwritten.
void HashMap_insert(HashMap* m, K key, V value);
/// returns true iff `m` contains `key`. In that case `out` is set to the stored
/// value.
bool HashMap_get(HashMap* m, K key, V* out);
/// Deletes the record for `key` in the map, if it exists.
/// Otherwise, nothing happens.
void HashMap_delete(HashMap* m, K key);

#endif // HASHMAP_H_
#include "hashmap.h"
#include <stdlib.h>

HashMap HashMap_new(size_t initial_capacity) {
  __HashMapRecord* table = NULL;
  if (initial_capacity > 0) {
    table = malloc(initial_capacity * sizeof(__HashMapRecord));
  }
  for (int i = 0; i < initial_capacity; i++) {
    table[i].state = __CellState_EMPTY;
  }
  return (HashMap){.cap = initial_capacity, .table = table, .len = 0};
}

void HashMap_free(HashMap map) {
  free(map.table);
}

size_t __hash_string(const char* string, size_t modulus) {
  int i = 1;
  size_t hash = 0;
  while (*string != '\0') {
    hash += i * *string % modulus;
    i++;
    string++;
  }
  return hash;
}

size_t __index_of_insert(HashMap* map, K key, bool* overwriting) {
  *overwriting = false;
  size_t increment = 1;
  size_t hash = __hash_string(key, map->cap);
  while ((map->table[hash].state == __CellState_FILLED &&
          map->table[hash].key != key) ||
         map->table[hash].state == __CellState_TOMBSTONE) {
    hash = (hash + increment) % map->cap;
    increment++;
  }
  return hash;
}

/// Insert in a map without performing dynamic resizing
void __insert_no_resize(HashMap* map, K key, V value) {
  bool overwriting;
  size_t hash = __index_of_insert(map, key, &overwriting);
  map->table[hash] = (__HashMapRecord){
      .state = __CellState_FILLED, .key = key, .value = value};
  if (!overwriting)
    map->len++;
}

void __resize(HashMap* map, size_t new_size) {
  HashMap new_map = HashMap_new(new_size);
  for (int i = 0; i < map->cap; i++) {
    __HashMapRecord record = map->table[i];
    if (record.state == __CellState_FILLED) {
      __insert_no_resize(&new_map, record.key, record.value);
    }
  }
  map->table = new_map.table;
  map->cap = new_size;
}

void HashMap_insert(HashMap* m, K key, V value) {
  if (m->len * 2 >= m->cap) {
    if (m->cap == 0)
      __resize(m, 1);
    else
      __resize(m, m->cap * 2);
  }
  __insert_no_resize(m, key, value);
}

size_t __index_of_get(HashMap* m, K key) {
  // NOTE this relies on the fact that empty cells exist.
  // If it is not the case this may not terminate.
  size_t hash = __hash_string(key, m->cap);
  size_t increment = 1;
  while ((m->table[hash].state == __CellState_FILLED &&
          m->table[hash].key != key) ||
         m->table[hash].state == __CellState_TOMBSTONE) {
    hash = (hash + increment) % m->cap;
    increment++;
  }
  return hash;
}

bool HashMap_get(HashMap* m, K key, V* out) {
  size_t hash = __index_of_get(m, key);
  __HashMapRecord rec = m->table[hash];
  if (rec.state == __CellState_FILLED && rec.key == key) {
    *out = rec.value;
    return true;
  }
  return false;
}

void HashMap_delete(HashMap* m, K key) {
  size_t hash = __index_of_get(m, key);
  __HashMapRecord rec = m->table[hash];
  if (rec.state == __CellState_FILLED && rec.key == key) {
    m->table[hash].state = __CellState_TOMBSTONE;
  }
}
#include <stdlib.h>
#include <stdio.h>



int est_min(int n) {
    if (n>= 'a' && n<= 'z')
        return 1;
    else
        return 0;
}


char maj(char c) {
    if (est_min(c))
        return (c- 'a')+'A';
    
    else 
        return c;
    
}



void majuscule(char chaine[]) {
    int i=0;
    while (chaine[i] != '\0') {
        chaine[i]=maj(chaine[i]);
        i++;
    }
}

void exo2() {
    char* s=malloc(7*sizeof(char));
    s[0]='c';
    s[1]='o';
    s[2]='u';
    s[3]='c';
    s[4]='o';
    s[5]='u';
    s[6]='\0';
    for (int i=0; i<7; i++) {
        printf("%c", s[i]);
    };
    printf("\n");

    char chaine[]= "coucou";
    printf("%s \n", chaine);

    majuscule(chaine);
    printf("%s \n", chaine);
}

void exo2b() {
    char chaine[]="Bonjour le monde";
    printf("%s \n", chaine);
    chaine[16]='!';
    printf("%s \n", chaine);
}


void exo4 () {
    char fichier[]="input.txt";
    char mot[51];
    FILE* fd=fopen(fichier, "r");
    while(fscanf(fd, "%50s ", mot)==1) {
        printf("%s ", mot);
    }
    printf("\n");
    fclose(fd);
}


int TAILLE_TABLE=100;

int hash(char s[]) {
    int sum=0;
    int i=0;
    while (s[i] != '\0') {
        sum+=(i+1)*((unsigned int) s[i]);
        i++;
    }
    return(sum % TAILLE_TABLE);
}





void ajoute(x, h) {
    int i=hash(x);
    while (h[i]!=-1){
        i=(i+1)%TAILLE_TABLE;
    }
    h[i]=x;
}


*char cherche(x,h) {
    for (i=0; i<TAILLE_TABLE; i++) {
        if (h[i]==x) {
            return(h[i]*)
        }
    }
    return (*char NULL)
}


int main() {
    printf("%d", hash("Hello World"));
}


#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdbool.h>

#define TAILLE_TABLE 100
#define TAILLE_MOT 50


/**Exercices d'introduction */

int minuscule (char c){
    return (c >= 'a' && c <= 'z');
}

void test_minuscule(){
    printf("test minuscule.............. ");
    assert(minuscule('c'));
    assert(!minuscule('E'));
    assert(!minuscule('!'));
    printf("OK\n");
}

/**Exercices principaux */

/**Ex 5 */

int hash(char* str){
    int res = 0;
    int i = 0;
    while(str[i] != '\0'){
        res = (res + (unsigned int)((str[i])*(i+1))) % TAILLE_TABLE;
        i++;
    }
    assert(res >= 0 && res < TAILLE_TABLE);
    return res;
}

typedef struct tuple{
    char* key;
    int val;
} tuple;


//on est en adressage ouvert
typedef struct table{
    int nbr_elems;
    tuple* tableau;
} table;

void add(char* str, table* hashtbl){
    if (hashtbl->nbr_elems == TAILLE_TABLE){
        assert(false);
    }
    int hash_value = hash(str);
    while(!(strcmp(hashtbl->tableau[hash_value].key, "") == 0) && !(strcmp(hashtbl->tableau[hash_value].key, str) == 0)){
        hash_value = (hash_value + 1) % TAILLE_TABLE;
    }
    if(strcmp(hashtbl->tableau[hash_value].key, "") == 0){
        hashtbl->nbr_elems++;
        char* new_key = (char*)malloc(sizeof(char)*TAILLE_MOT);
        strcpy(new_key, str);
        tuple new_tuple = {.key = new_key, .val = 1};
        hashtbl->tableau[hash_value] = new_tuple;
    }
    else{
        hashtbl->tableau[hash_value].val++;
    }
    
}

//il reste toujours au moins une case vide d'après add
tuple* search(char* str, table* hashtbl){
    int hash_value = hash(str);
    while(!(strcmp(hashtbl->tableau[hash_value].key, "") == 0) && !(strcmp(hashtbl->tableau[hash_value].key, str) == 0)){
        hash_value = (hash_value +1) % TAILLE_TABLE;
    }
    if (strcmp(hashtbl->tableau[hash_value].key, "") == 0){
        return NULL;
    }else{
        return &(hashtbl->tableau[hash_value]);
    }
}

void libere(table *hashtbl){
    for (int i = 0; i < TAILLE_TABLE; i++){
        free(hashtbl->tableau[i].key);
    }
    free(hashtbl->tableau);
    free(hashtbl);
}

table* table_vide(){
    table* hashtbl = (table*)malloc(sizeof(table));
    tuple* tableau = (tuple*)malloc(sizeof(tuple)*TAILLE_TABLE);
    for (int i = 0; i < TAILLE_TABLE; i++){
        tableau[i].key = (char*)malloc(sizeof(char)*TAILLE_MOT);
        strcpy(tableau[i].key, "");
        tableau[i].val = 0;
    }
    hashtbl->tableau = tableau;
    hashtbl->nbr_elems = 0;
    return hashtbl;
}

void count_occ(){
    table* hashtbl = table_vide();
    char fichier[] = "input.txt";
    char mot[TAILLE_MOT+1];
    FILE* fd = fopen(fichier, "r");
    while (fscanf(fd, "%50s ", mot) == 1){ // TAILLE_MOT = 50
        add(mot, hashtbl);
    }
    fclose(fd);
    printf("Liste des occurences :\n");
    for (int i = 0; i < TAILLE_TABLE; i++){
        if (!(strcmp(hashtbl->tableau[i].key, "") == 0)){
            printf("%s : ", hashtbl->tableau[i].key);
            printf("%d\n", hashtbl->tableau[i].val);
        }
    }
    libere(hashtbl);
}

int main(){
    count_occ();
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define TAILLE_TABLE 1000
#define TAILLE_MOT 50 


struct cellule {
    char key[TAILLE_MOT] ;
    int val;
}; 
typedef struct cellule cel ;

/// @brief Fonction qui renvoie la position de chaine dans notre tableau 
/// @param chaine 
/// @return Un entier entre 0 et TAILLE_TABLE
int hash(char chaine[]){
    int i = 0 ; 
    int somme = 0;
    while (chaine[i] != '\0'){
        somme += (i+1)* chaine[i] ;
        i++ ;
    }
    return somme % TAILLE_TABLE ;
} 

typedef cel* table[TAILLE_TABLE] ;

/// @brief Rajoute un mot dans la table d'hashage, si elle est pleine renvoie une erreur. 
/// @param t Une table de Hashage de taille TAILLE_TABLE
/// @param mot Une chaine de caractère
void add(table t, char mot[]) {
    int pos = hash(mot) ;
    int i = 0 ;

    // On cherche la bonne position, i permet de ne pas boucler à l'infini, dans le cas ou le tableau est plein, la valeur est réecrite 
    while ( (t[pos % TAILLE_TABLE]->val != 0)  && (strcmp(t[pos % TAILLE_TABLE]->key,  mot)) && (i < TAILLE_TABLE)) {pos ++ ; i++;}

    // On update la valeur de la case pose, 
    if (t[pos]->val == 0) {
        t[pos]->val = 1 ;
        strcpy(t[pos]->key, mot); }
    else {
        t[pos]->val += 1;
    }

    // Renvoie d'une erreur si le tableau est plein. 
    if (i == TAILLE_TABLE) {printf("Erreur, tableau plein");}

}

/// @brief  Cherche la case ou se situe le mot dans la table de hashage, 
///         renvoie un pointeur vers celle-ci si elle existe. Sinon un pointeur vide
/// @param t La table de hashage étudié
/// @param mot Une chaine de caractère recherché
/// @return Un pointeur vers une cellule ou un pointeur vide
cel* find(table t, char mot[]) {
    for (int i = 0; i < TAILLE_TABLE; i++){
        if (strcmp (t[i]->key , mot)) {return t[i] ;} 
    }
    return NULL ;
}



void init(table t) {
    for (int i = 0; i < TAILLE_TABLE; i++){
        t[i] = malloc(sizeof(cel));
        t[i]->val = 0 ;
    }
}


void occurences(table t, char file[]) {
    char mot[51];
    FILE* fd = fopen(file, "r") ;
    while (fscanf(fd, "%50s ", mot) == 1) {
        add (t , mot) ;
    }
    fclose(fd);
} 


void affichage (table t) {
    for (int i = 0; i < TAILLE_TABLE; i++){
        printf("%s, %d", t[i]->key, t[i]->val);
    }

}



int main() {
    table t; 
    init(t) ;
    occurences(t, "input.txt");
    affichage (t) ;


    return 0;
}
# include <stdio.h>
/// @brief  Lis un fichier et affiche les mots un par un 
/// @return 0 
int main() {
    char fichier[] = "input.txt";
    char mot[51];
    FILE* fd = fopen(fichier, "r") ;
    while (fscanf(fd, "%50s ", mot) == 1) {
        printf("%s \n", mot) ;
    }
    fclose(fd);
    return 0;
}
#include <stdlib.h>

/// @brief structure de cellule d'une table de hashage
typedef struct cellule
{
    char *key;
    int val;
    struct cellule *suite;
} cell;

typedef struct{
    cell** contenu;
    int taille;
    int nb_elements;
} table;

unsigned int hash(char s[], int taille_tbl)
{
    unsigned int c;
    unsigned int somme;
    int i = 0;

    while (s[i] != '\0')
    {
        c = s[i];
        somme += c * (1 + i);
        ++i;
    }

    somme = somme % taille_tbl;

    return somme;
}

void ajoute(char key[], int val, table tbl)
{
    unsigned int i = hash(key, tbl.taille);
    cell **ptr = &(tbl.contenu[i]);

    while (*ptr != NULL)
    {
        ptr = &((*ptr)->suite);
    }

    *ptr = malloc(sizeof(cell));
    (*ptr)->key = key;
    (*ptr)->val = val;
    (*ptr)->suite = NULL;
}

table resize(table tbl){
    table new_tbl;
    new_tbl.nb_elements = tbl.nb_elements;
    new_tbl.taille = 2*tbl.taille;
    new_tbl.contenu = malloc(new_tbl.taille * sizeof(cell *));

    //ajouter à new_tbl les éléments de tbl (+ ou - recursif car listes chainées)
}

void ajoute_resize(char key[], int val, table* tbl){
    if (tbl->nb_elements + 1 > 0.75 * tbl->taille){
        *tbl = resize(*tbl);
    }
    ajoute(key, val, *tbl);
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>

int TAILLE_TABLE = 100;

typedef struct Cell{
    char* key;
    int val;
}cell;

typedef struct Cell_List{
    cell cellule;
    struct Cell_List* suiv;
}cell_lst;

typedef struct Hashtable{
    cell_lst** table; // de taille TAILLE_TABLE
}hashtable;


int hash(char* s){
    int res = 0;
    int len = strlen(s);
    for(int i=0; i<len; i++){
        res = (res + (i+1)*s[i]) % TAILLE_TABLE;
    }
    assert(0 <= res && res < TAILLE_TABLE);
    return res;
}


hashtable* creer_hsht(){
    hashtable* ht = malloc(sizeof(hashtable));
    ht->table = malloc(sizeof(cell_lst*)*TAILLE_TABLE);
    for(int i=0; i<TAILLE_TABLE; i++){
        ht->table[i] = NULL;
    }
    return ht;
}

void ajout_hsht(char* s, int val, hashtable* ht){
    int h = hash(s);
    cell new_cell = {s, val};
    cell_lst* new = malloc(sizeof(cell_lst));
    new->cellule = new_cell;
    new->suiv = ht->table[h];
    ht->table[h] = new;
}


cell* cherche_hsht(char* s, hashtable* ht){
    int h = hash(s);
    for(cell_lst* l = ht->table[h]; l != NULL; l = l->suiv){
        if (strcmp(l->cellule.key, s)==0){ //si les deux chaines sont égales
            return &(l->cellule);
        }
    }
    return NULL;
}


void free_lst(cell_lst* lst){
    while (lst != NULL){
        cell_lst* old = lst;
        lst = lst->suiv;
        free(old);
    }
}

void free_hashtble(hashtable* ht){
    for(int i=0; i<TAILLE_TABLE; i++){
        free_lst(ht->table[i]);
    }
    free(ht->table);
    free(ht);
}



hashtable* compte_occurences(char** texte, int l_texte){
    hashtable* ht = creer_hsht();
    for(int i=0; i<l_texte; i++){
        cell* res = cherche_hsht(texte[i], ht);
        if(res == NULL){
            ajout_hsht(texte[i], 1, ht);
        }
        else{
            res->val ++;
        }
    }
    return ht;
}

void print_resultats(char** texte, int l_texte, hashtable* ht){
    /*for(int i=0; i<l_texte; i++){
        cell* res = cherche_hsht(texte[i], ht);
        printf("%s : %d\n", texte[i], res->val);
    }*/

    for(int i=0; i<TAILLE_TABLE; i++){
        for(cell_lst* lst = ht->table[i]; lst != NULL; lst = lst->suiv){
            printf("%s : %d\n", lst->cellule.key, lst->cellule.val);
        }
    }
}


char** lecture_texte(char fichier[], int* pl_texte){
    char mot[51];
    int l_texte = 0;

    //premier parcours pour avoir le nombre de mots
    FILE* fd = fopen(fichier, "r");
    while(fscanf(fd, "%50s ", mot) == 1){
        l_texte ++;
    }
    *pl_texte = l_texte;
    char** texte = malloc(sizeof(char*)*l_texte);
    int i=0;
    fclose(fd);

    //deuxième parcours pour avoir tous les mots
    fd = fopen(fichier, "r");
    while(fscanf(fd, "%50s ", mot) == 1){
        texte[i] = malloc(sizeof(char)*51);
        strcpy(texte[i], mot);
        //printf("%d %s\n", i, texte[i]);
        i++;
    }
    fclose(fd);
    return texte;
}


void print_texte(char** texte, int l_texte){
    for(int i=0; i<l_texte; i++){
        printf("%d, %s", i, texte[i]);
    }
}

int main(){
    int l_texte;
    char fichier[] = "input.txt";
    //printf("Début lecture texte\n");
    char** texte = lecture_texte(fichier, &l_texte);
    //printf("Fin lecture texte, l_texte = %d\n", l_texte);
    print_resultats(texte, l_texte, compte_occurences(texte, l_texte));
}
/**
 * @brief
 *
 * @param s
 * @return length of s
 * @example if s is "Bonjour" then it returns 7
 */
int strlen_homemade(const char *s)
{
    int c = 0;
    while (*s) // continue the loop when the current char is non-zero
    {
        s++; // move the pointer to the right
        c++;
    }
    return c;
}

Reviews 5

/**
 * @brief
 *
 * @param s
 * @return length of s
 * @example if s is "Bonjour" then it returns 7
 */
int strlen_homemade(const char *s)
{
    int c = 0;
    while (*s) // continue the loop when the current char is non-zero
    {
        s++; // move the pointer to the right
        c++;
    }
    return c;
}
void enlarge_tab(intarray* pa, size_t new_capacity) {
    pa->begin = realloc(pa->begin, new_capacity * sizeof(uint32_t));
    pa->capacity = new_capacity;
}
// Renvoie une copie du tableau a
intarray copy(intarray a) {
    // On copie ici le tableau utilisé en pratique, les éléments "superflues" (ie le tableau réel) sont ici ignorés
    uint32_t* t = malloc(a.size * sizeof(uint32_t));

    // On copie les éléments du tableau
    for (size_t k = 0; k < a.size; k++) {
	t[k] = a.begin[k];
    }

    return (intarray){t, a.size, a.size};
}
/**
 * @brief Retire le dernier élément du tableau
 * 
 * @param pa Le tableau
 * @return L'élément supprimé
 */
u_int32_t pop(intarray* pa) {
    assert(pa->size > 0);
    return pa->begin[--pa->size];
}

/**
 * @brief Cherche un élément dans le tableau
 * 
 * @param a Le tableau
 * @param i L'indice
 * @return L'élément trouvé si indice valide
 * @return 0 si indice non valide
 */
u_int32_t force_get(const intarray a, const int i) {
    if (0 > i || i >= a.size) return 0;
    return a.begin[i];
}
/*@brief Créer un tableau dynamique vide
@return Un tableau dynamique avec un pointeur quelquonque, size =0 et capacity = 0*/
intarray make(){
    u_int32_t *i = malloc(sizeof(u_int32_t *));
    intarray a = {i, 0 , 0};
    return a;
}
/*@brief Fonction qui rajoute un élément à un tableau dynamique (alloue de la mémoire si nécessaire)
@param le tableau dynamique
@param l'élément à ajouter
@return Rien*/
void push(intarray *pa , u_int32_t x){
    if (pa->size +1 > pa->capacity);{
         pa->begin = (u_int32_t*) realloc(pa->begin,(5/4)*(pa->capacity) +4);
         pa->capacity = (5/4)*(pa->capacity) +4;
    }
    pa->size += 1;
    pa->begin[pa->size]=x;
    }
/// @brief Insertion de l'élement x dans le tableau dynamique pointé par pa. Si besoin on augmente la capacité du tableau avec la formule new_capacity = 5/2*capacity +4
/// @param pa Un pointeur vers le tableau dynamique
/// @param x Un entier sur 32 bit. 
void push(intarray* pa, u_int32_t x) {
    if (pa->size < pa->capacity) {
        pa->begin[pa->size+1] = x ;
        pa->size ++ ;
    }  
    else {
        int new_capacity = 5/2*pa->capacity +4; 
        pa->begin = realloc(pa->begin, new_capacity*4 ) ;
        pa->begin[pa->size+1] = x ;
        pa->size ++ ;
        pa->capacity = new_capacity ; 
    }
}


/// @brief Copie le tableau a et renvoie un nouveau tableau, la capacité et size reste inchangé, les éléments sont insérés un par un. 
/// @param a Un tableau dynamique de type intarray
/// @return Un tableau dynamique b de type intarray.
intarray copy(intarray a) {
    intarray b = make() ;
    b.begin = malloc(a.capacity * 4) ;
    b.size = a.size ;
    for (int i=0;i<a.size+1; i++){
        b.begin[i] = a.begin[i] ;
    }
    return b ;
}

/// @brief Renvoie la valeur a[i] si elle existe sinon 0
/// @param a Un tableau dynamique de type intarray
/// @param i Un entier int
/// @return Un entier sur 32 bit soit appartenant au tableau ou 0.
u_int32_t force_get(intarray a , int i ) {
    if ( 0 <= i && i <= a.size ) {
        u_int32_t x = get(a, i) ;
        return x ;
    }
    u_int32_t x = 0 ; 
    return x ;
}
/*revoien un intarray de taille=capacité=0 pointant vers aucun tableau*/
intarray make () {
    intarray t;
    t.size=0;
    t.capacity=0;
    t.begin=NULL;
    return t;
}
/*Rajoute a la fin d'un tableau pointé par pa:intarray*  l'entier x: __uint32_t si pas assew de place realloue un tableau plus grand*/
void push(intarray* pa, __uint32_t x){
    if (pa->capacity>= (pa->size +1))
    {
        /* on a suffisment de place */
        pa->begin[pa->size]=x;
        pa->size=pa->size+1;
        

    }
    else
    {
        /*On creer un nouveau tableau*/
        pa->capacity=5/4*pa->capacity+4;
        __uint32_t* nv_begin=malloc(sizeof(__uint32_t)*(pa->capacity));
        for (int i = 0; i < pa->size; i++)
        {
            nv_begin[i]=pa->begin[i];
        }
        free(pa->begin);
        pa->begin=nv_begin;
        push(pa,x);
    }
}
/*Essaie de mettre x:__uint32_t a la case i;int du tableau pointé par pa:int array 
si pa pas assez long il souleve une erreur*/
void set (intarray a, int i, __uint32_t x){
    assert(i<a.size);
    /* On regarde si le ieme element du tableau existe deja pour pouvoir le modifier*/
    a.begin[i]=x;
}
/*Cherche l'element a l'indice i:int dans a:intarray, si existe pas alors renvoie 0 */
__uint32_t force_get(intarray a, int i){
    if (i<a.size)
    {
        return a.begin[i];
    }
    else
    {
        return 0;
    }   
}
// teste les fontions précédentes
void test_exo_1() {
  intarray a = make();
  push(&a, 1);
  push(&a, 2);
  push(&a, 3);
  print_array(a);
  assert(get(a, 1) == 2);
  set(a, 1, 7);
  assert(get(a, 1) == 7);
  u_int32_t element = pop(&a);
  print_array(a);
  reset(&a);
}
void print_value(big_int b)
{
    intarray digits = make();
    big_int temp = copy(b);
    while(temp.size > 0)
        push(&digits, tenth(&temp));
    for(int i = temp.size-1; i>=0; i--) printf("%u", temp.begin[i]);
    printf("\n");
    return;
}
// Ce fichier étant inlcus dans le fichier ex2.c, je ne peux mettre 2 fonctions main et donc je commente celle-ci.

// int main() {
//     intarray t = make();
//     push(&t, 15);
//     push(&t, 20);
//     push(&t, 5);
//     push(&t, 12);
//     print_array(t);
//     printf("L'élément d'indice 2 est : %u.\n", get(t, 2));
//     // printf("L'élément d'indice 6 est : %u.\n", get(t, 6));   // Test pour vérifier que l'assertion fonctionne bien.
//     set(t, 2, 55);
//     print_array(t);
//     printf("L'élément enlevé du haut de la pile était : %u.\n", pop(&t));
//     print_array(t);
//     reset(&t);
//     return 0;
// }
#include "ex1.c"
//on travaillera uniquement avec des pointeurs vers des intarray donc un seul malloc est ici nécessaire
intarray* make(){
    intarray* new = malloc(sizeof(intarray));
    new->begin = NULL;
    new->size = 0;
    new->capacity = 0;
    return new;
}
intarray* copy(intarray* pa){
    intarray* pb = malloc(sizeof(intarray));
    pb->capacity = pa->capacity;
    pb->size = pa->size;
    pb->begin = malloc(sizeof(uint32_t)*pb->capacity);
    for(int i=0; i<pb->size; i++){
        pb->begin[i] = pa->begin[i];
    }
    return pb;
}
intarray make(){ // On initialise le tableau vide par un pointeur nul, une capacité nulle et une taille de 0
    intarray tab;
    tab.begin=NULL;
    tab.capacity = 0;
    tab.size = 0;
    return tab;
}
u_int32_t pop(intarray* pa){
    assert((*pa).size>0); //Pour enlever le dernier élément du tableau, on commence par vérifier que la tableau n'est pas vide,
    (*pa).size=((*pa).size)-1; // On diminue ensuite la taille du tableau (il n'est pas nécessaire de modifier la valeur inscrite)
    return *((*pa).begin+(*pa).size); //On renvoie la dernière valeur de l'ancien tableau
}
//on créée dynamiquement un intarray sur la pile 
//il faut un pointeur sans quoi la durée de vie de l'objet crééé ne dépasse pas celui de la fonction
intarray* make() {
    intarray* t = malloc(sizeof(intarray));
    (*t).begin = NULL; (*t).size = 0; (*t).capa = 0;
    return t;
}
*/

int main(void) {
  // TEST EXERCICE 1 ET 2
  
  intarray test = make();
  for (int i = 0; i < 20; i++) {
    push(&test, i);
    if (i % 3 == 0) set(test, i / 2, i * 2);
  }
  for (int i = 0; i < 10; i++) {
    printf("%d\n", pop(&test));
  }
  printf("get : %d\n", get(test, 5));
  printf("force_get : %d\n", force_get(test, 5));
  printf("force_get : %d\n", force_get(test, 15));

  intarray test2 = copy(test);

  printf("Tableau test : ");
  print_array(test);

  force_set(&test2, 15, 30);
  push_front(&test, 50);

  printf("Tableau test : ");
  print_array(test);
  printf("Tableau test2 : ");
  print_array(test2);
  
  reset(&test);
  reset(&test2);

  //FIN TEST EXERCICE 1 ET 2

  // TEST EXERCICE 3

  big_int b = make();
  print_repr(b);
  push(&b, 3);
  push(&b, 15);
  push(&b, 7);
  push(&b, 0);
  push(&b, 0);
  push(&b, 0);
  print_repr(b);

  canonical(&b);
  print_repr(b);

  printf("%d\n", half(&b));
  reset(&b);
  
  return 0;
}
int main() {
  // Test every function
  test_intarray();
  test_bigint();
}

Critères

  • Est-ce que le dépôt mentionné dans le formulaire existe ou non ?
  • Utilisation de commentaires (si possible docstring, pas un blabla horrible)
  • Pas de commentaires utiles dans le corps d'une fonction
  • Utilisation de realloc au lieu de malloc si c'est pour réallouer
  • Gestion d'erreurs de malloc faite
  • Séparation en fichier .h et .c
  • Indentation bien faite et uniforme
  • Ecriture de tests
  • Pas d'utilisation de "5 / 4" qui vaut 1
  • Pas de pratique bizarre (inclure un fichier .c)
  • De combien l'étudiant.e est allé

Reviews 6

typedef struct trie {
    int val;
    struct trie* table[NB_LETTRES];
} trie;


trie* make_trie(){
    trie* a = malloc(sizeof(trie));
    for(int i=0; i<NB_LETTRES; i++){
        a->table[i] = NULL;
    }
    a->val = 0;
    return a;
}

void delete_trie(trie* pt){
    if(pt != NULL){
        for(int i=0; i<NB_LETTRES; i++){
            trie* fils = pt->table[i];
            delete_trie(fils);
        }
        free(pt);
    }
}

void add_char(char c, trie* pt){
    if(pt->table[c-'a'] == NULL){
        pt->table[c-'a'] = make_trie();
    }
}


void add_word(char word[], trie* pt){
    if(word[0] == '\0'){
        pt->val = 1;
    }
    else{
        char lettre = word[0];
        add_char(lettre, pt);
        add_word(&word[1], pt->table[lettre-'a']);
    }
}


trie* search_word(char word[], trie* pt){
    if(word[0]=='\0' && pt->val == 1){
        return pt;
    }
    else{
        trie* pt_lettre = pt->table[word[0] - 'a'];
        if(pt_lettre == NULL){
            return NULL;
        }
        else{
            return search_word(&word[1], pt_lettre);
        }
    }
}

void incr_word(char word[], trie* pt){
    if(word[0] =='\0'){
        pt->val = 1;
    }
    else{
        trie* pt_lettre = pt->table[word[0] - 'a'];
        if(pt_lettre != NULL){
            incr_word(&word[1], pt_lettre);
        }
    }
}



trie* clean(trie* pt){
    if(pt == NULL){
        return NULL;
    }
    else{
        int all_sons_null = 1;
        for(int i=0; i<NB_LETTRES; i++){
            trie* fils =  pt->table[i];
            if(clean(fils) != NULL){
                all_sons_null = 0;
            }
            else{
                delete_trie(fils);
                pt->table[i] = NULL;
            }
        }
        if(all_sons_null && (pt->val == 0)){
            return NULL;
        }
        else{
            return pt;
        }
    }
}

void aux_delete(char word[], trie* pt){
    if(word[0] =='\0'){
        pt->val = 0;
    }
    else{
        trie* pt_lettre = pt->table[word[0] - 'a'];
        if(pt_lettre != NULL){
            aux_delete(&word[1], pt_lettre);
        }
    }
}

void delete_word(char word[], trie* pt){
    aux_delete(word, pt);
    clean(pt);
}


int depth_trie(trie* pt){
    if(pt == NULL){
        return -1;       
    }
    else{
        int h = -1;
        for(int i = 0; i<NB_LETTRES; i++){
            int hf = depth_trie(pt->table[i]);
            if(hf > h){
                h = hf;
            }
        }
        return h + 1;
    }
}

void print_langage_aux(trie* pt, char* prefix, int longueur_prefixe){
    if(pt != NULL){
        if(pt->val == 1){
            printf("%.*s", longueur_prefixe, prefix);
        }
        for(int i=0; i<NB_LETTRES; i++){
            prefix[longueur_prefixe] = 'a' + i;
            print_langage_aux(pt->table[i], prefix, longueur_prefixe + 1);
        }
    }
}

void print_langage(trie* pt){
    int h = depth_trie(pt);
    char* prefix = malloc(h*sizeof(char));
    print_langage_aux(pt, prefix, 0);
    free(prefix);
}

int main(){

    trie* a = make_trie();
    add_char('c',a);
    add_word("bonjour", a);
    if (search_word("bonjour", a) != NULL){
        printf("bonjour trouve !\n");
    }
    incr_word("c", a);
    if (search_word("c", a) != NULL){
        printf("trouve !\n");
    }
    printf("hauteur %d\n",depth_trie(a));
    delete_word("bonjour",a);
    printf("hauteur %d\n",depth_trie(a));

    if (search_word("bonjour", a) == NULL){
        printf("bonjour plus la !\n");
    }

    incr_word("blabla", a);
    print_langage(a);

    delete_trie(a);
    return 0;
}


#include <stdio.h>
#include <stdbool.h>


#define ALPHABET_SIZE 26

typedef struct trie {
                    int val; 
                    struct trie* children[ALPHABET_SIZE];
                    } trie;



/* trie : crée un trie initialisé avec la valeur 0 et des pointeurs NULL*/
trie* make_trie(){
    trie* this = MALLOC(sizeof(trie));
    if (this != NULL){
        this -> val = 0;
        for (int i=0; i < ALPHABET_SIZE; i++){
            this ->children[i] = NULL;
    }
    return this;
    }
    else{
        printf("COuld not allocate trie. :( \n");
        exit(1);
    }
}
/*delete_trie : libère récursivement la mémoire allouée*/
void delete_trie (trie* pt){
    if (pt != NULL){
        for (int i =0; i< ALPHABET_SIZE; i++){
                delete_trie(pt ->children[i]);
        }
        free(pt);
    }
}

/*add_char : ajoute le caractère c au noeud de l'arbre pointé par pt si possible*/
void add_char (char c, trie* pt){
    if (pt -> children[c-'a'] == NULL){
        trie* t = make_trie();
        (pt -> children[c-'a']) = t;
    }
}

/*add_word : ajoute le mot word à pt et lui donne la valeur 1*/

/*fonction auxiliaire pour introduire une variable compteur pendant les appels récursifs*/
void aux (char word[], trie* pt, int compt){
        if (word[compt] == '\0'){
            pt -> val = 1;
        }
        else {
            add_char (word[compt], pt);
            aux (word, (pt ->children[word[compt]-'a']), (compt+1));
        }
}

void add_word (char word[], trie* pt){  
    aux (word, pt, 0);
}

/* get the subtrie corresponding to a given letter
@param char c a lowercase letter
@param trie* pt a pointer to the trie

*/
trie* subtrie(char c, trie* pt){
    if (subtrie(c, pt) != NULL){
        return pt -> children[c - 'a'];

    }
}

/*alternative*/
void add_word2(char word[], trie* pt){
    char c = word[0];
    if  (c== '\000'){
        pt -> val = 1;
    }
    else{
        add_char(c,pt);
        add_word(word+1, subtrie(c,pt));
    }
}

/* cherche word dans *pt et renvoie un poineur vers le noeud qui représente ce mot s'il existe */
trie* search_word(char word[], trie* pt){
    trie* point = pt; 

    for (int i=0; word[i] != '\0'; i++){
        if (point -> children[word[i] - 'a'] != NULL){
            point = point -> children[word[i] - 'a'];
        }
        else return NULL;
    }
    if (point ->val >0) return point;
    else return NULL;
}

/*alternative*/

trie* search_word(char word[], trie* pt){
    char c = word[0];
    if (c == '\000'){
        return pt->val >0 ? pt: NULL;
    }
    else{
        trie* child = subtrie(c,pt);
        return child != NULL ? search_word(word+1,child) : NULL;

    }
}
/*augmente la valeur du mot word dans pt s'il existe*/
void incr_word(char word[], trie* pt){
    trie* found = search_word(word, pt);
    if(found != NULL){
        found -> val++;
    }
}

/*clean : nettoie un trie*/
bool clean (trie* pt){
    int i = 0;
    if(pt ->val == 0){
        while(i< ALPHABET_SIZE && pt ->children[i]==0){
            i++;
        }
        if (i == ALPHABET_SIZE){
            delete_trie(pt);
            return true;
        }
    }
    return false;
}


/*supprime un mot et nettoie l'arbre de tous les sous arbres qui ne représentent aucun mot*/
void delete_word(char word[], trie* pt){
    char c = word[0];
    if (c=='\000'){
        pt -> val = 0;
    }
    else{
        trie* child = subtrie(c,pt);
        if(child !=NULL){
            delete_word(word+1, child);
            if(clean(child)){
                pt->children[c-'a'] = NULL;
            }
            
            
        }
    }
      
}

/*depth_trie : calcule la hauteur de l'arbre pt*/

int depth_trie(trie* pt){
    int max = -1;
    if (pt != NULL){
        for (int i = 0; i< ALPHABET_SIZE; i++){
            int depth = depth_trie (pt ->children[i]);
            if (depth >max){
                max = depth;
            }
        }
        return max + 1;
    }
    else{
        return max;
    }
}

void print_language_aux(trie* pt, char* prefix, int at){
    if (pt != NULL){
        if (pt -> val >0){
            printf("%s\n", prefix);
        }
        for (int i; i<ALPHABET_SIZE; i++){
            prefix[at] = 'a' + i;
            print_language_aux(pt-> children[i], prefix, at+1);
        }
        prefix[at] = 0;
    }
}

void print__language(trie* pt){
    int prefixSize = depth_trie(pt)+1;
    char prefix[prefixSize];
    for (int i = 0; i < prefixSize; i++){
        prefix[i] = '\000';
    }
    print_language_aux(pt,prefix,0);
}
int main(){
    trie* ceratops = make_trie();
    add_char('t', ceratops);
    add_word("tri", ceratops);
    trie* test = search_word("test",ceratops);
    printf("%p\n",test);
    trie* truc = search_word("truc",ceratops);
    printf("%p\n",truc);
    printf("%d\n", depth_trie(ceratops));
    print_language(ceratops);
    delete_trie(ceratops);

    return 0;
}
#include<assert.h>
#include<stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

/**
 * @brief The size of the alphabet used in a trie
 */
#define ALPHABET_SIZE 26

/**
 * @brief A node in a trie
 */
struct node_s {
  unsigned int value;
  struct node_s** next;
};

/**
 * @brief A trie is given by the root of the tree
 */
typedef struct node_s trie;

/**
 * @brief Make an empty trie (allocates memory)
 * 
 * @return trie* the created trie
 */
trie* make_trie() {
  trie** next = malloc(ALPHABET_SIZE * sizeof(trie*));
  assert(next != NULL);

  for(int i = 0; i < ALPHABET_SIZE; i++)
    next[i] = NULL;

  trie* t = malloc(sizeof(trie));
  assert(t != NULL);

  t->value = 0;
  t->next = next;

  return t;
}

/**
 * @brief Deletes (frees the memory used by) a trie
 * 
 * @param pt A pointer to the trie that needs to be deleted
 */
void delete_trie(trie* pt) {
  if(pt == NULL) return;

  for(int i = 0; i < ALPHABET_SIZE; i++)
    delete_trie(pt->next[i]);

  free(pt->next);
  free(pt);
}

/**
 * @brief Adds a character in a trie (doesn't change the value, but adds the subtree if not already there)
 * 
 * @param c The character to add
 * @param pt A pointer to the trie
 */
void add_char(const char c, trie* pt) {
  int i = c - 'a';
  assert(0 <= i && i < ALPHABET_SIZE);

  if(pt->next[i] == NULL) {
    pt->next[i] = make_trie();
  }
}

/**
 * @brief Add a word to a trie (sets the value of the corresponding node to 1)
 * 
 * @param word The word to be added
 * @param pt A pointer to the trie
 */
void add_word(const char word[], trie* pt) {
  if(word[0] == '\0' || pt == NULL) return;

  char c = word[0];
  int i = c - 'a';
  assert(0 <= i && i < ALPHABET_SIZE);

  add_char(c, pt);


  if(word[1] == '\0') {

    pt->next[i]->value = 1;
  }

  add_word(word + 1, pt->next[i]);
}

/**
 * @brief Search a word in a trie
 * 
 * @param word A word
 * @param pt A pointer to the trie
 * @return trie* The subtree for the corresponding word and NULL if it doesn't exist
 */
trie* search_word(const char word[], trie* pt) {
  if(word[0] == '\0') return pt;

  char c = word[0];
  int i = c - 'a';
  assert(0 <= i && i < ALPHABET_SIZE);

  if(pt->next[i] == NULL)
    return NULL;
  else
    return search_word(word + 1, pt->next[i]);
}

/**
 * @brief Increment the value of a word in a trie
 * 
 * @param word A word
 * @param pt A pointer to the trie
 */
void incr_word(const char word[], trie* pt) {
  trie* subtree = search_word(word, pt);

  if(subtree != NULL)
    subtree->value++;
}

/**
 * @brief Removes the empty subtrees in a trie (will free the memory)
 * 
 * @param pt A pointer to a trie
 * @return true if the trie considered is empty
 */
bool remove_empty_subtrees(trie* pt) {
  if (pt == NULL) return true;

  bool empty = true;

  for (int i = 0; i < ALPHABET_SIZE; i++) {
    if(pt->next[i] == NULL) continue;

    if(pt->next[i]->value > 0 || !remove_empty_subtrees(pt->next[i])) {
      empty = false;

      continue;
    }

    delete_trie(pt->next[i]);
    pt->next[i] = NULL;
  }

  return empty;
}

/**
 * @brief Delete a word in a trie (and removes the empty subtrees)
 * 
 * @param word A word
 * @param pt A pointer to the trie
 */
void delete_word(char word[], trie* pt) {
  trie* subtree = search_word(word, pt);

  if(subtree != NULL) {
    subtree->value = 0;
  }

  remove_empty_subtrees(pt);
}

/**
 * @brief Computes the depth of a trie
 * 
 * @param pt A pointer to the trie
 * @return int The depth of the trie
 */
int depth_trie(const trie* pt) {
  if(pt == NULL) return 0;

  int depth = 0;
  for (int i = 0; i < ALPHABET_SIZE; i++) {
    int depth_i = depth_trie(pt->next[i]);

    if(depth_i > depth) depth = depth_i;
  }

  return depth + 1;
}

/**
 * @brief Prints the language in a subtree with the corresponding prefix
 * 
 * @param pt A pointer to a subtree
 * @param prefix A prefix (is added at the beginning of a word)
 */
void print_language_aux(trie* pt, char prefix[]) {
  if(pt == NULL) return;

  if(pt->value > 0) printf("%s\n",prefix);

  int n = strlen(prefix);
  char new_prefix[n+2];

  strcpy(new_prefix, prefix);
  new_prefix[n+1] = '\0';
  
  for(int i = 0; i < ALPHABET_SIZE; i++) {
    new_prefix[n] = 'a' + i;
    print_language_aux(pt->next[i], new_prefix);
  }
}

/**
 * @brief Prints the language in a trie
 * 
 * @param pt A pointer to the trie
 */
void print_language(trie* pt) {
  print_language_aux(pt, "");
}

/**
 * @brief Testing the trie functions
 */
void test_trie() {
  trie* t = make_trie();

  printf("Adding some words...\n");

  add_word("sea", t);
  add_word("sell", t);
  add_word("sells", t);
  add_word("she", t);

  printf("depth: %d\n", depth_trie(t));
  printf("Words in trie:\n");
  print_language(t);

  printf("Removing the other words\n");

  delete_word("sea", t);
  delete_word("sell", t);
  delete_word("sells", t);
  delete_word("she", t);

  printf("There shouldn't be any word after removing everything:\n");

  print_language(t);

  printf("Depth sould be 1.\n");

  printf("depth: %d\n", depth_trie(t));

  delete_trie(t);
}

/**
 * @brief A string buffer with capacity = size + 2
 */
typedef struct {
  char* string;
  int size;
} buffer;

/**
 * @brief Makes an empty buffer (will allocate memory)
 * 
 * @param n The size of the buffer (capacity = n + 2)
 * @return buffer The empty buffer
 */
buffer make_buffer(int n) {
  char* string = malloc((n+2)*sizeof(char)); 
  assert(string != NULL);

  return (buffer) { string, n };
}

/**
 * @brief Frees the memory used by a buffer
 * 
 * @param b A buffer
 */
void destroy_buffer(buffer b) {
  free(b.string);
  b.size = 0;
}

/**
 * @brief Fills the unused part of the buffer with data from a stream
 * 
 * @param b A buffer
 * @param stream A stream
 * @return true If there are characters remaining in the stream
 */
bool fill_buffer(buffer b, FILE* stream) {
  int n = strlen(b.string);

  return fgets(b.string+n, b.size + 2 - n, stream) != NULL;
}

/**
 * @brief Prints n characters from a buffer (naïve method)
 * 
 * @param b A buffer b
 */
void print_buffer(buffer b) {
  // Maybe a format option in printf can be used to only print the first n chars of a string

  for(int i = 0; i < b.size; i++) {
    if(b.string[i] == '\0') break;

    putchar(b.string[i]);
  }

  putchar('\n');
}

/**
 * @brief Clears a buffer (makes it represent the empty string)
 * 
 * @param b A buffer
 */
void clear_buffer(buffer b) {
  b.string[0] = '\0';
}

/**
 * @brief Scrolls a buffer of size n (capacity = n + 2), that way b[n] -> b[0]
 * 
 * @param b A buffer
 */
void scroll_buffer(buffer b) {
  b.string[0] = b.string[b.size];
  b.string[1] = b.string[b.size+1];

  for(int i = 2; i < b.size + 2; i++)
    b.string[i] = '\0';
}

/**
 * @brief Get the last space in a buffer
 * 
 * @param b A buffer
 * @return int The index of the last space
 */
int get_last_space(buffer b) {
  for(int i = strlen(b.string); i >= 0; i--) {
    if(b.string[i] == ' ') return i;
  }

  return b.size;
}

/**
 * @brief Prints characters from a buffer until a space is reached (left alignment)
 * 
 * @param b A buffer b
 */
void print_buffer_left(buffer b) {
  int n = get_last_space(b);

  for(int i = 0; i < n; i++) {
    if(b.string[i] == '\0') break;

    putchar(b.string[i]);
  }

  putchar('\n');
}

/**
 * @brief Scrolls a buffer by removing characters before the last space
 * 
 * @param b A buffer
 */
void scroll_buffer_left(buffer b) {
  int n = get_last_space(b);

  for(int i = 1; i < n; i++) {
    b.string[i - 1] = i < n ? b.string[n + i] : '\0';
  }
}

/**
 * @brief Count the number of spaces in a string
 * 
 * @param s A string
 * @return int The number of spaces
 */
int number_space(char* s) {
  int n = 0;

  while(s[0] != '\0') {
    if (s[0] == ' ') n++;
    s++;
  }

  return n;
}

/**
 * @brief Prints characters from a buffer until a space is reached 
 *        and adds enough spaces before the first character (right alignment)
 * 
 * @param b A buffer b
 */
void print_buffer_right(buffer b) {
  int n = get_last_space(b);
  int spaces_to_add = b.size - n;

  for(int i = 0; i < spaces_to_add; i++)
    putchar(' ');

  
  for(int i = 0; i < n; i++) {
    if(b.string[i] == '\0') break;

    putchar(b.string[i]);
  }

  putchar('\n');
}

/**
 * @brief Prints characters from a buffer until a space is reached
 *        and adds spaces between words (justified alignment)
 * 
 * @param b A buffer b
 */
void print_buffer_justified(buffer b) {
  int n = get_last_space(b);

  b.string[n] = '\0';
  int n_spaces = number_space(b.string);
  b.string[n] = ' ';

  if(strlen(b.string) <= b.size) return print_buffer(b);

  int x_floor = (b.size - n) / n_spaces;

  int space_count = 0;

  for(int i = 0; i < n; i++) {
    if(b.string[i] == '\0') break;

    putchar(b.string[i]);

    if(b.string[i] == ' ') {
      int max = x_floor + (space_count < (b.size - n) % n_spaces ? 1 : 0);
      for(int j = 0; j < max; j++) putchar(' ');
      space_count++;
    }
  }

  putchar('\n');
}

/**
 * @brief Test the buffer-related functions
 */
void test_buffer() {
  FILE* file = fopen("sample.txt", "r");

  buffer b = make_buffer(60);


  printf("============================================================\n");
  printf("====================== METHODE  NAIVE ======================\n");
  printf("============================================================\n");

  while(fill_buffer(b, file)) {
    print_buffer(b);
    scroll_buffer(b);
  }

  clear_buffer(b);
  rewind(file);

  printf("\n\n");

  printf("============================================================\n");
  printf("===================== ALIGNER A GAUCHE =====================\n");
  printf("============================================================\n");

  while(fill_buffer(b, file)) {
    print_buffer_left(b);
    scroll_buffer_left(b);
  }

  clear_buffer(b);
  rewind(file);

  printf("\n\n");

  printf("============================================================\n");
  printf("===================== ALIGNER A DROITE =====================\n");
  printf("============================================================\n");

  while(fill_buffer(b, file)) {
    print_buffer_right(b);
    scroll_buffer_left(b);
  }

  clear_buffer(b);
  rewind(file);

  printf("\n\n");

  printf("============================================================\n");
  printf("======================== JUSTIFIER =========================\n");
  printf("============================================================\n");

  while(fill_buffer(b, file)) {
    print_buffer_justified(b);
    scroll_buffer_left(b);
  }

  destroy_buffer(b);
  fclose(file);
}

int main() {
  // Test all functions
  test_trie();
  test_buffer();
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "trie.h"

int max(int a, int b) { return a >= b ? a : b; }

trie* make_trie() {
    trie* pt = malloc(sizeof(*pt));
    if (!pt) {
        fprintf(stderr, "Erreur d'allocation mémoire !");
        exit(1);
    }
    pt->children = calloc(SIZEALPHABET, sizeof(*(pt->children)));
    if (!pt->children) {
        fprintf(stderr, "Erreur d'allocation mémoire !");
        exit(1);
    }
    pt->is_end_of_word = 0;
    return pt;
}

void delete_trie(trie *pt) {
    if (pt == NULL) return;
    for (int i = 0; i < SIZEALPHABET; ++i)
        delete_trie(pt->children[i]);
    free(pt->children);
    free(pt);
}

void add_char(char c, trie *pt) {
    if (!pt->children[c])
        pt->children[c] = make_trie();
}

void add_word(char word[], trie* pt) {
    trie* pt_ = pt;
    for (int i = 0; word[i] != 0; ++i) {
        char c = word[i] - 'a';
        add_char(c, pt_);
        pt_ = pt_->children[c];
    }
    if (pt_->is_end_of_word == 0)
        pt_->is_end_of_word = 1;
}

trie* search_word(char word[], trie* pt) {
    trie* pt_ = pt;
    for (int i = 0; word[i] != 0 && pt_; ++i) {
        pt_ = pt_->children[word[i] - 'a'];
    }
    return pt_;
}

void incr_word(char word[], trie *pt) {
    trie* pw = search_word(word, pt);
    if (pw) ++pw->is_end_of_word;
}

bool aux_delete_word(char word[], trie* pt, trie* r) {
    if (!pt) return false;
    if (word[0] == 0) 
        pt->is_end_of_word = 0;
    else
        if (aux_delete_word(word+1, pt->children[word[0] - 'a'], r))
            pt->children[word[0]-'a'] = NULL;
    
    if (pt == r) return false;
    if (pt->is_end_of_word != 0) return false;
    for (int i = 0; i < SIZEALPHABET; ++i)
        if (pt->children[i]) return false;
    
    delete_trie(pt);
    return true;
}

void delete_word(char word[], trie* pt) {
    aux_delete_word(word, pt, pt);
}

int depth_trie(trie *pt) {
    if (!pt) return 0;
    int m = 0;
    for (int i = 0; i < SIZEALPHABET; ++i)
        m = max(m, depth_trie(pt->children[i]));
    return m;
}

void print_langage_aux(trie *pt, char prefix[], int idx) {
    if (!pt) return;
    if (pt->is_end_of_word) printf("%s\n", prefix);
    for (int i = 0; i < SIZEALPHABET; ++i) {
        prefix[idx] = 'a' + i;
        print_langage_aux(pt->children[i], prefix, idx+1);
        prefix[idx] = 0;
    }
}

void print_langage(trie *pt) {
    char* prefix = calloc(depth_trie(pt) + 1, sizeof(*prefix));
    print_langage_aux(pt, prefix, 0);
    free(prefix);
}

void test_trie() {
    trie* pt = make_trie();
    add_word("hello", pt);
    add_word("world", pt);
    incr_word("hello", pt);
    printf("%d\n", search_word("hello", pt)->is_end_of_word);
    print_langage(pt);
    delete_word("world", pt);
    delete_word("hello", pt);
    print_langage(pt);
    for(int i=0;i<SIZEALPHABET;++i)
        printf("%p ", pt->children[i]);
    printf("\n");
    delete_trie(pt);
}
#include <stdlib.h>
#include <stdio.h>
#include<stdbool.h>

#define ALPHABET_SIZE 26




typedef struct trie {
    int value;
    struct trie* children[ALPHABET_SIZE];
} trie;




trie* make_trie() {
    trie* this = malloc(sizeof(trie));
    if (this !=NULL) {
        this->value=0;
        for (int i=0; i<ALPHABET_SIZE; i++) {
            this->children[i]=NULL;
        }
        return this;
    }
    else {
        printf("erreur");
    }
}




void delete_trie(trie* pt) {
    if (pt!=NULL) {
        for (int i=0; i<ALPHABET_SIZE; i++) {
            delete_trie(pt->children[i]);
        }
    };
    free(pt);
}



void add_char(char c, trie* pt) {
    int offset = c-'a';
    if (pt->children[offset]==NULL) {
        pt->children[offset]=make_trie();
    }
}







void add_word(char word[], trie* pt) {
    int i=0;
    trie* sous_mot=pt;
    while (word[i] != '\0') {
        add_char(word[i], sous_mot);
        sous_mot=sous_mot->children[word[i]-'a'];
        i++;
    }
    sous_mot->value=1;
}





trie* search_word(char word[], trie* pt) {
    int i=0;
    trie* sous_mot=pt;
    while(word[i] != '\0') {
        if (sous_mot == NULL) {
            return (NULL);
        }
        else {
            sous_mot=sous_mot->children[word[i]-'a'];
        }
    }
    if (sous_mot->value == 1) {
        return(sous_mot);
    }
    else {
        return (NULL);
    }
}






void incr_word(char word[], trie* pt) {
    trie* mot=search_word(word, pt);
    if (mot != NULL) {
        mot->value+=1;
    }
}







bool clean (trie* pt) {
    int i=0;
    while (i<ALPHABET_SIZE && pt->children[i]==0) {
        i++;
    }
    if (i==ALPHABET_SIZE) {
        delete_trie(pt);
        return(true);
    }
    return(false);
}





void delete_word(char word[], trie* pt) {
    char c=word[0];
    if (c=='\0') {
        pt->value=0;
    }
    else {
        trie* child=pt->children[c-'a'];
        if (child!=NULL) {
            delete_word(word+1,child);
            if(clean(child)) {
                pt->children[c-'a']=NULL;
            }

        }
    }
}







int main() {}


#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#define CHAR_MIN 97    // the value of a
#define CHAR_MAX 122   // the value of z
#define NBR_LETTRS 26  // the number of letters
#define BUFFER_SIZE 60 // the size of the buffer

typedef struct node {
  int val;
  struct node *tab[26];
} trie;

trie *make_trie() {
  trie *node = (trie *)malloc(sizeof(trie));
  for (int i = 0; i < NBR_LETTRS; i++) {
    node->tab[i] = NULL;
  }
  node->val = 0;
  return node;
}

void delete_trie(trie *pt) {
  if (pt != NULL) {
    for (int i = 0; i < NBR_LETTRS; i++) {
      delete_trie(pt->tab[i]);
    }
    free(pt);
  }
}

void add_char(char c, trie *pt) {
  if (pt->tab[(int)c - CHAR_MIN] == NULL) {
    pt->tab[(int)c - CHAR_MIN] = make_trie();
  }
}

// we suppose that the word can be added to the tree (we have NULL pointer in
// 'tab')
void add_word(char word[], trie *pt) {
  trie *temp = pt;
  int i = 0;
  while (word[i] != '\0') {
    add_char(word[i], temp);
    temp = temp->tab[(int)word[i] - CHAR_MIN];
    i++;
  }
  temp->val = 1;
}

// the word is not in the tree if its value is 0
trie *search_word(char word[], trie *pt) {
  if (pt == NULL) {
    return NULL;
  }
  int i = 0;
  trie *temp = pt;
  while (word[i] != '\0') {
    if (temp->tab[(int)word[i] - CHAR_MIN] == NULL) {
      return NULL;
    } else {
      temp = temp->tab[(int)word[i] - CHAR_MIN];
    }
    i++;
  }
  if (temp->val == 1) {
    return temp;
  } else {
    return NULL;
  }
}

void incr_word(char word[], trie *pt) {
  trie *node = search_word(word, pt);
  if (node != NULL) {
    node->val++;
  }
}

// return true if we deleted the tree
bool clean_tree_aux(trie *pt) {

  bool is_empty = true;
  for (int i = 0; i < NBR_LETTRS; i++) {
    if (pt->tab[i] != NULL) {
      bool deleted = clean_tree_aux(pt->tab[i]);
      if (!deleted) {
        is_empty = false;
      } else {
        pt->tab[i] = NULL; // we do not want to point at unallocated memory
      }
    }
  }

  if (is_empty && (pt->val == 0)) {
    free(pt);
    return true;
  }

  return false;
}

void clean_tree(trie *pt) {
  if (pt != NULL) {
    bool b = clean_tree_aux(pt);
  }
  if (pt == NULL) {
    pt = make_trie();
  }
}

void delete_word(char word[], trie *pt) {
  trie *node = search_word(word, pt);
  if (node != NULL) {
    node->val = 0;
  }
  clean_tree(pt);
}

int depth_tree(trie *pt) {
  if (pt == NULL) {
    return 0;
  }
  int max = 0;
  for (int i = 0; i < NBR_LETTRS; i++) {
    int new_depth = 1 + depth_tree(pt->tab[i]);
    max = (new_depth > max) ? new_depth : max;
  }
  return max;
}

void print_langage_aux(char prefix[], int len_prefix, trie *pt) {
  if (pt != NULL) {
    if (pt->val == 1) {
      printf("%s\n", prefix);
    }
    for (int i = 0; i < NBR_LETTRS; i++) {
      if (pt->tab[i] != NULL) {
        char new_prefix[len_prefix + 1];
        strcpy(new_prefix, prefix);
        new_prefix[len_prefix - 1] = (char)(i + CHAR_MIN);
        new_prefix[len_prefix] = '\0';
        print_langage_aux(new_prefix, len_prefix + 1, pt->tab[i]);
      }
    }
  }
}

void print_langage(trie *pt) {
  char prefix[1];
  prefix[0] = '\0';
  print_langage_aux(prefix, 1, pt);
}

void test_part_one() {
  printf("test part_one ...................\n");
  trie *test_tree = make_trie();
  add_word("hello", test_tree);
  add_word("world", test_tree);
  assert(search_word("notInTree", test_tree) == NULL);
  assert(search_word("hello", test_tree) != NULL);
  print_langage(test_tree);
  printf("\n");
  delete_word("hello", test_tree);
  assert(!(test_tree == NULL));
  print_langage(test_tree);
  delete_trie(test_tree);
  printf("OK\n");
}

typedef struct {
  char *string;
  int size;

} buffer;

buffer make_buffer(int n) {
  char *str = (char *)malloc(sizeof(char) * (n + 2));
  str[0] = '\0';
  return (buffer){.string = str, .size = n};
}

bool fill_buffer(buffer b, FILE *stream) {
  int len = strlen(b.string);
  char next_str[b.size];
  int to_fill = b.size - len;

  char *res = fgets(next_str, to_fill, stream);
  strcat(b.string, next_str);

  return (res != NULL);
}

void print_buffer(buffer b){
    printf("%60s\n", b.string);
}

void test_print_buffer(){
    buffer b = make_buffer(60);
    FILE* stream = fopen("test_file.txt", "r");
    bool _ = fill_buffer(b, stream);
    print_buffer(b);
}

int main() {
  //test_part_one();
  test_print_buffer();
  return 0;
}
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define size_of_alphabet 26

/* Définition de la structure de données */
typedef struct
{
    int val;
    trie *tab[size_of_alphabet];
} trie;

/* Création d'un trie vide */
trie *make_trie()
{
    trie *tab = malloc(size_of_alphabet * sizeof(trie *));
    if (tab != NULL)
    {
        for (int i = 0; i < size_of_alphabet; i++)
            tab[i] = NULL;
        return (trie){0, tab};
    }
    printf("Impossible de créer le trie\n") return (trie){0, NULL};
}

/* Libération de la mémoire allouée */
void delete_trie(trie *pt)
{
    for (int i = 0; i < size_of_alphabet; i++)
    {
        if (pt->tab[i] != NULL)
            delete_trie(pt->tab[i]);
    }
    free(pt->tab);
}

/* Ajoute un caractère au trie */
void add_char(char c, trie *pt)
{
    if (pt->tab[c - 'a'] == NULL)
    {
        trie *new = make_trie();
        if (new->tab != NULL)
            pt->tab[c - 'a'] = new;
        else
            printf("Impossible d'ajouter le caractère, la création a échouée")
    }
    else
        printf("Impossible d'ajouter le caractère, caractère déjà existant")
}

/* Ajoute un mot au trie */
void add_word(char word[], trie *pt)
{
    int l = str(len(word));
    trie *current = pt;
    for (int i = 0; i < l; i++)
    {
        if (current->tab[word[i] - 'a'] == NULL)
            add_char(word[i], current);
        current = current->tab[word[i] - 'a'];
    }
    current->val = 1;
}

/* Cherche un mot dans le trie */
trie *search_word(char word[], trie *pt)
{
    int l = str(len(word));
    trie *current = pt;
    for (int i = 0; i < l; i++)
    {
        if (current->tab[word[i] - 'a'] == NULL)
            return NULL;
        current = current->tab[word[i] - 'a'];
    }
    return current;
}

/* Incrémente la valeur d'un mot dans le trie */
void incr_word(char word[], trie *pt)
{
    int l = str(len(word));
    trie *current = pt;
    int b = 1;
    for (int i = 0; i < l; i++)
    {
        if (current->tab[word[i] - 'a'] != NULL)
        {
            current = current->tab[word[i] - 'a'];
            b = 0;
        }
    }
    if (b == 1)
        current->val++;
}

/* Réalise un parcours de l'arbre trie et renvoie 1 s'il existe un sous-arbre de trie de valeur non nulle */
int delete_word_aux(trie *pt)
{
    if (pt->tab = NULL)
        return 0;
    if (pt->val != 0)
        return 1;

    for (int i = 0; i < size_of_alphabet; i++)
    {
        if (pt->tab[i] != NULL)
        {
            if (delete_word_aux(pt->tab[i]) != 0)
                return 1;
        }
    }
    return 0;
}

/* Supprime les sous-arbres si nécessaire */
void delete_word_supr(trie *pt)
{
    if (pt->tab != NULL)
    {
        for (int i = 0; i < size_of_alphabet; i++)
        {
            if (pt->tab[i] != NULL)
            {
                delete_word_supr(pt->tab[i]);
            }
        }
        if (delete_word_aux(pt) != 0)
            delete_trie(pt);
    }
}

/* Supprime un mot de l'arbre, on considère qu'une modification des sous-arbres n'a lieu que si on efface effectivement un mot */
void delete_word(char word[], trie *pt)
{
    int l = str(len(word));
    trie *current = pt;
    trie *pred = NULL;
    int b = 1;
    for (int i = 0; i < l; i++)
    {
        if (current->tab[word[i] - 'a'] != NULL)
        {
            pred = current;
            current = current->tab[word[i] - 'a'];
            b = 0;
        }
    }
    if (b == 1)
    {
        current->val = 0;
        delete_word_supr(current);
        pred->tab[word[l - 1] - 'a'] = NULL;
    }
}

/* Calcule la profondeur d'un trie */
int depth_trie(trie *pt)
{
    if (pt == NULL)
        return 0;
    int out = 0;
    for (int i = 0; i < size_of_alphabet; i++)
    {
        out = max(out, depth_trie(pt->tab[i]));
    }
    return ++out;
}

/* Réalise le parcours dans le trie */
void print_langage_aux(trie *pt, char prefix[])
{
    l = strlen(prefix);
    if (pt->val != 0)
        printf("%s\n", prefix);
    for (int i = 0; i < size_of_alphabet; i++)
    {
        if (pt->tab[i] != NULL)
        {
            prefix[l] = (char)('a' + i);
            print_langage_aux(pt->tab[i], prefix);
            prefix[l] = '\0';
        }
    }
}

/* Affiche le langage représenté par le trie */
void print_langage(trie *pt)
{
    int d = depth_trie(pt);
    char prefix[d + 1];
    for (int i = 0, i < d + 1; i++)
        prefix[i] = '\0';
    print_langage_aux(pt, prefix);
}

/* Définition du buffer */
typedef struct
{
    char *string;
    int size;
} buffer;

/* Initialise un buffer vide */
buffer make_buffer(int n)
{
    char *ptr = malloc((n + 2) * sizeof(char));
    if (ptr != NULL)
    {
        for (int i = 0; i < n + 2; i++)
            ptr[i] = '\0';
        return (buffer){ptr, n + 2};
    }
    printf("Erreur lors de la création du buffer");
}

/* Permet de remplir le buffer tant que possible */
bool fill_buffer(buffer b, FILE *stream)
{
    l = strlen(b.string);
    char *s[b.size] = *fgets(s, b.size - l - 1, stream);
    int i = 0;
    while (b.string[i] != '\0')
    {
        i++;
    }
    for (int j = i; j < b.size; j++)
    {
        if (s[j - i] != "\0")
            b.string[j] = s[j - i];
        else
            return false;
    }
    return true;
}

/* Affiche le contenu du buffer */
void print_buffer(buffer b)
{
    char temp[b.size - 1];
    for (int i = 0; i < b.size - 1; i++)
    {
        temp[i] = b.string[i];
    }
    temp[b.size - 2] = '\0';
    printf("%s \n", temp);
}

/* Fait défiler le buffer */
void scroll_buffer(buffer b)
{
    b.string[0] = b.string[b.size - 2];
    b.string[1] = b.string[b.size - 1];
    for (int i = 0; i < b.size; i++)
        b.string[i] = '\0';
}

/* Renvoie l'emplacement du dernier espace du buffer */
int get_last_space(buffer b)
{
    int out = -1;
    for (int i = 0; i < b.size; i++)
    {
        if (b.string[i] == ' ')
            out = i;
    }
    return out;
}

/* Affiche le contenu jusqu'au dernier espace exclus*/
void print_buffer_left(buffer b)
{
    int e = get_last_space(b);
    char temp[b.size - 1];
    for (int i = 0; i < e; i++)
    {
        temp[i] = b.string[i];
    }
    for (int i = e; i < b.size; i++)
        temp[i] = '\0';
    printf("%s \n", temp);
}

/* Fait défiler le buffer jusqu'au dernier espace exclus */
void scroll_buffer_left(buffer b)
{
    int e = get_last_space(b);
    for (int i = 0; i < e; i++)
    {
        b.string[i] = b.string[b.size - e + 1 + i];
    }
    for (int i = e; i < b.size; i++)
        temp[i] = '\0';
}

/* Compte le nombre d'espaces dans le buffer */
int number_space(buffer b)
{
    int out = 0;
    for (int i = 0; i < b.size; i++)
    {
        if (b.string[i] == ' ')
            out++;
    }
    return out;
}

/* Alligne le contenu du buffer à droite */
void print_buffer_droite(buffer b)
{
    char temp[b.size];
    int nb = get_last_space(b);
    for (int i = 0; i < nb; i++)
    {
        temp[i] = ' ';
    }
    for (int i = e; e < b.size - 1 - e; i++)
    {
        temp[i] = b.string[i - e];
    }
    printf("%s \n", temp);
}

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>

int NB_LETTRES = 26;



typedef struct Trie{
    struct Trie** fils;
    int valeur;
}trie;

typedef struct CharListe{
    char c;
    struct CharListe* suiv;
}charliste;



int indice(char c){
    return c -  97;
}

char reciproque_indice(int i){
    return i + 97;
}

trie* make_trie(){
    trie* res = malloc(sizeof(trie));
    res->valeur = 0;
    res->fils = malloc(sizeof(trie*)*NB_LETTRES);
    for(int i=0; i< NB_LETTRES; i++){
        res->fils = NULL;
    }
    return res;
}

void delete_trie(trie* pt){
    for(int i=0; i<NB_LETTRES; i++){
        if(pt->fils[i] != NULL){
            delete_trie(pt->fils[i]);
        }
    }
    free(pt->fils);
    free(pt);
}


void add_char(char c, trie* pt){
    if(pt->fils[indice(c)] == NULL){
        pt->fils = make_trie();
    }
}



void add_word(char* word, trie* pt){
    int len = strlen(word);
    trie* amodif = pt;
    for(int i=0; i<len; i++){
        add_char(word[i], amodif);
        amodif = amodif->fils[indice(word[i])];
    }
    amodif->valeur = 1;
}


trie* search_word_aux(int i, int len, char* word, trie* pt){
    assert(i < len);
    if(pt == NULL){
        return NULL;
    }
    if (i == len - 1){
        return pt->fils[indice(word[i])];
    }
    return search_word_aux(i+1, len, word, pt->fils[indice(word[i])]);
}

trie* search_word(char* word, trie* pt){
    return search_word_aux(0, strlen(word), word, pt);
}

bool fils_tous_nuls(trie* t){
    assert(t != NULL);
    for(int i=0; i<NB_LETTRES; i++){
        if(t->fils[i] != NULL){
            return false;
        }
    }
    return true;
}

trie* elaguer(trie* t){
    if (fils_tous_nuls(t)){
        delete_trie(t);
        return NULL;
    }
    return t;

}

trie* delete_word_aux(int i, int len, char* word, trie* pt){
    assert(pt != NULL);
    if (i == len - 1){
        pt->valeur = 0;
        return elaguer(pt);
    }
    return elaguer(delete_word_aux(i+1, len, word, pt->fils[indice(word[i])]));

}

void delete_word(char* word, trie* pt){
    delete_word_aux(0, strlen(word), word, pt);
}

int max(int a, int b){
    if (a > b){
        return a;
    }
    return b;
}

int depth_trie(trie* pt){
    if (pt == NULL){
        return 0;
    }
    int m = 0;
    for(int i=0; i<NB_LETTRES; i++){
        m = max(m, depth_trie(pt->fils[i]));
    }
    return m;
}


void print_liste(charliste* l, bool dernier_char){
    if (l == NULL){
        return;
    }
    print_liste(l->suiv, false);
    printf("%c", l->c);
    if (dernier_char){
        printf("\n");
    }
}

void print_langage_aux(trie* pt, charliste* prefix){
    if(pt == NULL){
        return;
    }
    if(pt->valeur > 0){
        print_liste(prefix, true);
    }
    for(int i=0; i<NB_LETTRES; i++){
        charliste* new = malloc(sizeof(charliste));
        new->c = reciproque_indice(i);
        new->suiv = prefix;
        print_langage_aux(pt, new);
    }
    return;
}

Reviews 7

  • STOP adding executable files to the repository
/**
 * @brief A struct to pass arguments to the mandelbrot_thread function
 * 
 */
typedef struct {
    bool **grid;
    int width;
    int height;
    int start_x;
    int start_y;
    double min_x;
    double max_x;
    double min_y; 
    double max_y;
} thread_args;

/**
 * @brief Calculate the mandelbrot set for sub-grid of pixels
 * 
 * @param args The sub-section of the grid to calculate
 * @return void* NULL
 */
void *mandelbrot_thread(void *args);

/**
 * @brief Use multiple threads to calculate the mandelbrot set for a grid of pixels
 * 
 * @param grid The grid of boolean values to store the mandelbrot set
 * @param width The width of the grid
 * @param height The height of the grid
 * @param min_x The minimum x value of the screen
 * @param max_x The maximum x value of the screen
 * @param min_y The minimum y value of the screen
 * @param max_y The maximum y value of the screen
 * @param num_threads The number of threads to use
 */
void mandelbrot_grid(bool **grid, int width, int height, double min_x, double max_x, double min_y, double max_y, int num_threads);
/**
 * @brief Create a grid object
 * 
 * @param width The width of the grid
 * @param height The height of the grid
 * @return The grid created
 */
bool** create_grid(int width, int height) {
    bool **grid = malloc(width * sizeof(bool *));
    if (!grid) {
        perror("Erreur d'allocation de mémoire !\n");
        exit(1);
    }
    for (int i = 0; i < width; i++) {
        grid[i] = calloc(height, sizeof(bool));
        if (!grid[i]) {
            perror("Erreur d'allocation de mémoire !\n");
            exit(1);
        }
    }
    return grid;
}
#include <raylib.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <complex.h>
#include <math.h>

#define MAX_ITERATIONS 100
#define WIDTH 800
#define HEIGHT 450

typedef struct {
    int x;
    int y;
} pos;

double module(double complex z){
    return creal(z)*creal(z) + cimag(z)*cimag(z);
}

bool convergence_pixel(double complex c){
    double complex z = 0;

    int nb_iterations = 0;

    while (nb_iterations < MAX_ITERATIONS && module(z) <= 4){
        z = z*z + c;
        nb_iterations++;
    }
    if (nb_iterations == MAX_ITERATIONS){
        return true;
    }else{
        return false;
    }

}

double complex coordonnees(int x, int y, double abscisse, double ordonnee, double abscisse_offset, double ordonnee_offset){
    double Zx = x * (abscisse/WIDTH) - (abscisse)/2 + abscisse_offset;
    double Zy = - y * ((ordonnee)/HEIGHT) + (ordonnee)/2 + ordonnee_offset;
    double complex z = Zx + I*Zy;
    return z;
}

void calcul_rectangle(double abscisse_init, double ordonnee_init, double abscisse_offset, double ordonnee_offset){
    ClearBackground(RAYWHITE);
    double abscisse = abscisse_init;
    double ordonnee = ordonnee_init;
    for (int i = 0; i < WIDTH; i++){
            for (int j = 0; j < HEIGHT; j++){
                if (convergence_pixel(coordonnees(i, j, abscisse, ordonnee, abscisse_offset, ordonnee_offset))){
                    DrawPixel(i, j, BLACK);
                }else{
                    DrawPixel(i, j, WHITE);
                }
            }
        }
        BeginDrawing();
        
        EndDrawing();
}

void handle_zoom(double* p_abscisse, double* p_ordonnee){
    double zoom = (double)GetMouseWheelMove();
        if (zoom > 0){
            *p_abscisse = *p_abscisse/(double)2;
            *p_ordonnee = *p_ordonnee/(double)2;
        }
        if (zoom < 0){
            *p_abscisse = *p_abscisse*2;
            *p_ordonnee = *p_ordonnee*2;
        }
}

void handle_slide(int* last_x_position, int* last_y_position, double* p_abscisse_offset, double* p_ordonnee_offset, double abscisse, double ordonnee){
    if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)){
        *p_abscisse_offset = *p_abscisse_offset + (GetMouseX() - *last_x_position)*0.01*abscisse;
        *p_ordonnee_offset = *p_ordonnee_offset - (GetMouseY() - *last_y_position)*0.01*ordonnee;
    }
    *last_x_position = GetMouseX();
    *last_y_position = GetMouseY();
}

void affiche_rectangle(double abscisse, double ordonnee, double abscisse_offset, double ordonnee_offset){
    InitWindow(WIDTH, HEIGHT, "mandelbrot");
    SetTargetFPS(50); 
    ClearBackground(RAYWHITE);
    int last_x_position = GetMouseX();
    int last_y_position = GetMouseY();
    while (!WindowShouldClose()){

        handle_zoom(&abscisse, &ordonnee);

        handle_slide(&last_x_position, &last_y_position, &abscisse_offset, &ordonnee_offset, abscisse, ordonnee);
        printf("%f, %f\n", abscisse_offset, ordonnee_offset);
        
        calcul_rectangle(abscisse, ordonnee, abscisse_offset, ordonnee_offset);
    }
    CloseWindow();
}

int main(){
    affiche_rectangle(4, 2, 0, 0);
    return 0;
}

Review 8

'''!
    @brief: Delete duplicates in a list without changing the first list
    @param: l: list

    @return: list without duplicates
'''
def dedup(l):
    return list(set(l))

'''!
    @brief: Create a list of numbers from 1 to n that are multiples of 5 or 7
    @param: n: int

    @return: list of numbers from 1 to n that are multiples of 5 or 7
'''
def multiple(n):
    return [i for i in range(1, n+1) if i % 5 == 0 or i % 7 == 0]

'''!
    @brief: Get index of elements in a list that are multiples of 5 or 7
    @param: l: list

    @return: list of index
'''
def arg_multiple(l):
    return [i for i in range(len(l)) if l[i] % 5 == 0 or l[i] % 7 == 0]

'''!
    @brief: Zip two lists
    @param: l1 list
    @param: l2 list
    
    @return list of tuples
'''
def zipping(l1, l2):
    return [(a, b) for a in l1 for b in l2]

'''!pythagoras_triples.__doc__
    @param: m matrix
    
    @return transposed matrix
'''
def transpose(m):
    return [[m[j][i] for j in range(len(m))] for i in range(len(m[0]))]

'''!
    @brief: Get the elements in a matrix
    @param: m matrix
    
    @return set of elements
'''
def elems(m):
    return {a for l in m for a in l}


def pythagoras_triples(n):
    '''!
    @brief: Find the Pythagoras triples in a range
    @param: n int

    @return list of Pythagoras triples
    '''
    return [(a, b, c) for a in range(1, n) for b in range(a, n) for c in range(b, n) if a**2 + b**2 == c**2]

if __name__ == "__main__":
   print(pythagoras_triples(20))

[(3, 4, 5), (5, 12, 13), (6, 8, 10), (8, 15, 17), (9, 12, 15)]
pythagoras_triples.__doc__
'!\n    @brief: Find the Pythagoras triples in a range\n    @param: n int\n\n    @return list of Pythagoras triples\n    '
'''!
    @brief Function that reads a file and returns a set of words that should be ignored.
    @param filename File to read.

    @return Set of words that should be ignored.
'''
def filtre(filename: str) -> {str}:
    try:
        f = open(filename, "r")
        l = f.read().split('\n')
        f.close()
        return set(l)
    except:
        return set()

def dedup (l) : 
    return list(set(l))
# Test 



### Exercice 2 : 

def multiple (n) : return [i for i in range(n) if i % 5 == 0 or i % 7 == 0]

def arg_multiple(l) : return [i for i in range(n) if l[i] % 5 == 0 or l[i]%7 == 0]

m = [[1,2,3],
     [4,5,6],
     [7,8,9]]


def zipping (l_a, l_b) : return [(l_a[i], l_b[i]) for i in range(min(len(l_a), len(l_b)))]


print (zipping (m[0], m[1]))

def transpose (m) : return [ [m[j][i] for i in range(len(m))] for j in range(len(m)) ]

def elems(m) : return list(set(element for row in m for element in row))

print(elems(m)) 

def pythagoras_triples(n) : return [(j,i,k) for i in range(1,n) for j in range(1,i) for k in range(1,n) if i**2 + j**2 == k**2]

print (pythagoras_triples(100))

## Exercice 3 


#!/usr/bin/env python3

import sys

def load_filter_words(filter_file_path):
    """Charge les mots à ignorer depuis un fichier."""
    with open(filter_file_path, 'r') as f:
        return set(line.strip() for line in f)

def words(file_object, filter_words=None):
    """Compte les mots dans le fichier, en ignorant les mots du fichier de filtrage."""
    word = ""
    word_counts = {}
    for line in file_object:
        for elt in line:
            if elt.isspace():
                if word:
                    # Ignorer les mots de la liste de filtrage
                    if filter_words is None or word not in filter_words:
                        if word in word_counts:
                            word_counts[word] += 1
                        else:
                            word_counts[word] = 1
                    word = ""
            else:
                word += elt
        # Compter le dernier mot si présent
        if word and (filter_words is None or word not in filter_words):
            if word in word_counts:
                word_counts[word] += 1
            else:
                word_counts[word] = 1
            word = ""
    return word_counts

def sorted_by_count(word_counts, n, rarest=False):
    """Trie les mots par fréquence et retourne les n premiers en fonction du tri."""
    return sorted(word_counts.items(), key=lambda x: x[1], reverse=not rarest)[:n]

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: lexicon.py [-r] n path/to/some/file.txt [path/to/filter_file.txt]")
        sys.exit(1)

    # Initialiser les variables de configuration
    rarest = False
    filter_file_path = None

    # Vérification des arguments et initialisation
    arg_index = 1
    if sys.argv[arg_index] == "-r":
        rarest = True
        arg_index += 1

    n = int(sys.argv[arg_index])
    file_path = sys.argv[arg_index + 1]

    # Charger le fichier de filtrage si un chemin est donné
    if len(sys.argv) > arg_index + 2:
        filter_file_path = sys.argv[arg_index + 2]
        filter_words = load_filter_words(filter_file_path)
    else:
        filter_words = None

    # Lecture du fichier de texte et comptage des mots
    with open(file_path, 'r') as file:
        word_counts = words(file, filter_words)

    # Récupération des n mots les plus fréquents ou les plus rares
    words_to_display = sorted_by_count(word_counts, n, rarest)

    # Affichage des résultats
    for word, count in words_to_display:
        print(f"{word}: {count}")

#!/usr/bin/env python3

import sys

# Renvoie le dictionnaire {mot : nombre d'occurences}
# des mots apparaissant dans f
def words(f, ignore):
    D = {}
    for x in f.read().split():
        if x not in D:
            D[x] = 0
        D[x] += 1
    return D

# Renvoie la liste triée par nombre d'occurence (décroissant de base)
# des couples (mot, occurences) obtenus à partir du dictionnaire D.
# r booléen tel que, si r vaut False, l'ordre soit effectivement décroissant,
# et si r vaut True, l'ordre est croissant.
def sorted_by_count(D, r = False):
    def n_occurence(c):
        return c[1]

    l = [(x, D[x]) for x in D]
    l.sort(reverse = not r, key = n_occurence)
    return l

# Obtient les mots à ignorer à partir du fichier indiqué par path_ignore
def getignore(f):
    return f.read.split()

# Travaille sur les arguments fournis pour obtenir une valeur utilisable
# par les fonctions définies plus tôt
def getargs(argv):
    n = int(argv[0])
    path = argv[1]

    if len(argv) >= 3:
        if argv[2] == "-r":
            ignore = []
            r = True
        else:
            path_ignore = argv[2]
            with open(path_ignore, "r") as f:
                ignore = getignore(f)

            if len(argv) >= 4 and argv[3] == "-r":
                r = True
            else:
                r = False
    else:
        ignore = []
        r = False

    return n, path, ignore, r

# Prend en argument un entier n, un chemin vers un fichier texte path et éventuellement path_ignore et "-r",
# et renvoie la liste des n mots les plus fréquents de path (les moins fréquents si -r est présent),
# en excluant du compte les mots de path_ignore si path_ignore est précisé.
if __name__ == '__main__':
    n, path, ignore, r = getargs(argv)

    with open(path, "r") as f:
        l = sorted_by_count(words(f, ignore), r)
        for c in l:
            print(c[0])

#!/usr/bin/env python3

class PriorityQueue:
	def pop(self):
		""" returns a most prioritzed element 

		Raises:
			NotImplementedError: _description_
		"""
		raise NotImplementedError

	def push(self, el) -> None:
		raise NotImplementedError

	def isEmpty(self) -> bool:
		raise NotImplementedError

	def update(self, el) -> None:
		raise NotImplementedError

class HeapQueue(PriorityQueue):
	def __init__(self, key=lambda x: x):
		self.repr = []
		self.key = key

	def parent(self, i):
		return (i-1) // 2

	def leftChild(self, i):
		return 2*i+1

	def rightChild(self, i):
		return 2*(i+1)

	def swap(self, i, j):
		self.repr[i], self.repr[j] = self.repr[j], self.repr[i]

	def push(self, el):
		self.repr.append(el)
		indice = len(self.repr) - 1
		parent = self.parent(indice)
		while indice > 0 and self.key(self.repr[parent]) >= self.key(self.repr[indice]):
			self.swap(parent, indice)
			indice = parent
			parent = self.parent(indice)

	def pop(self):
		m = self.repr[0]
		self.repr[0] = self.repr.pop()
		indice = 0
		leftChild = self.leftChild(indice)
		rightChild = self.rightChild(indice)
		while leftChild < len(self.repr):
			if self.key(self.repr[leftChild]) < self.key(self.repr[rightChild]) and self.key(self.repr[leftChild]) < self.key(self.repr[indice]):
				self.swap(leftChild, indice)
				indice = leftChild
				leftChild = self.leftChild(indice)
				rightChild = self.rightChild(indice)
			if self.key(self.repr[rightChild]) <= self.key(self.repr[leftChild]) and self.key(self.repr[rightChild]) < self.key(self.repr[indice]):
				self.swap(rightChild, indice)
				indice = rightChild
				leftChild = self.leftChild(indice)
				rightChild = self.rightChild(indice)
			else:
				leftChild = len(self.repr)

if __name__ == "__main__":
	hq = HeapQueue()
	hq.push(4)
	hq.push(5)
	hq.push(1)
	print(hq.repr)
class PriorityQueue:
    """Implementation of a max priority queue garanting insertion in O(1), peeking in O(1) and pulling element of **highest** priority in O(n)"""

    def __init__(self):
        self.elements = []

    def push(self, elem):
        """Push a tuple (priority, value) to the queue"""
        priority, value = elem
        if priority > self.elements[0][0]:
            self.elements = [(priority, value)] + self.elements
        else:
            self.elements.append((priority, value))

    def peek(self):
        """Look at the element of top priority"""
        return self.elements[0]

    def pop(self):
        """Retrieves and removes the element of top priority"""
        highest = self.elements[0]

        if len(self.elements) == 1:
            self.elements.remove(highest)
            return highest

        new_highest_index = 1
        for i in range(2, len(self.elements)):
            if self.elements[i][0] > self.elements[new_highest_index][0]:
                new_highest_index = i

        temp = self.elements[1]
        self.elements[1] = self.elements[new_highest_index]
        self.elements[new_highest_index] = temp

        self.elements.remove(highest)

        return highest


class HeapQueue(PriorityQueue):
    """Min Heap implementation
    Switch to Max Heap with `reverse = True`
    """

    def __init__(self, *, key=lambda x: x, reverse: bool = False):
        super().__init__()
        self.lt = lambda x, y: key(x) < key(y) if reverse else key(x) > key(y)

    def parent(i):
        return int((i - 1) / 2)

    def left_child(i):
        return (i * 2) + 1

    def right_child(i):
        return (i * 2) + 2

    def swap(self, i, j):
        temp = self.elements[i]
        self.elements[i] = self.elements[j]
        self.elements[j] = temp

    def sift_up(self, i):
        while i != 0 and not self.lt(
            self.elements[i], self.elements[HeapQueue.parent(i)]
        ):
            self.swap(i, HeapQueue.parent(i))
            i = HeapQueue.parent(i)

    def push(self, elem):
        i = len(self.elements)
        self.elements.append(elem)
        self.sift_up(i)

    def sift_down(self, i):
        n = len(self.elements)

        # swap elements until either there is at most one child remaining or both children are smaller
        while (
            HeapQueue.left_child(i) < n - 1
            and self.lt(self.elements[i], self.elements[HeapQueue.left_child(i)])
        ) and (
            HeapQueue.right_child(i) < n - 1
            and self.lt(self.elements[i], self.elements[HeapQueue.right_child(i)])
        ):
            if self.lt(
                self.elements[HeapQueue.right_child(i)],
                self.elements[HeapQueue.left_child(i)],
            ):
                self.swap(i, HeapQueue.left_child(i))
                i = HeapQueue.left_child(i)
            else:
                self.swap(i, HeapQueue.right_child(i))
                i = HeapQueue.right_child(i)

        while HeapQueue.left_child(i) < n - 1 and self.lt(
            self.elements[i], self.elements[HeapQueue.left_child(i)]
        ):
            self.swap(i, HeapQueue.left_child(i))
            i = HeapQueue.left_child(i)

        while HeapQueue.right_child(i) < n - 1 and self.lt(
            self.elements[i], self.elements[HeapQueue.right_child(i)]
        ):
            self.swap(i, HeapQueue.right_child(i))
            i = HeapQueue.right_child(i)

    def pop(self):
        n = len(self.elements)
        self.swap(0, n - 1)

        self.sift_down(0)

        top = self.elements[n - 1]
        self.elements.remove(top)

        return top

    def update(self, old_val, new_val):
        i = self.elements.index(old_val)
        self.elements[i] = new_val

        if self.lt(old_val, new_val):
            self.sift_up(i)
        else:
            self.sift_down(i)

    def __repr__(self):
        return repr(self.elements)

    def __len__(self):
        return len(self.elements)


def heap_sort(l, *, key=lambda x: x, reverse=False):
    """Sorts l with a heap sort according to the key `key`"""
    hq = HeapQueue(key=key, reverse=reverse)
    for e in l:
        hq.push(e)

    return [hq.pop() for _ in range(len(hq))]


if __name__ == "__main__":
    hq = HeapQueue()

    hq.push(10)
    hq.push(4)
    hq.push(7)
    hq.push(2)
    hq.push(1)
    hq.push(9)
    hq.push(8)
    hq.push(12)
    hq.push(3)

    print(hq)

    hq.update(12, 6)

    print(hq)

    while hq:
        print(hq.pop())

    l = [10, 4, 7, 2, 1, 9, 8, 12, 3]
    print(l, heap_sort(l, reverse=True))

Other code

#!/usr/bin/env python3
def parent(i) -> int: return (i - 1) // 2
def leftChild(i) -> int: return 2 * i + 1
def rightChild(i) -> int: return 2 * i + 2

class HeapQueue:

    def __init__(self):
        self.heap = []
        self.size = 0

    def swap(self, i, j):
        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]

    def siftUp(self, i):
        while i > 0 and self.heap[parent(i)] > self.heap[i]:
            self.swap(i, parent(i))
            i = parent(i)

    def push(self, value):
        self.heap.append(value)
        self.size += 1
        self.siftUp(self.size - 1)

    def siftDown(self, i):
        minIndex = i
        l = leftChild(i)
        if l < self.size and self.heap[l] < self.heap[minIndex]:
            minIndex = l
        r = rightChild(i)
        if r < self.size and self.heap[r] < self.heap[minIndex]:
            minIndex = r
        if i != minIndex:
            self.swap(i, minIndex)
            self.siftDown(minIndex)

    def pop(self):
        result = self.heap[0]
        self.heap[0] = self.heap[self.size - 1]
        self.size -= 1
        self.siftDown(0)
        return result

    def update(self, i, value):
        old_value = self.heap[i]
        self.heap[i] = value
        if value > old_value:
            self.siftDown(i)
        else:
            self.siftUp(i)

    
    


#!/usr/bin/env python3
import HeapQueue

def heapSort(l, **kwargs):
    h = HeapQueue.HeapQueue()
    if 'key' in kwargs:
        l = [*map(lambda x: (kwargs['key'](x), x), l)]
    for i in l:
        h.push(i)
    nl = []
    for i in range(h.size):
        if 'key' in kwargs:
            nl.append(h.pop()[1])
        else:
            nl.append(h.pop())
    if 'reverse' in kwargs and kwargs['reverse']:
        nl.reverse()
    return nl

#!/usr/bin/env python3
import json
from heapQueue import HeapQueue

class TEI:
    def __init__(self, filename):
        with open(filename) as f:
            self.data = json.load(f)

    def path(self, start, end):
        # applique l'algorithme de Dijkstra pour trouver le chemin le plus court
        # entre start et end
        # retourne une liste de noeuds
        h = HeapQueue()
        h.push((0, [start]))
        visited = {}
        while h.size > 0:
            cost, nodes = h.pop()
            node, *_ = nodes
            if node in visited:
                continue
            visited[node] = 1
            if node == end:
                nodes.reverse()
                return nodes
            for neighbor in self.data[node]:
                if neighbor not in visited:
                    h.push((cost + 1, [neighbor, *nodes]))

class PriorityQueue():
    # Classe abstraite de file de priorité
    def __init__(self, priority):
        self.priority = priority

    def push(self, x):
        pass

    def pop(self):
        pass

    def isempty(self):
        pass

    def update(self, x):
        pass

class HeapQueue(PriorityQueue):
    # Implémentation d'une file de priorité avec un tas

    def __init__(self):
        # Création d'un tas vide
        self.heap = []
        self.size = 0

    def parent(self, i):
        # Revoie l'index du parent du noeud d'index i
        return i//2

    def leftChild(self, i):
        # Revoie l'index de l'enfant gauche du noeud d'index i
        return 2*i + 1

    def rightChild(self, i):
        # Revoie l'index de l'enfant droite du noeud d'index i
        return 2*i + 2

    def swap(self, i, j):
        # Échange les valeurs des noeuds d'index i et j
        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]

    def remonte(self, i, key = lambda x : x):
        # Fait remonter dans le tas la valeur du noeud d'index i
        # jusqu'à ce qu'elle soit supérieure à ses parents

        while key(self.heap[i]) < key(self.heap[self.parent(i)]): #parent(0) = 0 ce qui est bien pratique
            self.swap(i, self.parent(i))
            i = self.parent(i)

    def descend(self, i, key = lambda x : x):
        # Fait descendre dans le tas la valeur du noeud d'index i
        # jusqu'à ce qu'elle soit supérieure à ses parents

        while (self.rightChild(i) < self.size
        and key(self.heap[i]) > min(key(self.heap[self.leftChild(i)]),                  key(self.heap[self.rightChild(i)]))):
            if key(self.heap[self.rightChild(i)]) < key(self.heap[self.leftChild(i)]):
                self.swap(i, self.rightChild(i))
                i = self.rightChild(i)
            else:
                self.swap(i, self.leftChild(i))
                i = self.leftChild(i)

        if (self.leftChild(i) < self.size
        and key(self.heap[i]) > key(self.heap[self.leftChild(i)])):
            self.swap(i, self.leftChild(i))

    def push(self, a, key = lambda x : x):
        # Ajoute un élément au tas en préservant la structure
        if self.size == len(self.heap):
            self.heap.append(a)
        else:
            self.heap[self.size] = a
        i = self.size
        self.size += 1

        self.remonte(i, key)

    def pop(self, i, key = lambda x : x):
        # Retire et renvoie un élément du tas en préservant la structure
        self.swap(i, self.size - 1)
        self.size -= 1

        self.descend(i, key)
        return self.heap[self.size]

    def update(i, a, key = lambda x : x):
        # Met à jour la valeur du noeud d'index i en préservant la structure
        if a < self.heap[i]:
            self.heap[i] = a
            self.remonte(i, key)

        elif a > self.heap[i]:
            self.heap[i] = a
            self.descend(i, key)

def heap_sort(l, key = lambda x : x, reverse = False):
    # Tri par tas
    heap = HeapQueue()
    for x in l:
        heap.push(x, key)

    l_tri = [heap.pop(0, key) for x in l]

    if reverse:
        l_tri.reverse()
    return l_tri

Idées de projets pour experts

Voici des idées de projets pour les experts en programmation. Langages autorisées : C, C++ ou Rust.

Point dans polygone - version ++

  1. Corriger la version du cours pour que les cas dégénérées fonctionnent
  2. L'algorithme du cours est en THETA(n) où n est le nombre de points dans le polygone. Ce n'est pas acceptable pour des grands polygones. Développer une structure de données (que l'on peut appeler polygon) arborescentes que l'on précalcule depuis le polygone afin que les requêtes d'appartenance d'un point dans ce polygone soit plus efficace. Développer une API claire comme:
    • polygon createPolygon(tPoint[] A, int n) qui crée cette structure de données
    • bool isPointInPolygon(tPoint point, polygon P) qui renvoie vrai si le point est dans le polygone P
  3. Implémenter un bidding Python/C pour appeler votre fonction C en Python

Interpréteur Scheme

Scheme est un langage fonctionnelle à typage fort dynamique. L'avantage est que les expressions sont faciles à analyser syntaxiquement (à parser) car ce sont des expressions préfixes parenthésées.

  1. Ecrire un interpréteur pour des expressions simples (+ 1 2) etc.
  2. Etendre aux définitions de fonction etc.
  3. Implémenter une gestion d'exception, le call/cc
  4. Implémenter un bidding Python/C pour appeler votre fonction C d'évaluation d'expression Scheme en Python

Multi-agent path finding

On considère un environnement qui est une grille où certaines cases contiennent des obstacles (immobiles). Un nombre n de robots occupent des positions initiales (des cases libres) et doivent se rendre à des positions finales (des cases libres aussi).

L'objectif est de construire un chemin pour chaque robot (qui se déplace horizontalement ou verticalement d'une case à chaque étape, ou reste sur place) qui l'amène de sa position initiale à sa position finale telle que :

  • les robots n'entrent pas en collision
  • la durée totale de la mission (le max des longueurs des chemins) soit minimale.
  1. Construire un programme qui permet de charger des environnements et des instances
  2. Développer un algorithme basé sur A*
  3. Lire le papier sur CBS [1] et l'implémenter
  4. Implémenter les améliorations de CBS
  5. Implémenter un bidding Python/C pour utiliser votre programme depuis Python

[1] Guni Sharon, Roni Stern, Ariel Felner, Nathan R. Sturtevant: Conflict-based search for optimal multi-agent pathfinding. Artif. Intell. 219: 40-66 (2015)

## Débugueur en raylib (projet très libre)

L'idée est de faire un programme qui discute avec gdb et affiche le contenu de la mémoire graphiquement.

Sokoban (projet très libre)

  1. Ecrire un solveur de Sokoban
  2. Ecrire un générateur de niveau de Sokoban.

Apprentissage et MINST (projet très libre)

Réimplémenter l'architecture utilisé par Yann LeCun pour apprendre à reconnaître des chiffres.

Annonces

25 septembre 2024

  • TD 5 ou TD 6 sera noté

  • N'ayez pas peur de raylib, c'est juste une librairie parmi d'autres

  • Projets challenge pour les élèves qui le veulent

  • Je vais essayer d'être plus lent en cours

  • Vous, posez des questions en cours. N'abandonnez pas !

  • Office hours lundi de 15h30 à 16h30 si vous avez des questions

  • Les élèves qui veulent écouter peuvent venir devant dans l'amphi et les autres (qui font les projets challenge se mettent derrière et écoutent d'une oreille)

  • SVP mettez vos codes de TPs sur vos dépôts git respectifs

2 octobre

  • Commitez, pushez vos codes de TPs afin d'innonder les reviews de code du début de cours.
  • TP 5 sera noté. Vous aurez les instructions de préparation au TP 4.

19 novembre

  • Beaucoup de personne ne comite et pushe pas leur code. C'est pénible pour faire les reviews de code de début de cours.
  • DM noté pour mercredi 27 novembre à 12h15.
    • Nous envoyer (à François, Alice et moi) par mail le lien vers votre dépôt git avec vos TPs avec comme objet du mail [PROG] git.
    • Implémenter Dijkstra/A* avec une classe abstraite de file de priorité et implémenter le tas binaire qui hérite de la classe abstraite. Code simple mais efficace. Vous pouvez reprendre le code du cours et ce que vous avez fait en TP. Pas besoin de faire quelque chose d'original. Mais il faut que le code écrit soit le plus propre possible : commenté, respecte à peu près les principes SOLID, des tests, typer le code.
    • L'implémentation Dijkstra/A* doit être pushé sur vos TDs.
  • Projet Python noté par groupe de trois ou quatre personnes sur les trois prochaines séances : mardi 26 novembre, mercredi 27 novembre, mardi 3 décembre.
    • Le sujet est libre mais vous trouverez deux sujets possibles https://etudes.ens-lyon.fr/course/view.php?id=6334
    • Ici vous trouverez un squelette pour utiliser pygame (pour ceux celles qui veulent) : https://gitlab.aliens-lyon.fr/enslyon-l3-prog/code-vu-en-cours/-/tree/main/python_pygame?ref_type=heads
    • Soutenance le mercredi 4 décembre (démonstration + questions sur le code, pas de transparents à faire).