Les listes chaînées et les arbres

Aujourd'hui, nous voir comment la glib-2.0 nous permet de manipuler facilement et de manière très complète les listes chaînées.

Présentation

Les listes chaînées sont des structures de données dynamiques permettant de stocker un grand nombre d'éléments, de manière beaucoup plus souple qu'avec un simple tableau. Chacun des éléments d'une telle liste (d'une telle chaîne), est composé d'une donnée, d'un pointeur sur l'élément précédent de la chaîne et d'un pointeur sur l'élément suivant. Au sein de la glib-2.0, ces éléments, ces maillons, sont représentés par la structure suivante:

typedef struct _GList GList;

struct _GList

gpointer data;
GList *next;
GList *prev;
;

data est un pointeur générique sur un élément de type quelconque, next est un pointeur sur le maillon suivant de la chaîne et prev est un pointeur sur le maillon précédent de la chaîne. Si l'élément précédent n'existe pas (notre maillon est le premier de la chaîne), prev vaut NULL. De même, pour le dernier maillon de la chaîne, next vaut NULL.

Tout ce dont on a besoin pour pouvoir manipuler une telle chaîne, c'est un pointeur sur le début (le premier maillon) de celle-ci. Si ce pointeur vaut NULL, c'est que la chaîne est vide.

Insertion

Pour ajouter un élément à une chaîne, on peut utiliser la fonction suivante :
GList *g_list_append(GList *list,
gpointer data);
list doit être un pointeur sur une chaîne valide, ou NULL, si l'on veut créer le premier maillon d'une nouvelle chaîne.
data est un pointeur sur l'élément que l'on veut introduire dans la chaîne. La valeur renvoyée par cette fonction est le nouveau pointeur sur le début de la chaîne. Il est important de bien sauvegarder cette nouvelle valeur, car c'est la seule référence réelle à la chaîne. La fonction g_list_append() ajoute l'élément à la fin de la chaîne. Si l'on veut ajouter un élément en début de chaîne, on utilisera la fonction suivante :
GList *g_list_prepend(GList *list,
gpointer data);
qui fonctionne exactement de la même façon.

Il est également possible d'insérer un élément dans une chaîne à un endroit quelconque à l'aide de la fonction :
GList *g_list_insert(GList *list,
gpointer data,
gint position);
Le paramètre position indique où il faut insérer le nouvel élément. S'il est négatif ou plus grand que la taille actuelle de la chaîne, le nouvel élément sera inséré à la fin de la chaîne. S'il vaut zéro, il sera inséré en début de chaîne. Sinon, il sera inséré comme le ``positionième'' élément de la chaîne.

La dernière façon d'ajouter un maillon dans une chaîne est d'utiliser la fonction suivante :
GList *g_list_insert_sorted(GList *list,
gpointer data,
GCompareFunc cmp_fonction);
Cette fonction permet d'insérer les éléments suivant un ordre précis défini par la fonction cmp_fonction. Cette fonction doit avoir le prototype suivant:
gint cmp_fonction(gconstpointer a, gconstpointer b);
a et b sont des pointeurs sur les données de deux maillons de la chaîne. Cette fonction doit renvoyer une valeur positive si a est supérieur à b (ici, le sens du mot "supérieur" est entièrement défini par la fonction...), une valeur négative si a est inférieur à b et 0 si a et b sont égaux. Par exemple, pour une liste où tous les éléments sont des chaînes de caractères, on peut insérer une nouvelle chaîne, dans l'ordre alphabétique par un appel du genre:
list = g_list_insert_sorted(list, "nouvelle chaîne",
(GCompareFunc)strcmp);

A vrai dire, je déconseille plutôt l'utilisation de g_list_insert_sorted() lorsque l'on veut créer une grande chaîne triée à partir de données brutes. Il est en général beaucoup plus rapide d'utiliser g_list_prepend() pour chacun des éléments, puis de trier la chaîne une fois constituée à l'aide de la fonction suivante:
GList *g_list_sort(GList *list,
GCompareFunc cmp_fonction);
où le paramètre cmp_fonction a le même rôle que dans la fonction g_list_insert_sorted().

Suppression

Lorsque l'on parle d'un maillon (ou d'un élément), il faut bien faire attention à ne pas confondre deux notions:
•        
le pointeur sur l'élément proprement dit, de type gpointer, donc générique. Il peut s'agir d'un pointeur sur une structure, d'un pointeur sur une chaîne de caractères ou même d'un entier, par exemple en utilisant la macro GINT_TO_POINTER().
•         le pointeur sur la structure de type GList, qui contient les pointeurs sur les maillons précédents et suivants et sur l'élément proprement dit.

C'est pourquoi il existe plusieurs fonctions permettant de supprimer un maillon de la chaîne:
GList *g_list_remove(GList *list,
gconstpointer data);
supprime le premier maillon de la liste list dont la donnée est désignée par le pointeur data.

Il peut bien entendu exister plusieurs maillons possédant le même pointeur data (ou s'il on a affaire à une liste d'entier, plusieurs maillons avec la même valeur numérique...). Aussi est-il possible de supprimer tous les maillons dont la donnée est pointée par data avec la fonction suivante:
GList *g_list_remove_all(GList *list,
gconstpointer data);

Il est également possible de supprimer un maillon, en connaissant un pointeur sur sa structure GList avec la fonction suivante:
GList *g_list_remove_link(GList *list,
GList *maillon);
Attention! Contrairement aux précédentes, cette fonction ne libère pas la mémoire réservée par la structure GList (je ne parle pas de celle qui est éventuellement pointée par le pointeur data). C'est pourquoi on l'utilise assez peu. On lui préférera la fonction:
GList *g_list_delete_link(GList *list,
GList *maillon);
qui fonctionne à la manière de g_list_remove().

Opérations sur les listes

Une fois une liste constituée, il est possible d'effectuer un certain nombre d'opérations. Vous connaissez déjà g_list_sort(), qui permet de trier la liste.
La fonction
GList *g_list_reverse(GList *list);
permet d'inverser l'ordre de tous les maillons d'une chaîne.

On peut effectuer une copie d'une liste à l'aide de la fonction suivante:
GList *g_list_copy(GList *list);
Il faut bien garder à l'idée que cette fonction ne fait que copier les structures GList, en les reliant entre elles. Les éventuelles structures pointées par les pointeurs data ne sont pas copiées.

Il est également possible de placer le contenu d'une liste à la fin d'une autre avec:
GList *g_list_concat(GList *list1,
GList *list2);
Cette fonction concatène la liste list2 à la liste list1. Rien n'est copié. Il s'agit juste d'une fusion des deux listes.

Enfin, la fonction la plus intéressante de toutes est, à mon avis, la suivante:
void g_list_foreach(GList *list,
GFunc fonction,
gpointer donnee_sup);
Un seul appel à cette fonction appellera la fonction fonction autant de fois qu'il y a de maillons dans la chaîne. Cette fonction est appelée avec deux arguments. Le premier est le pointeur data de chacun des maillons, le second est le paramètre donnee_sup. Par exemple, pour afficher toutes les données d'une liste de chaînes de caractères, on pourra utiliser la fonction suivante:
void afficher_maillon(gchar *data, gchar *format)

printf(format, data);


Puis, cette fonction pourra être appelée pour chacun des maillons grâce à l'appel suivant:
g_list_foreach(list, (GFunc)afficher_maillon, "%sn");

Les recherches dans les listes.

Il est possible d'effectuer différents types de recherche dans une liste. Par exemple, il est possible de retrouver le premier ou le dernier maillon d'une chaîne à partir d'un maillon quelconque de cette chaîne à l'aide, respectivement, des deux fonctions suivantes:
GList *g_list_first(GList *maillon);
GList *g_list_last(GList *maillon);

Les deux macros suivantes permettent respectivement de retrouver le maillon précédant ou suivant un maillon donné:
GList *g_list_previous(GList *maillon);
GList *g_list_next(GList *maillon);

Il est possible de retrouver le nième maillon d'une liste grâce à la fonction suivante:
GList *g_list_nth(GList *list,
guint n);
En fait, cette fonction fait bien plus que cela. Elle retrouve aussi le nième maillon après un maillon donné. Par exemple, Pour retrouver le 5ième maillon après le maillon maillon1, on utilisera:
maillon = g_list_nth(maillon1, 5);

Réciproquement, il est possible de retrouver le nième maillon avant un maillon donné avec la fonction suivante:
GList *g_list_nth_prev(GList *list,
guint n);

Pour une recherche un peu plus avancée, la fonction suivante permet de retrouver le maillon (le pointeur GList *) à partir de son pointeur data.
GList *g_list_find(GList *list,
gconstpointer data);
Pour cela, elle compare, un à un tous les pointeurs data des maillons de la chaîne au paramètre data. S'ils sont égaux, le maillon correspondant est renvoyé.

Si vous voulez utiliser une fonction particulière pour tester l'égalité des pointeurs data, vous pouvez utiliser la fonction suivante:
GList *g_list_find_custom(GList *list,
gconstpointer data,
GCompareFunc fonction);
fonction est une fonction de la même sorte que celles utilisées avec g_list_insert_sorted().

Toutes les fonctions de recherche présentées jusqu'à maintenant renvoient un pointeur sur la structure GList du maillon si un maillon est effectivement trouvé, et NULL sinon.

En connaissant un maillon dans une liste, il est possible de connaître son rang avec la fonction suivante:
gint g_list_position(GList *list,
GList *llink);
qui fonctionne un peu à l'inverse de la fonction g_list_nth().

De même, il est possible de connaître la position du premier maillon dont le pointeur data est data:
gint g_list_index(GList *list,
gconstpointer data);

Enfin, la fonction suivante renvoie le nombre de maillons que comporte une liste donnée:
guint g_list_length(GList *list);

Voyons maintenant un programme exemple qui met en uvre quelques-unes de toutes ces fonctions.

/* glist.c */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>

/* Fonction pour afficher chacun des maillons */
void afficher_maillon(gchar *data, gchar *format)

printf(format, data);


/* fonction de comparaison de deux maillons de la liste liste_entier */
gint compare(gconstpointer data1, gconstpointer data2)

gint valeur1, valeur2;

valeur1 = GPOINTER_TO_INT(data1);
valeur2 = GPOINTER_TO_INT(data2);
return valeur1 - valeur2;


int main(int argc, char *argv[])

GList *liste_entier = NULL;
GList *liste_chaine1 = NULL;
GList *liste_chaine2 = NULL;
GList *maillon;
gint i, valeur;

/* Affichage du numéro de version de la glib */
printf("glib version : %d.%d.%dn",
GLIB_MAJOR_VERSION,
GLIB_MINOR_VERSION,
GLIB_MICRO_VERSION);

/* On crée une liste d'entiers */
for (i=0 ; i < 20 ; i++)

valeur = rand() % 100;
liste_entier = g_list_prepend(liste_entier, GINT_TO_POINTER(valeur));

/* on l'affiche : */
g_list_foreach(liste_entier, (GFunc)afficher_maillon, "%d, ");
printf("n");
/* on la trie :*/
liste_entier = g_list_sort(liste_entier, (GCompareFunc)compare);
/* puis on l'affiche à nouveau */
g_list_foreach(liste_entier, (GFunc)afficher_maillon, "%d, ");
printf("n");

/* Création de la liste de chaîne de caractères */
liste_chaine1 = g_list_append(liste_chaine1, "lundi");
liste_chaine1 = g_list_append(liste_chaine1, "mardi");
liste_chaine1 = g_list_append(liste_chaine1, "merdredi");
liste_chaine1 = g_list_append(liste_chaine1, "jeudi");
liste_chaine2 = g_list_append(liste_chaine2, "vendredi");
liste_chaine2 = g_list_append(liste_chaine2, "samedi");
liste_chaine2 = g_list_append(liste_chaine2, "dimanche");

/* on concatène tous les deux listes : */
liste_chaine1 = g_list_concat(liste_chaine1, liste_chaine2);

/* on récupère le pointeur sur le maillon contenant "merdredi" */
maillon = g_list_find_custom(liste_chaine1, "merdredi",
(GCompareFunc)strcmp);
/* on change la donnée de ce maillon */
maillon->data = "mercredi";
/* et on affiche la liste */
g_list_foreach(liste_chaine1, (GFunc)afficher_maillon, "%s, ");
printf("(%d jours)n", g_list_length(liste_chaine1));

return 0;

Compilez ce programme avec la ligne de commande suivante :
gcc glist.c -o glist `pkg-config --cflags --libs glib-2.0` -O2 -g -Wall

Et exécutez-le. Vous devriez obtenir quelque chose comme ceci :
glib version : 1.3.6
36, 72, 26, 40, 26, 63, 59, 90, 27, 62, 21, 49, 92, 86, 35, 93, 15, 77, 86, 83,
15, 21, 26, 26, 27, 35, 36, 40, 49, 59, 62, 63, 72, 77, 83, 86, 86, 90, 92, 93,
lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche, (7 jours)

Les GSLists

Il existe un autre type de listes, gérées par la glib-2.0. Il s'agit des GSLists. Elles fonctionnent de la même façon que les GLists, mais les maillons des GSLists ne possèdent pas de pointeurs prev. Cela permet de gagner un peu de place, mais pour le coup, certaines fonctions ne sont plus accessibles.
Les fonctions qui gèrent les GLists portent le même nom que celles des GLists. Simplement, le préfixe g_list_ est remplacé par g_slist_.

La prochaine fois

La prochaine fois, nous étudierons une autre structure de données gérée par la glib-2.0: les arbres.

--
<David.Odin@bigfoot.com> <odin@mandrakesoft.com>
Créateur et coordinateur de Giram
Auteur de Crafted (projet Freecraft)
Copyright (c) Linux Magazine France
Permission vous est donnée de distribuer des copies exactes de cette page tant que cette note de permission et le copyright apparaissent clairement.