Introduction
Une équipe de terrain a besoin de deux lampes torches qui fonctionnent. Deux lignes de scan peuvent représenter deux lampes différentes, une seule lampe scannée deux fois, ou une lampe en bon état et une autre endommagée. Votre programme doit préserver ces distinctions avant de déterminer si l’équipement est complet.
Dans ce laboratoire, vous implémenterez en Rust un analyseur d’états et un collecteur d’éléments. Vous conserverez les objets physiques distincts, ignorerez les scans répétés identiques et rejetterez les enregistrements contradictoires sans modifier les fichiers sources. Le laboratoire suivant utilisera ces identités pour expliquer le manque ou le surplus de chaque type d’équipement. Il s’agit d’enregistrements synthétiques, pas d’inspections de matériel physique.
Vous devez déjà connaître les structures et énumérations Rust, HashMap, Result, la lecture de fichiers texte et les boucles for élémentaires. L’analyseur, l’interface de commandes et le formateur préparés vous permettent de vous concentrer sur l’identité. L’auteur estime la durée à 45 minutes, lecture et corrections ordinaires comprises ; cette durée n’a pas été mesurée auprès de débutants.
Lire les états sans les deviner
Dans cette étape, examinez deux fichiers de scans réels, prédisez leurs objets utilisables et implémentez la courte décision d’état qui fait fonctionner l’affichage normal des lignes du programme. Ouvrez Terminal. Le projet préparé se trouve dans /home/labex/project/field-kit, avec Rust 1.99.1.
Le export suivant ajoute le répertoire de Cargo au chemin de recherche des commandes de ce shell. $PATH conserve les répertoires existants. Cela dure uniquement pendant la session Terminal actuelle ; répétez-le dans un nouveau shell. Aucun fichier de démarrage du shell n’est modifié.
export PATH=/home/labex/.cargo/bin:$PATH
Utilisez cd pour sélectionner le projet ; répétez-le si vous perdez votre répertoire. pwd affiche le répertoire courant pour confirmer où les prochaines modifications de fichiers auront lieu.
cd /home/labex/project/field-kit
pwd
Vous devez obtenir /home/labex/project/field-kit. Corrigez une faute de chemin avec cd, sans réinitialiser la VM. cat affiche un fichier sans le modifier. Lisez ces deux entrées réelles :
cat data/two-good.tsv
cat data/one-damaged.tsv
TSV désigne des valeurs séparées par des tabulations. Chaque scan comporte trois champs : asset_id, type_id, condition, séparés par de vraies tabulations. Dans le premier fichier, les objets physiques a et b ont tous deux l’état good. Dans le second, a est en bon état et b est damaged. Vérifiez le résultat à la main : les deux fichiers contiennent deux objets physiques, mais seul le premier décrit deux lampes utilisables. Les mots good et damaged sont les deux états exacts autorisés ; un équipement endommagé est une donnée valide, pas une ligne d’entrée incorrecte.
Le projet fournit src/lib.rs avec les structures et lecteurs TSV, src/main.rs avec l’interface de commandes et le formateur, et src/readiness.rs pour le laboratoire suivant. Gardez ces fichiers, Cargo.toml, rust-toolchain.toml et chaque fichier original de data/ inchangés. Ici, vous pouvez modifier uniquement src/condition.rs et, à l’étape suivante, src/identity.rs, en conservant leurs noms et types publics.
Lisez le module d’états :
cat src/condition.rs
L’énumération et la méthode d’affichage sont déjà fournies. impl regroupe les méthodes de Condition ; Self désigne cette énumération dans le bloc. pub rend ces noms accessibles au lecteur. derive fournit la copie, l’affichage de débogage et l’égalité. parse renvoie Result<Self, String> : soit Ok avec un état, soit Err avec une explication d’erreur possédée. La fonction provisoire rejette actuellement tous les mots ; elle ne distingue donc pas encore les deux fichiers normaux.
Créez un point de restauration limité au code source avant de modifier. mktemp -d crée un nouveau répertoire en remplaçant les six caractères X par un suffixe unique ; il ne réutilise pas une ancienne sauvegarde. $() stocke le chemin affiché dans une variable de session. && exécute cp -p uniquement si la création du répertoire réussit ; la copie conserve les métadonnées du source. Les guillemets gardent le chemin de la variable groupé. Si l’une des opérations échoue, lisez l’erreur et obtenez une sauvegarde réussie avant de modifier :
rs03_before_condition=$(mktemp -d .edit-before-condition.XXXXXX) && cp -p src/condition.rs "$rs03_before_condition/condition.rs"
Ouvrez le module existant avec nano ; -c affiche la position du curseur :
nano -c src/condition.rs
La notation ^ de nano signifie Ctrl. Appuyez sur Ctrl+W, saisissez pub fn parse, puis appuyez sur Entrée pour trouver la fonction. Lisez ses accolades ouvrante et fermante. Utilisez les flèches et Retour arrière/Suppr pour modifier uniquement le corps de cette méthode et la liaison de son paramètre inutilisé. Renommez _value en value, en conservant le type du paramètre. Gardez l’énumération, derive et la méthode name.
Écrivez une expression match value avec une branche exacte pour chaque mot autorisé et un cas générique pour toute autre chaîne. Une branche comme "good" => Ok(Self::Good), montre la syntaxe. Construisez vous-même le résultat Damaged correspondant. _ correspond à toute valeur restante, qui doit renvoyer Err plutôt qu’inventer silencieusement un état. format! crée un String ; {value:?} entoure le texte inattendu de guillemets pour rendre les espaces et l’orthographe visibles dans les diagnostics. Utilisez-le dans un message expliquant que l’entrée doit être good ou damaged. Fermez les accolades du match et de la fonction, en gardant le match comme résultat de la méthode.
Appuyez sur Ctrl+O, puis Entrée pour confirmer src/condition.rs ; Ctrl+X quitte nano. Ne saisissez pas de commandes shell tant que vous êtes encore dans l’éditeur. Lisez la modification enregistrée :
cat src/condition.rs
cargo run compile et démarre le programme préparé. --quiet masque la progression de Cargo et -- transmet les arguments suivants au programme. rows valide et affiche chaque ligne du fichier réel ; il ne déduplique pas encore.
cargo run --quiet -- rows data/two-good.tsv
asset_id type_id condition
a light-a good
b light-a good
L’affichage normal des lignes doit maintenant montrer les deux ID et leurs états good. Comparez avec l’autre source :
cargo run --quiet -- rows data/one-damaged.tsv
asset_id type_id condition
a light-a good
b light-a damaged
Cela correspond à votre prédiction manuelle : toujours deux objets scannés, mais un seul en bon état. Vous avez écrit une véritable décision dans le chemin du lecteur réel, pas un programme de démonstration séparé.
Le contrat d’entrée contrôlé
Lisez aussi le plan réel :
cat data/plan.tsv
Ses champs sont type_id, name, required. light-a et light-b affichent tous deux Flashlight, mais sont des ID de type distincts. Un nom est du texte d’affichage, pas une identité. Un besoin positif sans scans implique un manque ; un besoin nul sans scans implique un manque nul. Le lecteur fourni applique ces règles :
- Les ID commencent par une lettre ASCII minuscule, suivie uniquement de minuscules, de chiffres ou de
-. Ils sont sensibles à la casse. - Les états sont exactement
goodetdamaged; aucune autre orthographe n’est acceptée. - Les noms sont des textes ASCII imprimables non vides, espaces compris. Les quantités sont des chiffres décimaux de valeur 0–999, avec des zéros initiaux autorisés. Les ID de type du plan ne peuvent pas se répéter, même avec un contenu identique.
- L’en-tête exact de trois champs doit être en premier. LF/CRLF, l’absence de saut final et les lignes suivantes entièrement vides sont autorisés. Les lignes ne contenant que des espaces blancs ne sont pas vides. Les retours chariot isolés ne sont pas des fins de ligne. Les espaces aux extrémités des champs ne sont pas supprimés : les espaces dans un ID ou un état sont des erreurs, tandis que ceux d’un nom sont conservés.
ASCII désigne ici les lettres anglaises de base, les chiffres, les espaces et la ponctuation ; les noms excluent les caractères de contrôle. C’est un petit format défini, pas un lecteur CSV généraliste. Votre méthode d’état fournit la décision à deux états dans ce lecteur préparé.
Récupérer une modification du source
Pour une erreur Rust, lisez le fichier et la ligne indiqués par Cargo, rouvrez ce fichier, recherchez la fonction concernée et réparez la modification. Enregistrez, quittez et réessayez la commande en échec. Pour abandonner une modification accidentelle encore non enregistrée, utilisez Ctrl+X, puis N à la question d’enregistrement. Cela n’annule pas les modifications déjà enregistrées.
Uniquement pour récupérer une erreur enregistrée, restaurez votre propre point de sauvegarde condition plutôt que de relancer la préparation. Dans le même Terminal, sa variable identifie encore la sauvegarde. Dans un nouveau Terminal, répétez PATH et cd, puis énumérez les répertoires réels de cette phase :
ls -d .edit-before-condition.*
-d énumère les noms des répertoires plutôt que leur contenu ; * correspond aux suffixes générés. Exécutez la commande suivante, collez un nom de répertoire exact parmi ceux affichés pendant que le shell attend, puis appuyez sur Entrée. read stocke cette ligne dans la variable pour cette session :
read rs03_before_condition
Examinez le fichier sélectionné. Si le chemin ou la phase est incorrect, répétez la sélection et l’inspection ; si aucune sauvegarde n’existe, réparez avec la référence plutôt que de deviner un suffixe.
cat "$rs03_before_condition/condition.rs"
Après avoir confirmé le source d’avant modification voulu, cette commande remplace délibérément uniquement le fichier condition autorisé :
cp -p "$rs03_before_condition/condition.rs" src/condition.rs
Elle restaure l’analyseur inachevé : implémentez donc à nouveau la décision d’état et répétez les deux commandes d’affichage des lignes. La référence ci-dessous sert à comparer une méthode précise après votre tentative, pas à remplacer tout le module comme opération d’édition normale.
Référence complète des états pour comparaison et récupération
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Condition { Good, Damaged }
impl Condition {
pub fn parse(value: &str) -> Result<Self, String> {
match value {
"good" => Ok(Self::Good),
"damaged" => Ok(Self::Damaged),
_ => Err(format!("condition must be good or damaged, got {value:?}")),
}
}
pub fn name(self) -> &'static str {
match self { Self::Good => "good", Self::Damaged => "damaged" }
}
}
Recenser les objets physiques par ID
Dans cette étape, implémentez la collecte par identité en conservant les objets physiques distincts et en ne comptant qu’une fois les répétitions identiques. Continuez dans Terminal, dans /home/labex/project/field-kit. Lisez la source plus grande :
cat data/scans.tsv
Avant de coder, prédisez les objets uniques. L’objet a apparaît deux fois avec le même type et le même état ; a, b et c sont trois lampes distinctes de type light-a. L’objet d appartient à light-b, un autre type ayant le même nom d’affichage dans le plan. e et f appartiennent au type non planifié other ; les équipements supplémentaires valides ne doivent pas disparaître. Il doit y avoir six ID physiques, pas sept ni un par type ou par nom.
Lisez le squelette du collecteur existant :
cat src/identity.rs
Scan contient asset_id, type_id, condition et la line diagnostique. Le use crate::Scan fourni importe cette structure depuis la bibliothèque actuelle. La fonction emprunte &[Scan] : elle ne doit donc ni modifier les enregistrements de l’appelant ni en prendre possession. Son Result renvoie un vecteur d’enregistrements uniques ou une explication utile du conflit.
Une HashMap peut associer un enregistrement à chaque ID d’objet. Insérer une clé existante remplace son ancienne valeur : recherchez-la donc avant d’insérer. Décidez séparément quoi faire pour un nouvel ID, un ID/type/état répété identique et une répétition contradictoire. Ne comparez pas le Scan entier avec l’égalité dérivée : un contenu identique apparaît naturellement sur des numéros de ligne différents. Comparez le type et l’état ; conservez les deux numéros de ligne uniquement pour le diagnostic.
Préservez le source de cette phase avant de modifier :
rs03_before_identity=$(mktemp -d .edit-before-identity.XXXXXX) && cp -p src/identity.rs "$rs03_before_identity/identity.rs"
nano -c src/identity.rs
Ce code de départ n’importe pas HashMap. Gardez use crate::Scan; et ajoutez l’import de la bibliothèque standard à côté, en haut :
use std::collections::HashMap;
std est la bibliothèque standard de Rust ; crate est la bibliothèque de ce projet. Importer un nom le rend disponible dans ce module, pas dans tous les fichiers. Une implémentation équivalente avec une table ordonnée ou un tri est également autorisée, avec les imports dont elle a réellement besoin.
Recherchez pub fn collect_assets avec Ctrl+W, puis appuyez sur Entrée. Renommez _scans en scans, en conservant son type de tranche. Implémentez les décisions de la fonction ; gardez les autres définitions et le module d’états terminé inchangés.
Une approche avec une table commence par une HashMap<String, Scan> modifiable. Pour chaque scan emprunté, get(&scan.asset_id) renvoie Some avec un enregistrement antérieur ou None. if let Some(previous) n’entre dans le bloc que si un enregistrement existe. Si le type ou l’état diffère, renvoyez une Err nommant l’ID complet, la ligne actuelle et la première ligne. format! peut combiner ces champs dans un message possédé. Si le contenu correspond, continue évite d’insérer cette répétition.
Pour un nouvel ID, insérez un ID cloné et un enregistrement cloné ; .clone() crée des copies possédées depuis l’entrée empruntée. Après avoir examiné tous les scans, déplacez les enregistrements stockés dans un nouveau Vec et renvoyez Ok avec ce vecteur. Vec::new() crée un vecteur vide et push ajoute un élément. Dans une boucle comme for (_, scan) in by_id, la paire est décomposée et _ ignore la clé. L’ordre d’itération de HashMap n’est pas défini ; le formateur fourni trie les ID pour l’affichage, donc votre ordre interne n’a pas besoin d’être fixe.
Enregistrez avec Ctrl+O, Entrée, puis Ctrl+X. Examinez la fonction enregistrée avant de l’exécuter :
cat src/identity.rs
Comparez d’abord les lignes brutes réelles :
cargo run --quiet -- rows data/scans.tsv
Le a répété y figure toujours. Exercez maintenant votre collecteur terminé :
cargo run --quiet -- assets data/scans.tsv
asset_id type_id condition
a light-a good
b light-a good
c light-a good
d light-b damaged
e other good
f other damaged
Confirmez les six ID, types et états. Seule la répétition identique a disparu de l’affichage collecté ; le fichier d’entrée n’a pas été modifié. Les ID distincts du même type, les dommages et le matériel non planifié sont tous conservés. L’étape suivante vérifie les contradictions, qui doivent entraîner le rejet de tout le résultat collecté plutôt que la sélection silencieuse du premier ou du dernier enregistrement.
Pour une erreur de compilation, réparez la ligne indiquée dans nano, y compris un import manquant si c’est le problème. Uniquement pour annuler une erreur enregistrée, la récupération conditionnelle suivante utilise votre propre fichier d’avant le collecteur. Dans un nouveau Terminal, répétez PATH et cd, puis énumérez les noms réels des sauvegardes de cette phase :
ls -d .edit-before-identity.*
Exécutez read, collez un nom de répertoire exact parmi ceux affichés et appuyez sur Entrée. Confirmez son contenu avant de restaurer. Si aucune sauvegarde ne correspond, utilisez la référence pour une réparation précise plutôt que de deviner un chemin.
read rs03_before_identity
cat "$rs03_before_identity/identity.rs"
Après avoir confirmé le source voulu, restaurez uniquement le fichier identity autorisé :
cp -p "$rs03_before_identity/identity.rs" src/identity.rs
Cela restaure la fonction provisoire du collecteur d’origine, y compris ses imports d’origine. Ajoutez à nouveau l’import nécessaire à votre implémentation et complétez la fonction avant de tester à nouveau. Cela ne supprime pas votre analyseur d’états réussi et ne change pas les entrées brutes.
Référence complète des identités pour comparaison et récupération
Comparez la décision concernée après avoir tenté votre propre implémentation, et faites une réparation locale dans nano. C’est l’unique source complète des identités utilisée pour préparer les unités indépendantes suivantes.
use crate::Scan;
use std::collections::HashMap;
pub fn collect_assets(scans: &[Scan]) -> Result<Vec<Scan>, String> {
let mut by_id: HashMap<String, Scan> = HashMap::new();
for scan in scans {
if let Some(previous) = by_id.get(&scan.asset_id) {
if previous.type_id != scan.type_id || previous.condition != scan.condition {
return Err(format!(
"line {}: conflicting asset {} (first at line {})",
scan.line, scan.asset_id, previous.line
));
}
continue;
}
by_id.insert(scan.asset_id.clone(), scan.clone());
}
let mut unique = Vec::new();
for (_, scan) in by_id {
unique.push(scan);
}
Ok(unique)
}
Rejeter les conflits et reprendre après les erreurs d’entrée
Dans cette étape, vous distinguerez des preuves contradictoires, un état inconnu et un mauvais chemin. Continuez dans Terminal, dans le répertoire du projet. Les échecs suivants sont intentionnels : ils doivent afficher une erreur et renvoyer un état de sortie non nul. Ne supprimez ni ne réparez les exemples sources conservés pour les faire disparaître.
Les exemples d’erreurs ci-dessous montrent la formulation de l’implémentation de référence. Votre propre message peut employer d’autres mots : un conflit doit identifier le chemin réel, l’ID complet de l’objet et les deux lignes sources ; un état inconnu doit identifier son chemin et sa ligne réels et expliquer la valeur rejetée. Reproduire exactement la formulation n’est pas l’objectif.
Lisez d’abord les preuves contradictoires :
cat data/conflict.tsv
Elles décrivent l’élément a à la fois comme good et damaged. Exécutez le collecteur :
cargo run --quiet -- assets data/conflict.tsv
data/conflict.tsv: line 3: conflicting asset a (first at line 2)
L’interface de commandes fournie ajoute le chemin d’entrée réel à l’erreur du collecteur. Aucune table de réussite n’est imprimée. Une implémentation qui garde le dernier enregistrement masquerait les preuves et pourrait changer le résultat en inversant simplement le fichier. Les répétitions identiques sont autorisées, mais les contradictions nécessitent une investigation hors de cet exercice.
Reprenez en sélectionnant de nouveau la source valide :
cargo run --quiet -- assets data/scans.tsv
Lisez maintenant volontairement l’exemple contenant un état inconnu :
cargo run --quiet -- rows data/bad-status.tsv
data/bad-status.tsv: line 2: condition must be good or damaged, got "broken"
C’est une décision de format, pas un élément endommagé : damaged est une donnée valide, tandis que broken ne fait pas partie du vocabulaire contrôlé du cours. Lisez un enregistrement endommagé valide pour reprendre :
cargo run --quiet -- rows data/one-damaged.tsv
Enfin, utilisez volontairement un nom de fichier au singulier qui n’existe pas :
cargo run --quiet -- assets data/scan.tsv
L’erreur mentionne data/scan.tsv et indique que le fichier est introuvable. Cela diffère d’une erreur de ligne dans un fichier existant. Corrigez le chemin sans modifier la source réelle :
cargo run --quiet -- assets data/scans.tsv
La table des six éléments doit réapparaître. Si une modification Rust est incorrecte, rouvrez le source nommé dans nano et réparez la fonction concernée. Pour annuler une erreur enregistrée, utilisez le point de sauvegarde de la phase condition ou identity et la procédure explicite de confirmation ls/read/cat enseignée à l’étape précédente ; restaurez uniquement ce source autorisé, puis refaites son implémentation et relancez cette dernière commande. Les modifications non enregistrées dans nano peuvent plutôt être abandonnées avec Ctrl+X et N. Aucun de ces cas ne nécessite de relancer la préparation ni de modifier une entrée protégée. Votre programme distingue désormais les erreurs de source des enregistrements répétés, endommagés et supplémentaires valides. Le laboratoire suivant utilisera ces identités uniques pour calculer les manques.
Résumé
Vous avez implémenté un analyseur d’énumération à deux états et recensé les éléments d’équipement par ID physique. Les identités distinctes sont conservées, les répétitions identiques n’augmentent pas le résultat et les contradictions produisent des erreurs contextualisées au lieu de remplacer discrètement les preuves. Vous avez aussi repris après un état inconnu et un nom de fichier incorrect, sans altérer les originaux. Ces enregistrements fiables permettent ensuite d’expliquer les manques et surplus de chaque type.



