Les collections en Dart

Les collections en Dart

Manipulation des structures de Données

Salut à tous.
Je m'appelle Suleiman KELANY et je vous souhaite la bienvenue dans mon tutoriel, les collections en Dart.
Je suis formateur, développeur web et mobile.
Dans ce tutoriel, nous allons apprendre à manipuler les collections.
À la fin de ce tutoriel, vous saurez :

  • ce qu'est une collection
  • pourquoi l’utilisée en programmation
  • la différence entre List, Map et Set
  • ce qu'un itérable
  • quand utiliser l'un ou l'autre
  • et les différentes opérations que nous pouvons effectuées dessus
  • convertir du JSON en objet
  • passer d'un Set à une List et d'un Set à une Map

Comme toujours, essayons d’éclairci un peu le thème avant de commencer à faire de quelconques opérations.

Une collection en Dart, est un objet servant à contenir d'autres objets, appelés éléments et qui permet de manipuler certaines structures de données (List, Map, Set).
Plus simplement nous dirons qu'une collection permet de regrouper des données associées.

En programmation informatique, une structure de données est une manière particulière d'organiser et de stocker les données dans un ordinateur de manière à pouvoir les manipuler efficacement au besoin grâce à des algorithmes.

1. List

Une liste est une collection d'objets indexables ayant une longueur.
Elle s'apparente à un tableau dans d'autres langages de Programmation.
Nous utilisons les listes pour maintenir une liste ordonnée de valeurs.
L’exemple suivant nous montre l'initialisation d'une variable étudiante(etudiants), cette variable regroupe un ensemble pour le moment de quatre (04) étudiants.

void main(List<String> args) {

  var etudiants = ['Fleurian', 'Ramanath', 'Monel', 'Lynos'];

  print(etudiants.runtimeType); // List<String>

}

L'appel de runtimeType sur la variable étudiante(etudiants) ici nous donne List<String> , qui représente le type de la variable. Nous avons donc à faire à une variable du type Liste de chaine de Caractères.
Aussi, vu que dans Dart, nous pouvons typer les variables lors de l'initialisation, la variable étudiante précédente peut donc être réécrite comme suit:

 List<String> etudiants = ['Fleurian', 'Ramanath', 'Monel', 'Lynos'];

Plusieurs opérations peuvent être effectuées sur les listes. Parmi lesquelles :

  • longueur ou taille de la liste
 print(etudiants.length); // 4
  • accéder au premier élément de la liste.
  print(etudiants.first); // Fleurian
  • accéder au dernier élément de la liste.
    print(etudiants.last); // Lynos
  • accéder aux éléments d'une liste.

Généralement, il nous arrive en programmation de vouloir accéder à n'importe quel élément d'une liste.

