No history yet

Structures de données avancées

Optimisez Votre Code avec les Structures de Données Avancées

Vous connaissez déjà les listes et les dictionnaires, les piliers de la manipulation de données en Python. Il est maintenant temps de passer à la vitesse supérieure. Pour gérer des volumes de données réels de manière efficace et élégante, il faut choisir l'outil adapté à la tâche. Ce n'est pas seulement une question de syntaxe, mais une question de performance et de lisibilité.

Choisir la bonne structure de données peut faire la différence entre un code qui fonctionne et un code qui excelle.

Listes par Compréhension : L'Élégance Efficace

Les listes par compréhension (list comprehensions) sont une des fonctionnalités les plus appréciées de Python. Elles permettent de créer des listes de manière concise et lisible, en s'inspirant de la notation mathématique des ensembles. Au lieu d'écrire une boucle for complète pour créer et remplir une liste, vous pouvez tout faire en une seule ligne expressive.

# Méthode traditionnelle
nombres = [1, 2, 3, 4, 5]
carres = []
for n in nombres:
  if n % 2 == 0: # Ne garder que les nombres pairs
    carres.append(n**2)

# Avec une liste par compréhension
carres_comprehension = [n**2 for n in nombres if n % 2 == 0]

print(carres) # Affiche [4, 16]
print(carres_comprehension) # Affiche [4, 16]

Le résultat est identique, mais la version par compréhension est plus directe et souvent plus rapide à exécuter car les opérations sont optimisées en C, le langage sous-jacent de Python.

Pour des ensembles de données très volumineux, la création d'une liste complète en mémoire peut être inefficace. C'est là qu'interviennent les expressions génératrices. En remplaçant les crochets [] par des parenthèses (), vous créez un générateur. Ce dernier ne calcule les valeurs qu'au moment où vous en avez besoin, une par une. C'est un principe appelé évaluation paresseuse (lazy evaluation).

# Expression génératrice (notez les parenthèses)
carres_generateur = (n**2 for n in range(100000000))

# Le générateur n'a encore rien calculé.
# Il le fera à la demande, par exemple dans une boucle :
for i, carre in enumerate(carres_generateur):
    if i < 5:
        print(carre)
    else:
        break

Dictionnaires Surpuissants

Les dictionnaires sont incroyablement rapides pour les recherches par clé. Cependant, la gestion des clés absentes peut alourdir le code. Un scénario courant est de regrouper des éléments. Si vous essayez d'ajouter un élément à une liste associée à une clé qui n'existe pas encore, vous obtiendrez une KeyError.

Le module collections de Python offre une solution élégante : defaultdict. Il se comporte comme un dictionnaire normal, sauf qu'il ne lève jamais de KeyError. Si une clé n'est pas trouvée, il la crée automatiquement en utilisant une fonction "usine" que vous fournissez.

from collections import defaultdict

# Exemple : regrouper des mots par leur première lettre
mots = ['pomme', 'poire', 'banane', 'prune', 'ananas']

# Méthode classique
groupes = {}
for mot in mots:
    lettre = mot[0]
    if lettre not in groupes:
        groupes[lettre] = []
    groupes[lettre].append(mot)

# Avec defaultdict
groupes_default = defaultdict(list)
for mot in mots:
    groupes_default[mot[0]].append(mot)

# Le résultat est le même, mais le code est plus propre
# groupes_default['p'] -> ['pomme', 'poire', 'prune']
# groupes_default['b'] -> ['banane']

En spécifiant list comme usine, defaultdict crée une liste vide chaque fois qu'une nouvelle clé est rencontrée. Vous pouvez utiliser d'autres types comme int (qui créera une valeur 0) ou set.

La Puissance des Ensembles et des Tuples

Les ensembles (set) sont une structure de données optimisée pour deux choses : garantir l'unicité des éléments et tester l'appartenance très rapidement. Un set ne peut pas contenir de doublons. De plus, vérifier si un élément est in un set est une opération en temps constant, O(1)O(1) en moyenne, peu importe la taille de l'ensemble. Pour une liste, cette opération est en O(n)O(n), ce qui signifie que le temps de recherche augmente avec la taille de la liste.

# Supprimer les doublons d'une liste
ma_liste = [1, 2, 2, 3, 4, 3, 5, 1]
uniques = list(set(ma_liste))
print(uniques) # Affiche [1, 2, 3, 4, 5]

# Opérations sur les ensembles
voyelles = {'a', 'e', 'i', 'o', 'u'}
lettres = {'a', 'b', 'c', 'd', 'e'}

# Intersection (éléments en commun)
print(voyelles & lettres) # Affiche {'a', 'e'}

# Union (tous les éléments uniques)
print(voyelles | lettres) # Affiche {'a', 'b', 'c', 'd', 'e', 'i', 'o', 'u'}

# Différence (éléments dans voyelles mais pas dans lettres)
print(voyelles - lettres) # Affiche {'o', 'i', 'u'}

Pour des données structurées qui ne doivent pas changer, les tuples sont excellents, mais accéder aux éléments par index (point[0]) peut nuire à la lisibilité. La solution est le namedtuple, également du module collections. Il vous permet de créer des classes de tuples légères où vous pouvez accéder aux éléments par leur nom, comme avec un objet.

from collections import namedtuple

# Définir un type de tuple nommé
Point = namedtuple('Point', ['x', 'y'])

# Créer une instance
p1 = Point(10, 20)

# Accéder aux données par nom ou par index
print(f"Le point est à x={p1.x}, y={p1.y}")
print(f"La somme des coordonnées est {p1[0] + p1[1]}")

Les namedtuples sont aussi légers en mémoire que les tuples normaux, ce qui les rend parfaits pour représenter des enregistrements de données simples et immuables.

Slicing Avancé

Le slicing est une technique puissante pour extraire des sous-séquences. La syntaxe de base est sequence[start:stop]. Mais vous pouvez y ajouter un troisième argument, step, pour plus de contrôle : sequence[start:stop:step].

SlicingEffet
L[::2]Prend un élément sur deux, en partant du début.
L[1::2]Prend un élément sur deux, en partant du deuxième.
L[::-1]Inverse la séquence.
L[5:1:-1]Va de l'index 5 à l'index 2 (exclu), à l'envers.

Cette technique ne se contente pas d'extraire des données, elle peut aussi être utilisée pour modifier des listes. Par exemple, L[::2] = [99, 99, 99] remplacerait un élément sur deux dans la liste L par la valeur 99.

Quiz Questions 1/6

Quelle est la compréhension de liste (list comprehension) équivalente au code suivant ?

carres = []
for i in range(5):
    carres.append(i * i)
Quiz Questions 2/6

Quel est le principal avantage d'une expression génératrice par rapport à une compréhension de liste pour de très grands ensembles de données ?

En maîtrisant ces structures et techniques, vous écrirez un code Python non seulement fonctionnel, mais aussi performant, lisible et idiomatique.