CodeGym /Cours /Swift SELF /Normalisation de l'entrée

Normalisation de l'entrée

Swift SELF
Niveau 7, Leçon 4
Disponible

1. Les utilisateurs sont des utilisateurs

Si les utilisateurs saisissaient toujours du texte parfaitement, les programmeurs perdraient la moitié de leur travail, et l'autre moitié partirait en débats sur le formatage. En réalité, les gens saisissent « comme ça vient » : avec des espaces superflus, avec des casses différentes, parfois avec des sauts de ligne (surtout s'ils copient depuis des notes), et parfois même avec des espaces « élégants » qui ressemblent à des espaces ordinaires, mais se comportent autrement. Résultat : un code simple du type if input == "help" cesse soudain de fonctionner, parce que l'utilisateur a saisi " Help ".

La normalisation, c'est transformer un texte « brut » en une « entrée pratique pour le programme ». En général, c'est une chaîne d'étapes : on coupe les espaces aux bords, on met la casse au même niveau, on réduit les espaces répétés, puis seulement après on compare et on analyse.

Petit schéma pour fixer l'idée :

flowchart TD
    A["readLine() (entrée brute)"] --> B["trim : supprimer les espaces et les sauts de ligne aux bords"]
    B --> C["lowercased() : uniformiser la casse"]
    C --> D["réduire les espaces internes"]
    D --> E["comparaison / conditions / analyse ultérieure"]

2. Chaîne de normalisation de base

Foundation et pourquoi un import est parfois nécessaire

En Swift, une partie des fonctionnalités utiles pour les chaînes ne vit pas dans la bibliothèque standard « nue », mais dans la bibliothèque Foundation. C'est normal : Foundation est un vaste ensemble d'outils pratiques, historiquement issu de Cocoa, mais aujourd'hui très largement utilisé en Swift.

Quand vous voyez des méthodes comme trimmingCharacters(in:) et replacingOccurrences(of:with:), ne soyez pas surpris si, parfois, sans import Foundation le compilateur vous dit qu'il ne connaît pas cela. Ces méthodes font partie de l'écosystème Swift, mais relèvent formellement de l'API de niveau Foundation (et proviennent historiquement du monde de NSCharacterSet / CharacterSet). Les noms de ces méthodes, dans leur forme Swift moderne, sont bien établis dans les guides d'API, y compris trimmingCharacters(in:) et replacingOccurrences(of:with:).

Règle pratique simple : si vous utilisez CharacterSet.whitespacesAndNewlines ou trimmingCharacters(in:), ajoutez simplement import Foundation au début du fichier. Et vivez tranquille.

trimmingCharacters(in:) : nettoyer les bords de la chaîne

trimmingCharacters(in:), c'est comme égaliser proprement les bords d'une feuille avec des ciseaux : on coupe tout ce qui est en trop uniquement au début et à la fin, mais on ne touche pas au milieu. C'est une idée très importante. Beaucoup de débutants s'attendent à ce que « trim enlève les espaces partout », puis s'étonnent que "a b" garde ses espaces internes. Et c'est normal : sinon, la méthode serait dangereuse, car elle pourrait modifier le sens du texte (par exemple dans le nom « Van  Helsing » ou dans une chaîne de code).

Le plus souvent, pour une entrée utilisateur, nous avons besoin de l'ensemble de caractères « espaces et sauts de ligne aux bords », c'est-à-dire CharacterSet.whitespacesAndNewlines. En association, cela ressemble à ceci :

import Foundation

let raw = "   Hello, Swift!  \n"
let cleaned = raw.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

print("|\(raw)|")      // |   Hello, Swift!
                      // |
print("|\(cleaned)|")   // |Hello, Swift!|

Remarquez l'astuce avec les barres verticales |...| : elle aide à voir avec les yeux ce qui reste exactement dans la chaîne. C'est une petite technique qui fait gagner du temps en débogage, surtout quand un \n se cache à la fin d'une ligne.

Voici un autre exemple, plus proche d'une « saisie depuis la console » :

import Foundation

let line = readLine() ?? ""
let trimmed = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

print("Vous avez saisi : |\(trimmed)|")

Ici, l'idée importante est la suivante : on fait d'abord ?? "" pour obtenir une chaîne à coup sûr, puis on applique la normalisation. Ainsi, le code reste simple, sans constructions « compliquées » que vous étudierez plus tard.

lowercased() : comparer des commandes sans guerre des casses

Avec la casse, les gens font souvent n'importe quoi — et c'est un compliment. Certains écrivent "Help", d'autres "HELP", d'autres "hElP", et d'autres encore pensent que Caps Lock est un art de vivre. Pour que le programme ne dépende pas de ces choix artistiques, nous ramenons la chaîne à une seule casse (le plus souvent en minuscules) et nous comparons ensuite la version normalisée.

