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
- Django: Tutorial
- 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
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
- Histoire du C : https://marc.mongenet.ch/Articles/C/
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 ça | Il faut taper ça |
---|---|
Pour récupérer du code du serveur | git clone <adresse> |
Pour dire qu'un fichier particulier doit être versionné | git add <nomdufichier> |
Pour estampiller mes fichiers | git commit -a -m "algorithme Dijkstra" |
Pour mettre mes modifications sur le serveur | git push |
Pour récupérer les modifications des autres depuis le serveur | git pull |
Pour connaître l'URL du dépôt distant (serveur) | git remote -v |
Commandes avancées
Pour faire ça | Il faut taper ça |
---|---|
Modifier l'URL du serveur d'un dépôt local | git 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 ligne | Ctrl + A |
Aller à la fin de ligne | Ctrl + E |
Supprimer tout ce qu'il y a après le curseur | Ctrl + K |
Pour coller ce qu'il y avait | Ctrl + Y |
Pour enlever la commande courante | Ctrl + C |
Rechercher une commande qu'on a écrite il y a longtemps | Ctrl + R |
Basic commands
To do... | write |
---|---|
Going in the directory <oùaller> | cd <oùaller> |
Going to the home directory | cd ~ |
List the files | ls and better ls -l |
Create a directory | mkdir <name> |
Remove/delete sth | rm <what> |
Search inside a file | grep 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
|
où
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ère | Ctrl + L |
Bien formater le fichier | Ctrl + Maj + I |
Fermer le fichier courant | Ctrl + W |
Pour renommer une variable/function | F2 |
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 utilisentraylib
). - Faire
sudo make install
. Cela va copier la librairie statique.a
dans 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
Commandes | Raccourcis | Effets et exemples |
---|---|---|
quit | q | on quitte le débogueur |
breakpoint 15 | b 15 | met un point d'arrêt à la ligne 15 |
breakpoint bloup.c:15 | b bloup.c:15 | met un point d'arrêt à la ligne 15 du fichier bloup.c |
breakpoint dfs | b dfs | met un point d'arrête au début de la fonction dfs |
watch x | w x | on s'arrête à chaque fois que la variable x change |
print x | p x | on affiche la valeur de la variable x |
list | l | on affiche l'endroit dans le code où on est |
step | s | on fait un pas dans l'exécution du programme |
next | n | on fait un pas mais en considérant un appel de fonction au niveau courant comme atomique |
continue | c | on continue l'exécution jusqu'au prochain point d'arrêt |
backtrace | bt | On affiche la pile d'exécution (la pile d'appels) |
clear 15 | enlève le point d'arrêt à la ligne 15 | |
clear dfs | enlève le point d'arrête au début de la fonction dfs | |
delete | supprime tous les points d'arrêt | |
delete 2 | supprime 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 ?
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 ?
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
?
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
%
signifienimportequelnomdefichier
. $@
= le nom de la règlenimportequelnomdefichier.o
$^
= la prémisse, icinimportequelnomdefichier.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)
When | What |
---|---|
1972 | Creation of C by Dennis Ritchie |
1973 | Re-implementation of the Unix kernel |
1978 C78 | first edition of the book The C Programming Language by Brian Kernighan |
and Dennis Ritchie and improvements of the language (functions returning struct) | |
1989 C89 | First standardization of C, locales (taking into account user's languages) |
1999 C99 | inline functions! complex numbers! variable-length arrays! |
2011 C11 | generic 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
incrementx
. - 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
Options | Meaning |
---|---|
-Wall | enables all compiler's warning messages |
-Wextra | still a bit more warnings |
-pedantic | enables all compiler's warning messages |
-ansi | turns off gcc features that are not in the ANSI C |
-std=c99 | ANSI C99 |
-std=c11 | ANSI C99 |
-o programme | tells 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.
Scope | Life-time | |
---|---|---|
Global variables | All | Always |
Variables in functions | Function | Function |
Static variables in functions | Function | Always |
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
- https://emscripten.org/ Pour développer des programmes qui tournent sur le Web
- https://cs50.readthedocs.io/style/c/
- https://www.gnu.org/prep/standards/html_node/Writing-C.html#Writing-C
- Fonctions de base en langage C sur wikiversité
- Du matériel sur la génération de labyrinthes
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
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
Type | Nombre de bits |
---|---|
int | 32 |
char | 8 |
short | 16 |
long | 32 ou 64 |
long long | 64 |
- Quelles sont les valeurs possibles pour un
unsigned char
?Entre \(0\) et \(2^{8}-1\) autrement dit entreEntre 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
- Quelles sont les valeurs possibles pour un
char
?Entre \(-2^{7}\) et \(2^{7}-1\) autrement dit entreEntre -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.
Type | Nombre de bits | Exemples |
---|---|---|
float | 32 | 12.34F |
double | 64 | 12.34, 12.e-4 |
long double | 80 ou 128 | 12.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]
?
00000000
, donc il vaut 0.
Que vaut m.n[2]
?
00000010
, donc il vaut 2.
Que vaut m.n[3]
?
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
?
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 ?
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);
Adresse | Données |
---|---|
adresse | *adresse |
&data | data |
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é parint 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é parint *a;
, qu'est ce que*a
?*a
désigne l'entier que l'on peut lire à l'adresse mémoirea
.
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 ?
Combien d'octets prend p
en mémoire ?
Exercices
int x = 42;
int y;
y = x;
y++;
Que vaut x
? 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
? 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 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*
?
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
?
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 ?
- Qu'est ce qu'il y a dans la pile ?
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
?
- Où est stocker
*p
avec le malloc ci-dessus ?
Dans le tas.
- Que contient
p
?
- 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);
- 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);
- 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 int
)
La différence de pointeurs est un nombre relatif.
Dans l'exemple p - q
vaut
Marrant !
int a[3] = {0, 1, 42};
Que vaut a[2]
?
Que vaut 2 [a]
?
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';
- Que pensez-vous du programme suivant ?
char* s = "abca";
s[2] = 'b';
- 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';
<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';
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';
char* A = malloc(4);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[3] = `\0`;
\0
.
char* A = malloc(10);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[3] = `\0`;
\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émoiret
. -
Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char s[50] = "Bonjour";
return s;
}
- Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char* s = "Bonjour";
return s;
}
- 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;
}
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înes
.
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") |
---|---|---|
< 0 | 0 | > 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'appelstrdup
) de la même taille quesource
- 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 dedst
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 questrcat
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 variantestring_concat
qui renvoie une nouvelle chaîne de caractères qui est la concaténation des1
ets2
.
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ènes2
. Si pas assez de mémoire, on réalloues1
. La fonction renvoie la nouvelle chaîne. Dans tous les cas, la chaînes1
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
vaut3 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);
?
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 deoffset >= 0
depuis le début du fichier - Si
whence == SEEK_CUR
(1) alors on se déplace deoffset
depuis la position courante - Si
whence == SEEK_END
(2) alors on se déplace deoffset <= 0
depuis la fin du fichier
- D'après vous que fait
fseek(stdout, 2, SEEK_SET)
?
Lecture/écriture de données
size_t
fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream)
fread
litnitems
éléments desize
octets du fichierstream
. Elle les écrit à l'adresseptr
.- Elle renvoie le nombre d'éléments lus (a priori
nitems
, 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
?
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>
.
- Tu poses un jalon pour y revenir plus tard
- Tu exécutes des choses (chargement d'un gros fichier par exemple)
- Tu tombes sur une erreur en croisant un ours
- 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 deprintf(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 ?
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 :
- au moins on est sûr que ça même avec des anciennes versions de C sous Unix.
- La librairie standard n'est pas franchement différente.
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éé dansmon_thread
. C'est pourquoi on passe l'adresse demon_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 demon_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);
}
- 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);
}
- 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);
}
- 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);
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 ?
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 ?
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 ?
- Est-ce que vous voyez un défaut à cette approche ?
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 ?
- Est-ce que vous voyez un défaut à cette approche ?
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;
}
- Est-ce que une valeur inférieure à la valeur attendue ?
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 typem42
. -
En image,
pthread_mutex_unlock(&m42);
, est une dalle qui ouvre les portes de typem42
.
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 1 | Thread 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
C | Python | |
---|---|---|
Use case | System | Prototyping, scripting, data science, etc. |
Users | Computer scientists | Computer scientists, Biologists, etc. |
Usage | Compiled to machine code | Compiled to bytecode, interpreted |
Typing | Static, weak | Dynamic, strong (somehow static with mypy ) |
Memory usage | By 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
When | What |
---|---|
1991 | Creation of Python by Guido van Rossum |
as a command-line interpreter for the OS Amoeba (research project) | |
1994 | Python 1.0: lambda, map, filter and reduce from LISP |
2000 | Python 2.0: list comprehensions from SETL (SET Language) and Haskell |
2001 | Creation of the Python Software Foundation |
dec 2008 | Python 3.0 First standardization of C, locales (taking into account user's languages) |
2014 | Python package manager pip by default |
- PEP 7 – Style Guide for C Code
- PEP 8 – Style Guide for Python Code
- PEP 20 – The Zen of Python
- PEP 257 – Docstring Conventions
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.
Mutable | Immutable |
---|---|
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 | |
set | frozenset |
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
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 tupleargs
- all (remaining) keyword parameters
**kwargs
as a dictionarykwargs
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
isdas selbe
==
isdas 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
andy
? - Are
xList
andyList
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
from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("Dog", "Animal"), ("Cat", "Animal"), ("Fish", "Animal")])
dot
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
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
from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("ArrayPriorityQueue", "PriorityQueue"), ("BinaryHeap", "PriorityQueue"), ("FibonacciHeap", "PriorityQueue")])
dot
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
- In Java, also every class is a subclass of
Java.lang.Object
. However, a class, e.g.Animal
itself is not an object butAnimal.class
is and inherits fromjava.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
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
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
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
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
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 ofISound
. 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 naturelx + 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 objetstring
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 objetstring
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 variables
.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
- https://www.thecodedmessage.com/posts/cpp-move/
- Vidéo à CPPCON 2021 sur la move semantics, dont est inspiré ce cours https://www.youtube.com/watch?v=Bt3zcJZIalk
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 ?
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), } }
où 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
Vecteurs | Chaînes de caractères | |
---|---|---|
Type pour la grosse structure où on peut changer la taille | Vec<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
enString
:String::from
- Pour avoir la
str
totale d'unString
: 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
ouint* 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 ++
- Corriger la version du cours pour que les cas dégénérées fonctionnent
- 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éesbool isPointInPolygon(tPoint point, polygon P)
qui renvoie vrai si le point est dans le polygone P
- 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.
- Ecrire un interpréteur pour des expressions simples
(+ 1 2)
etc. - Etendre aux définitions de fonction etc.
- Implémenter une gestion d'exception, le
call/cc
- 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.
- Construire un programme qui permet de charger des environnements et des instances
- Développer un algorithme basé sur A*
- Lire le papier sur CBS [1] et l'implémenter
- Implémenter les améliorations de CBS
- 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)
- Ecrire un solveur de Sokoban
- 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.
- 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
- 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).