Pour y parvenir, il faut savoir que la caractéristique principale d'une liste(List), est que ses éléments sont indexés à partir de zéro (0), ainsi, le premier élément de la liste a pour indice 0, et la position du dernier élément est donnée par la longueur de la liste moins un (1).
Si on utilise un indice en dehors de cet intervalle, une exception sera levée (Unhandled exception: RangeError (index): Invalid value: Not in inclusive range 0..3 (en prenant pour exemple notre List etudiants actuelle) L'exemple suivant accède au second élément de la liste étudiante (etudiants):

   print(etudiants[1]); // Ramanath
  • trouver l'index d'un élément dans la liste.

Vu que chaque élément de la liste est caractérisé par un index, il peut nous arriver de chercher l'index d'un élément particulier. L'exemple suivant recherche l'index de l'élément 'Monel' dans la liste

   print(etudiants.indexOf('Monel')); // 2

Si l'élément spécifié en paramètre d'indexOf n'est pas dans la liste, indexOf(element) renvoie -1

  • ajouter en fin de la liste
void main(List<String> args) {
  List<String> etudiants = ['Fleurian', 'Ramanath', 'Monel', 'Lynos'];

  etudiants.add('Patrice'); // Ajout Patrice en Fin de la Liste

  print(etudiants); // [Fleurian, Ramanath, Monel, Lynos, Patrice]
}

La fonction add(element) permet donc d'ajouter un élément en fin de liste, au passage, la liste doit être extensible.

Si nous souhaitons ajouter à une position spécifique de la liste, nous pouvons utiliser la fonction insert(index, element).

void main(List<String> args) {
  List<String> etudiants = ['Fleurian', 'Ramanath', 'Monel', 'Lynos'];

  etudiants.insert(0, 'John');

  print(etudiants); // [John, Fleurian, Ramanath, Monel, Lynos]

}

Lors de l'utilisation de la fonction insert, cette opération augmente la longueur de la liste de un et déplace tous les objets.
Aussi, la valeur de [index] doit être non négative et ne doit pas être supérieure à [longueur] de la liste, sinon une exception est levée.

  • Supprimer un élément par son index

La fonction removeAt(index) supprime l'objet situé à la position [index] de la liste

void main(List<String> args) {
  List<String> etudiants = ['Fleurian', 'Ramanath', 'Monel', 'Lynos'];

  etudiants.removeAt(2); // Supprime Monel de la Liste
  print(etudiants); // [Fleurian, Ramanath,  Lynos]
}
  • supprimer une valeur de la liste

La fonction remove(value) supprime la première occurrence de [valeur] de la liste. Cette fonction retourne true si la valeur est dans la liste et retourne false sinon.

void main(List<String> args) {
  List<String> etudiants = ['Fleurian', 'Ramanath', 'Monel', 'Lynos'];

  etudiants.remove('Fleurian'); // Suppression de Fleurian de la liste

  print(etudiants); // [Fleurian, Ramanath,  Lynos]
}

D'autres fonctions de suppression sont encore disponibles, c'est le cas des fonctions removeRange(start, end) qui supprime une plage d'éléments de la liste, ou encore removeLast() comme son nom l'indique permet de supprimer le dernier élément de la liste.

Voir la liste complète des opérations applicables à une liste pour en savoir davantage.

Pour continuer notre aventure sur les listes, nous dirons qu'une liste est aussi une collection Itérable

un Iterable est une collection d'éléments auxquels on peut accéder de manière séquentielle.

Ainsi, Dart nous propose une boucle for-in. qui nous permet d’itérer sur une collection d'objets. l'exemple suivant mets en évidence l’utilisation de for-in sur la variable étudiante (etudiants).

void main(List<String> args) {
  List<String> etudiants = ['Fleurian', 'Ramanath', 'Monel', 'Lynos'];

  for (final etudiant in etudiants) {
    print('${etudiant}');
  }
}

L'extrait de code fourni plus haut, produit la sortie suivante en console:

Fleurian
Ramanath
Monel
Lynos

À chaque itération, une valeur de la liste étudiante (etudiants) est affectée au nom de la variable (etudiant) et cette boucle continue jusqu'à ce que toutes les valeurs de la liste soient épuisées.

Sachez toutefois que la sortie précédente pouvait être obtenue en utilisant une simple boucle for.

void main(List<String> args) {
  List<String> etudiants = ['Fleurian', 'Ramanath', 'Monel', 'Lynos'];

  for (var i = 0; i < etudiants.length; i++) {
    print(etudiants[i]);
  }
}

Imaginons un instant que les quatre étudiants (etudiants) précédents aillent dans une bibliothèque emprunter des livres de programmations, et on part du principe que chaque livre de la bibliothèque ne peut être emprunté que par un étudiant au même instant à la fois. L'objet List de Dart ne nous permets pas de représenter cette structure de données.

Cette structure de données que nous décrivons est caractérisé par les clefs (les livres) et les valeurs (les etudiants). Pour résoudre ce problème, Dart introduit une autre type collection (Map).

2. Map

Une Map est une collection de paires de clé/valeur, à partir de laquelle vous récupérez une valeur à l'aide de sa clé associée.
Il y a un nombre fini de clés dans la carte, et chaque clé a exactement une valeur qui lui est associée. Elle est semblable aux dictionnaires de Swift et aux tableaux associatif en PHP.

En prenants l'exemple de la bibliothèque, des étudiants et des livres nous pouvons définir la variable emprunts de type map suivante dans Dart.

void main(List<String> args) {
  var emprunts = {
    'php': 'Fleurian',  // élement 1 de la map
    'html': 'Ramanath', // élement 2 de la map
    'vbnet': 'Monel', // élement 3 de la map
    'wordpress': 'Lynos', // élement 4 de la map
  };

  print(emprunts); // {php: Fleurian, html: Ramanath, vbnet: Monel, wordpress: Lynos}
  print(emprunts.runtimeType); // _InternalLinkedHashMap<String, String>

}

Vous entourez les Maps avec des accolades { }. Utilisez des virgules pour séparer les éléments de la Map. Les éléments d'une Map sont appelés paires clé-valeur, où la clé est à gauche d'un deux-points(:) et la valeur est à droite. Les éléments de la map sont séparé par des virgules.
En appelant un runtimeType sur la variable emprunt (emprunts) (print(emprunts.runtimeType);), la console Dart ressort _InternalLinkedHashMap<String, String>, qui est un type de Map qui itère dans l'ordre d'insertion de la clé. Les clefs et valeurs de la map étudiante (etudiants) actuels sont tous des String.
Nous pouvons alors réécrire la map emprunts d'une autre façon en typant les clefs et les valeurs lors de la création.

  Map<String, String> emprunts = {
    'php': 'Fleurian',
    'html': 'Ramanath',
    'vbnet': 'Monel',
    'wordpress': 'Lynos',
  };

D’après la définition de Map donné plus haut, nous pouvons alors récupérer les valeurs en nous basant des clefs. Le code suivant récupère toutes les clefs de la map emprunts :

void main(List<String> args) {
  Map<String, String> emprunts = {
    'php': 'Fleurian',
    'html': 'Ramanath',
    'vbnet': 'Monel',
    'wordpress': 'Lynos',
  };

  // Clefs
  print(emprunts.keys); // (php, html, vbnet, wordpress)
  // Valeurs
  print(emprunts.values); // (Fleurian, Ramanath, Monel, Lynos)

}

Essayons de voir le type de emprunts.keys

  print(emprunt.keys.runtimeType); //  _CompactIterable<String>

L'objet emprunts.keys est de type _CompactIterable<String> qui est un itérable( Iterable ) Dart.
Plutôt dans ce tutoriel, nous avons vu qu'on pouvait accéder aux valeurs d'un itérable de manière séquentielle.
Ainsi, à l'aide de la boucle for-in nous pouvons récupérer toutes les clefs de la Map :

void main(List<String> args) {
  Map<String, String> emprunts = {
    'php': 'Fleurian',
    'html': 'Ramanath',
    'vbnet': 'Monel',
    'wordpress': 'Lynos',
  };

  for (final key in emprunts.keys) {
    print(key);
  }
}
  • accéder au premier et au dernier élément de la map.

Dans certains cas, vous souhaitez accéder uniquement au premier ou à la dernière clef de la Map. Pour cela vous pouvez utiliser les getters first ou last qui récupèrent respectivement le premier et le dernier élément.

void main(List<String> args) {
  Map<String, String> emprunts = {
    'php': 'Fleurian',
    'html': 'Ramanath',
    'vbnet': 'Monel',
    'wordpress': 'Lynos',
  };

print(emprunts.keys.first); // php
print(emprunts.keys.last); // wordpress

}

Contrairement aux Listes où on pouvait accéder aux éléments en utilisant [index], cela n'est pas possible avec les Map. Pour parcourir donc les clefs de Map on va utiliser la méthode elementAt(index)

void main(List<String> args) {
  Map<String, String> emprunts = {
    'php': 'Fleurian',
    'html': 'Ramanath',
    'vbnet': 'Monel',
    'wordpress': 'Lynos',
  };

print(emprunts.keys.elementAt(0)); // php
print(emprunts.keys.elementAt(1)); // html
print(emprunts.keys.elementAt(2)); // vbnet
print(emprunts.keys.elementAt(3)); // wordpress

}
  • longueur d'une Map.

Tout comme la liste, la Map est aussi définie par une longueur qui peut être récupérée par length.

void main(List<String> args) {
  Map<String, String> emprunts = {
    'php': 'Fleurian',
    'html': 'Ramanath',
    'vbnet': 'Monel',
    'wordpress': 'Lynos',
  };

  print(emprunts.keys.length); // 4
  print(emprunts.length); // 4
}
  • ajouter une ou plusieurs paires clé/valeur d'une Map.

La méthode addAll(others), nous permet d'ajouter une paire ou autant de paires clé/valeur que nous souhaitons à une Map.
L'exemple suivant ajoute deux clés(javascript, dart) à la map emprunts avec respectivement deux valeurs (Patrice, Charlotte), puis nous affichons ensuite la map emprunts pour voir son contenu.

void main(List<String> args) {
  Map<String, String> emprunts = {
    'php': 'Fleurian',
    'html': 'Ramanath',
    'vbnet': 'Monel',
    'wordpress': 'Lynos',
  };

  emprunts.addAll({'javascript': 'patrice', 'dart': 'Charlotte'});

  print(emprunts); // {php: Fleurian, html: Ramanath, vbnet: Monel, wordpress: Lynos, javascript: patrice, dart: Charlotte}

}
  • supprimer toutes les entrées de Map.

La méthode clear(), permet de vider supprimer toutes les entrées(paire cle-valeur) d'une map. Ainsi, a la fin de cette opération, la map est totalement vide.

emprunts.clear();
  • supprimer une entrée de Map.

La méthode remove(key) permet de supprimer une clé (key) et sa valeur associée.
L'exemple suivant supprime la clef php de la map emprunts

emprunts.remove('php');

Je vous invite à regarder d'autres méthodes disponible sur la documentation officielle. Si vous aviez du mal à comprendre une méthode, vous pouvez me le faire savoir en commentaire et je vous donnerai plus d'exemples concernant cette dite méthode.

Poursuivons notre aparté sur les maps. Imaginons que nous voulons récupérer les données provenant d'une autre application ou d'un backend dans notre application Dart. Alors à quoi devons-nous attendre?

Les amis, prenons par exemple un site comme JSONPlaceholder qui nous fournir des faux API gratuits pour les tests et le prototypage.
Essayons de voir les données diffusées à l'adresse (jsonplaceholder.typicode.com/albums/1). Si vous être effectivement allez sur le lien précédent, vous devriez voir quelque chose ressemblant au code suivant;

{
  "userId": 1,
  "id": 1,
  "title": "quidem molestiae enim"
}

Dites-moi, cela ne ressemble-t-il pas à quelque chose que vous aviez déjà rencontré dans ce tutoriel? Au cas où vous auriez oublié, laisser mois vous rafraichis la mémoire, il s'agit d'une map en Dart. Mais dans les jargons des apis, cette structure est appelée JSON(JavaScript Object Notation).

Faisons alors de cette structure une variable Dart, on obtiens alors le code suivant

  var album = {
    "userId": 1,
    "id": 1,
    "title": "quidem molestiae enim",
  };

Dans un tutoriel précédent, Introduction à la Programmation Orientée , nous avons appris à écrire des classes et des constructeurs.
Utilisons alors nos connaissances antérieures pour construire une classe Album qui contient les données de la requête réseau provenant du site jsonplaceholder.

class Album {
  final int userId;
  final int id;
  final String title;

  Album({
    required this.userId,
    required this.id,
    required this.title,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

Cette classe, inclut un constructeur d'usine(factory) nommé qui crée un Album à partir du JSON renvoyer par jsonplaceholder. Notre constructeur est donc une fonction factory, c'est a dire une fonction qui renvoie une instance d'une classe.

Le code complet suivant montre l'exemple d'utilisation de la classe et du constructeur factory.

void main(List<String> args) {
  // Objet Provenant de JsonPlaceholder
  var album = {
    "userId": 1,
    "id": 1,
    "title": "quidem molestiae enim",
  };

  var album1 = Album.fromJson(album); // Conversion du Json en Objet

  print(album1); // Instance of 'Album'
}

class Album {
  final int userId;
  final int id;
  final String title;

  Album({
    required this.userId,
    required this.id,
    required this.title,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

Fondamentalement, il est très important de garder bien au chaud le constructeur factory car dans la pratique des API en Dart, vous seriez amené à le faire plus souvent.

3.Set

Imaginons-nous enregistrer des valeurs uniques dans une seule variable, peut importe leur ordre d'entrée, pour cela, en Dart il nous faut utiliser une collection de type Set. Set est donc un liste ou toutes les entrées sont unique.

Il existe deux manières de déclarer des collections de types Set. Le code suivant nous le montre.

void main(List<String> args) {
  var identifiants = {1, 2};

  Set identifiant2 = {1, 2, 1};

  print(identifiants); // {1, 2};
  print(identifiant2); // {1, 2};
}

Malgré le fait qu'identifiant2 ait trois valeurs en entrée, en sortir on a juste deux valeurs, la valeur répétée a simplement été ignorée.
Tous comme les Map, List, il est également possible de faire des opérations sur un Set.

  • longueur d'un Set.

La longueur de la Set est obtenue en appelant la méthode length sur la variable.

void main(List<String> args) {
  Set<String> noms = {'Johny', 'dupond'};

  print(noms.length); // 2
}
  • ajouter à une Set.

Pour ajouter un élément à une Set, on peut utiliser la fonction add(value) ou addAll(elements) pour ajouter plusieurs valeurs.

void main(List<String> args) {
  Set<String> noms = {'Johny', 'dupond'};

  noms.add('salomon'); // Ajout d'un nom

  noms.addAll({'pierre', 'paul', 'pierre'}); // Ajout de plusieurs noms

  print(noms); // {Johny, dupond, salomon, pierre, paul}
}

Dans cet exemple, on remarque encore que la valeur répétée pierre a été ignorée.

  • supprimer une valeur du Set
   noms.remove('Johny');
  • vider un Set.
   noms.clear();
  • parcourir un set
void main(List<String> args) {
  Set<String> noms = {'Johny', 'dupond'};

  noms.forEach((element) {
    print(element); 
    // Sortie de la console: 
    // Johny
    // dupond
  });
}

Dans certains cas, on peut avoir besoin de passer d'un type de collection a un autre durant notre programme. Dans les exemples qui suivront, essayons de passer de Set à List, puis de Set a map:

  • Conversion d'un Set en List
    Pour pouvoir convertir un Set en List, on utilise la methode toList();
void main(List<String> args) {
  Set<String> noms = {'Johny', 'dupond'};

  List<String> maListe = noms.toList();

  print(maListe); // [Johny, dupond]
}
  • Conversion d'un Set en map
void main(List<String> args) {
  Set<String> noms = {'Johny', 'dupond'};

  var maListe = noms.map((e) {
    return 'Cle - $e';
  });

  print(maListe); // (Cle - Johny, Cle - dupond)
}

Hé les amis tout au long de ce tutoriel, nous avons abordé la notion de collection, notamment avec Map Set, List. Et nous avons vu quelques opérations utiles.

J'espère que vous manipulerez les collections dans vos prochains programmes Dart, sur ce, je vous dis à bientôt pour un nouveau tutoriel.