La méthode lowercased() renvoie une nouvelle chaîne. Elle ne « corrige » pas la chaîne en place, parce que String est un type valeur, et en Swift on préfère généralement renvoyer une nouvelle valeur (c'est à la fois plus sûr et plus clair). Dans la documentation sur les propriétés des chaînes et des caractères, on mentionne souvent des opérations de conversion de casse comme lowercased()/uppercased().

Exemple avec une commande :

import Foundation

let raw = readLine() ?? ""
let normalized = raw
    .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
    .lowercased()

if normalized == "help" {
    print("Commandes disponibles : help, exit")
} else {
    print("Commande inconnue : \(normalized)")
}

Oui, une « chaîne » d'appels avec des points apparaît ici. Il faut la lire sereinement : on lit littéralement de gauche à droite — « on prend raw, on fait trim, on fait lowercased ». Tant que la chaîne reste courte, elle se lit très bien. Si elle devient longue, mieux vaut la découper en plusieurs let — non pas parce que « c'est plus correct », mais parce que c'est plus facile pour le cerveau.

Je montre le même exemple, mais en mode « escalier », pour que ce soit totalement transparent :

import Foundation

let raw = readLine() ?? ""
let trimmed = raw.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let lowered = trimmed.lowercased()

print("raw=|\(raw)|")
print("trimmed=|\(trimmed)|")
print("lowered=|\(lowered)|")

Remplacements simples : replacingOccurrences(of:with:)

Après trim et la casse, on a souvent envie de « retoucher » le texte : remplacer les virgules par des espaces, remplacer plusieurs espaces par un seul, supprimer les doubles tirets, etc. Pour ce genre de tâches, il existe la méthode replacingOccurrences(of:with:), qui renvoie une nouvelle chaîne. C'est aussi une partie de l'API de chaînes bien connue, conforme aux conventions modernes de nommage Swift, y compris replacingOccurrences(of:with:).

L'exemple le plus simple consiste à ajouter un espace après une virgule :

import Foundation

let s = "pomme,banane,poire"
let spaced = s.replacingOccurrences(of: ",", with: ", ")

print(spaced) // pomme, banane, poire

Ici, il ne faut pas s'attendre à de la « magie » : la méthode effectue un remplacement littéral de toutes les occurrences. S'il y avait déjà un espace quelque part, vous obtiendrez deux espaces. Et c'est précisément ce qui nous amène au sujet suivant : il faut parfois stabiliser les espaces à l'intérieur de la chaîne.

Réduction des espaces : un espace au lieu de plusieurs

Réduire les espaces est une tâche classique de normalisation : l'utilisateur peut mettre accidentellement plusieurs espaces d'affilée, et pour la logique, vous voulez que les mots soient séparés par un seul espace. Par exemple, vous voulez reconnaître la commande "add book", mais l'utilisateur a saisi "add book". Pour un humain, c'est « la même chose », mais pour le programme, ce sont des chaînes différentes.

Il existe de nombreuses façons de résoudre cela (et certaines, nous choisissons volontairement de ne pas les aborder aujourd'hui pour ne pas prendre d'avance). La méthode la plus claire à ce stade consiste à répéter le remplacement de " " (double espace) par " " (espace simple), jusqu'à ce qu'il n'y ait plus de doubles espaces dans la chaîne.

Voici une version compacte :

import Foundation

var s = "add     book"
while s.contains("  ") {
    s = s.replacingOccurrences(of: "  ", with: " ")
}

print(s) // add book

Pourquoi cela fonctionne-t-il ? Si une chaîne contient cinq espaces d'affilée, après un remplacement elle en aura par exemple trois, puis deux, puis un. On « pousse » simplement la chaîne vers un état stable.

Il y a ici une nuance utile à comprendre intuitivement : cette solution n'est pas la plus rapide sur de très gros textes, parce que chaque passage crée une nouvelle chaîne, et il peut y avoir plusieurs passages. Mais pour une saisie en console par un humain, c'est presque toujours acceptable : les chaînes sont courtes, et la lisibilité de la solution compte plus que les microsecondes.

Voici un exemple un peu plus « réel », où l'on normalise une saisie de commande :

import Foundation

let raw = readLine() ?? ""
var cmd = raw.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
cmd = cmd.lowercased()

while cmd.contains("  ") {
    cmd = cmd.replacingOccurrences(of: "  ", with: " ")
}

print("Normalisé : |\(cmd)|")

Si l'utilisateur saisit " HeLP ", vous obtiendrez "help". S'il saisit "ADD BOOK", vous obtiendrez "add book". Autrement dit, vous avez rendu l'entrée prévisible.

Rappel : ce que fait chaque étape de normalisation

Quand on débute, le cerveau confond volontiers « trim », « lowercase » et « replace ». Pour vous orienter plus vite, gardez une table simple en tête. Elle ne remplace pas la pratique, mais elle aide bien à ne pas confondre le sens des méthodes.

Étape Exemple de code Ce qui change Ce qui ne change pas Objectif fréquent
1
trimmingCharacters(in: .whitespacesAndNewlines)
Les bords de la chaîne (début et fin) Le milieu de la chaîne Supprimer les espaces et sauts de ligne superflus autour de l'entrée
2
lowercased()
La casse des lettres Les espaces, signes et chiffres Comparer des commandes « sans Caps Lock »
3
replacingOccurrences(of:with:)
Toutes les occurrences d'une sous-chaîne Tout le reste De petites « retouches » de texte
4
while s.contains("  ") { ... }
Les espaces répétitifs à l'intérieur Ne touche pas aux espaces simples Stabiliser la séparation des mots

3. Exemple : mini-assistant en console help et exit

Assemblons maintenant tout cela dans un petit scénario cohérent, qui ressemble à une « application » et non à une suite de chaînes sans lien. Nous allons faire une boucle primitive : on demande une commande, on normalise l'entrée, on réagit à "help" et "exit", et tout le reste est considéré comme une commande inconnue.

Cet exemple entraîne à la fois les chaînes, les conditions et les boucles — et montre pourquoi la normalisation se place avant les comparaisons.

import Foundation

var isRunning = true

while isRunning {
    print("Entrez une commande (help/exit) :", terminator: " ")
    let raw = readLine() ?? ""

    var cmd = raw.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
    cmd = cmd.lowercased()

    while cmd.contains("  ") {
        cmd = cmd.replacingOccurrences(of: "  ", with: " ")
    }

    if cmd == "help" {
        print("Commandes : help, exit")
    } else if cmd == "exit" {
        print("Au revoir !")
        isRunning = false
    } else if cmd.isEmpty {
        print("Entrée vide — je ne me vexe pas, mais je n'ai pas compris la commande.")
    } else {
        print("Commande inconnue : \(cmd)")
    }
}

Remarquez un détail important : on vérifie cmd.isEmpty après trim. C'est logique : l'utilisateur a peut-être saisi du « vide » composé d'espaces et d'Entrée. Avant trim, ce n'est pas une chaîne vide, mais après, si. Et c'est exactement ce comportement qu'il nous faut.

4. Erreurs typiques

Erreur n°1 : s'attendre à ce que trimmingCharacters(in:) supprime les espaces à l'intérieur de la chaîne.
C'est l'une des surprises les plus fréquentes : on voit le mot « trimming » et on pense « donc il va tout nettoyer ». En réalité, la méthode est conçue précisément pour les bords. Et c'est très bien : sinon, vous pourriez modifier le sens du texte par accident. Si vous devez gérer les espaces à l'intérieur, faites-le dans une étape séparée, explicitement (par exemple en réduisant les doubles espaces).

Erreur n°2 : oublier import Foundation et tomber sur “Value of type 'String' has no member ...”.
Quand le compilateur se plaint de trimmingCharacters ou de CharacterSet, cela signifie presque toujours que vous avez oublié l'import. N'essayez pas de « réparer » cela avec des méthodes étranges : ajoutez simplement import Foundation au début du fichier, puis continuez. Et oui, c'est normal : une partie des outils de chaînes se trouve formellement dans Foundation.

Erreur n°3 : croire que lowercased() modifie la chaîne « en place ».
lowercased() renvoie une nouvelle chaîne. Si vous écrivez raw.lowercased() sans enregistrer le résultat quelque part, rien ne change. C'est un moment classique de type « j'ai appelé la méthode, pourquoi cela n'a-t-il pas marché ? ». En Swift, beaucoup de transformations renvoient une nouvelle valeur, donc soit vous la stockez dans un let, soit vous utilisez var et une affectation.

Erreur n°4 : faire un seul remplacement " " → " " et penser que cela suffit.
Un seul remplacement ne supprimera qu'une partie du problème. S'il y avait beaucoup d'espaces d'affilée, il restera des doubles espaces après un seul passage, et la chaîne ne sera toujours pas normalisée. C'est pourquoi on utilise une boucle while, qui répète le remplacement jusqu'à obtenir un résultat stable. C'est un peu plus long, mais bien plus fiable sur le fond.

Erreur n°5 : normaliser l'entrée après les vérifications.
Parfois, le code ressemble à ceci : d'abord if raw == "help", puis plus bas trim et lowercase. Au final, les vérifications ne fonctionnent toujours pas sur une entrée « bancale ». La règle est simple : on normalise d'abord, puis on compare. La normalisation est un filtre d'entrée, pas une retouche cosmétique à la fin du programme.

1
Mission
Swift SELF, niveau 7, leçon 4
Bloqué
Traces de saisie
Traces de saisie
1
Mission
Swift SELF, niveau 7, leçon 4
Bloqué
Interrupteur de mode
Interrupteur de mode
1
Mission
Swift SELF, niveau 7, leçon 4
Bloqué
Liste de courses
Liste de courses
1
Mission
Swift SELF, niveau 7, leçon 4
Bloqué
Mini-console de commandes
Mini-console de commandes
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION