Introducción
Una lista de activos fiable es solo el comienzo de la revisión del equipo. El grupo necesita una explicación por tipo: cuántos activos están en buen estado, cuáles están dañados, qué falta y qué supera el plan. Un objeto no planificado o una cantidad requerida cero no invalidan la entrada.
En este laboratorio implementarás la lógica de conciliación y leerás su informe con dos conjuntos diferentes de archivos de entrada reales. Es un entorno independiente: el analizador de estados y el recopilador de identidades del laboratorio anterior ya están suministrados. No necesitas archivos de su VM. Debes conocer sus reglas de identidad y conflictos, además de estructuras, enumeraciones, HashMap, Result, lectura de archivos y bucles de Rust. El autor estima 45 minutos, incluidas la lectura y las correcciones habituales; no es un tiempo de finalización medido con principiantes.
Conciliar cada tipo y activo
En este paso construirás la conciliación mediante tres ediciones: identificar los tipos relevantes, recopilar sus conjuntos completos de activos y calcular después los faltantes y excedentes. Abre Terminal. Añade Cargo a la ruta de búsqueda de este shell y entra en el proyecto preparado de forma independiente. Repite el export en un terminal nuevo y repite cd si pierdes la ubicación:
export PATH=/home/labex/.cargo/bin:$PATH
cd /home/labex/project/field-kit
Examina primero un resultado normal que ya funciona. En este laboratorio se suministran el analizador de estados y el recopilador de identidades:
cat data/plan.tsv
cargo run --quiet -- assets data/scans.tsv
La tabla contiene seis ID físicos, no siete escaneos. a, b y c son activos buenos de tipo light-a. d es un activo dañado de tipo light-b; e y f son activos bueno y dañado del tipo no planificado other. El nombre visible Flashlight pertenece a dos ID de tipo planificados distintos. El plan también requiere dos cuerdas sin escaneos y cero repuestos sin escaneos. Conserva estas diferencias al decidir qué debe explicar un informe completo.
Edita únicamente src/readiness.rs, conservando el nombre público de la función y los tipos de sus parámetros y retorno. Mantén sin cambios los demás archivos fuente, Cargo.toml, rust-toolchain.toml y todos los originales de data/. Crearás entradas experimentales separadas en el directorio del proyecto. La interfaz de comandos lee las rutas reales que indicas; no las sustituye por una muestra.
El contrato TSV controlado sigue siendo el mismo: cabeceras exactas de tres campos type_id, name, required para planes y asset_id, type_id, condition para escaneos, con tabuladores reales entre campos. Los ID empiezan por una letra ASCII minúscula y continúan con minúsculas, dígitos o -. Los nombres del plan son ASCII imprimible no vacío, incluidos espacios. Las cantidades son dígitos decimales con valor de 0 a 999 y admiten ceros iniciales. Los ID de tipo del plan no pueden repetirse, aunque sí sus nombres visibles. Los estados son exactamente good o damaged. Se aceptan LF/CRLF, la ausencia de salto final y líneas posteriores completamente vacías. No se recortan los campos; una línea solo con espacios en blanco no está vacía y los retornos de carro aislados no terminan líneas. Son registros sintéticos de texto, no inspecciones físicas.
Lee los tipos suministrados y la función incompleta:
cat src/lib.rs
cat src/readiness.rs
Need contiene el ID de tipo del plan, el nombre y la cantidad requerida. Scan contiene un ID de activo, un ID de tipo, el estado y la línea diagnóstica. reconcile recibe &[Need] y &[Scan], slices prestados que no debe cambiar. Los activos ya han pasado la validación de formato y la recopilación por identidad, por lo que cada ID físico aparece una sola vez. El Vec<TypeSummary> devuelto debe incluir la unión de tipos planificados y escaneados, los conjuntos completos de ID buenos y dañados, la cantidad requerida, la faltante y la excedente. El formateador preparado aporta los nombres visibles, ordena filas y listas de ID e imprime la relación planned o unplanned de cada activo.
Primera edición: conservar los tipos relevantes
Considera un plan sin activos escaneados. Una necesidad positiva sigue siendo un faltante, mientras que una necesidad cero tiene faltante cero. Crea una entrada de escaneos vacía separada; no vacíes el original protegido. printf imprime su cadena de formato, interpretando \t como tabulador y \n como salto de línea. Las comillas simples conservan esas barras para printf y > escribe este archivo experimental. Su cabecera es la única fila:
printf 'asset_id\ttype_id\tcondition\n' > empty-scans.tsv
cat empty-scans.tsv
Antes de cambiar el código, predice qué tipos debe conservar esta entrada: light-a, light-b, rope y zero, con necesidades 2, 1, 2 y 0. En esta entrada concreta no existe ningún tipo exclusivo de los escaneos. Aun así, la función general debe descubrir tipos de ambos slices para que los escaneos reales posteriores puedan añadir other.
Crea un punto de recuperación solo del código fuente. mktemp -d crea un directorio nuevo sustituyendo las seis X por un sufijo único; no reutiliza una copia anterior. $() captura la ruta impresa en la variable de sesión. && ejecuta cp -p solo tras crear correctamente el directorio. La copia conserva los metadatos del código y las comillas mantienen unida la ruta de la variable. Si falla cualquiera de las operaciones, lee el error y consigue una copia antes de editar:
rs03_before_types=$(mktemp -d .edit-before-types.XXXXXX) && cp -p src/readiness.rs "$rs03_before_types/readiness.rs"
Abre el archivo existente con nano; -c muestra la posición del cursor:
nano -c src/readiness.rs
Nano usa ^ para indicar Ctrl. Pulsa Ctrl+W, busca pub fn reconcile y pulsa Enter. Lee las llaves de apertura y cierre de esa función. Usa las flechas y Retroceso/Supr para editar solo su cuerpo y los nombres de parámetros sin usar. Cambia _needs y _assets por needs y assets, conservando sus tipos. Mantén la importación suministrada use crate::{Need, Scan, TypeSummary};; esta fase aún no usa Condition.
Construye esta primera fase a partir de las relaciones de datos. Crea un vector types vacío. Añade cada ID de tipo planificado. Recorre después los activos y añade un ID de tipo solo cuando types.contains(&asset.type_id) sea falso. Los ID de tipo del plan ya son únicos. .clone() copia una cadena de ID de tipo con propiedad propia desde un registro prestado para que el resultado pueda poseerla.
Crea después un vector summaries vacío y recorre cada ID de tipo recopilado. Inicializa su cantidad requerida a cero y busca ese tipo en el plan. usize::from(need.required) convierte el valor u16 del plan al tipo de recuento usado por las longitudes de vectores. Por ahora usa vectores good_ids y damaged_ids vacíos. Mientras no se hayan contado activos utilizables, asigna required a missing y establece surplus en cero. Añade un TypeSummary con estas variables y deja summaries como expresión final de la función. La notación abreviada de campos, como type_id, required, utiliza variables de igual nombre. Aún no has implementado la clasificación de activos.
Guarda con Ctrl+O, Enter para confirmar el nombre actual y Ctrl+X para salir. Lee la función guardada antes de ejecutarla:
cat src/readiness.rs
cargo run compila y ejecuta el programa; --quiet oculta el progreso de Cargo y -- pasa los argumentos siguientes al programa. Prueba el caso limitado sin escaneos:
cargo run --quiet -- check data/plan.tsv empty-scans.tsv
Debes ver esos cuatro tipos planificados, cero activos buenos y conjuntos de ID vacíos, cantidades faltantes 2, 1, 2 y 0 y excedentes cero. No debe haber filas de datos de activos tras la cabecera ASSET. Esta primera fase solo es correcta para esta observación sin escaneos. No es una implementación completa para una lista de activos no vacía. No interpretes un estado de salida cero como prueba de que todas las reglas de conciliación están terminadas.
Segunda edición: conservar los conjuntos completos de estados
Predice ahora los conjuntos completos de los escaneos reales: light-a tiene los ID buenos a,b,c; light-b tiene el ID dañado d; other tiene el ID bueno e y el dañado f. rope y zero siguen necesitando sus propios resúmenes aunque no tengan activos. Conserva todos los conjuntos en lugar de elegir un activo «adicional» preferido.
Conserva la fase de unión de tipos que ya funciona:
rs03_before_sets=$(mktemp -d .edit-before-sets.XXXXXX) && cp -p src/readiness.rs "$rs03_before_sets/readiness.rs"
nano -c src/readiness.rs
Al principio, cambia la importación de crate para incluir la enumeración que usarás ahora:
use crate::{Condition, Need, Scan, TypeSummary};
Es una edición explícita del código: el archivo inicial original no importa Condition. crate se refiere a esta biblioteca; use incorpora sus nombres a este módulo. Añadir la importación al empezar a usarla también evita una advertencia de importación sin usar en la fase anterior.
Busca pub fn reconcile. Conserva la lógica de unión de tipos y cantidades requeridas. Haz mutables good_ids y damaged_ids porque les añadirás ID. Dentro del bucle de cada tipo, recorre todos los activos, selecciona los que tienen el type_id actual y compara su estado mediante match. Para la variante Good, añade una copia del ID del activo al vector de buenos; para Damaged, añádela al de dañados. Ambas variantes son entradas válidas. No elimines activos dañados ni no planificados. Deja sin cambios las dos asignaciones temporales de faltantes y excedentes hasta la siguiente edición.
Guarda, sal y ejecuta con los escaneos reales:
cargo run --quiet -- check data/plan.tsv data/scans.tsv
Lee los ID de tipo, las cantidades requeridas, los recuentos de buenos, los conjuntos completos de ID y las filas ASSET. Deben coincidir con tu predicción e incluir other. Las columnas de faltantes y excedentes siguen siendo temporales y no son resultados generales válidos. Por ejemplo, el faltante temporal de light-a aún puede mostrar 2 aunque ya estén presentes sus tres ID buenos. Explica por qué esto no puede ser un informe terminado. Corregirás esos dos cálculos a continuación; no afirmes que esta tabla intermedia pasa la comprobación final.
Tercera edición: calcular faltantes y excedentes solo con activos buenos
Solo los activos únicos buenos cubren demanda. Para light-a, necesidad 2 y buenos 3 significa faltante 0, excedente 1. Para light-b, su activo dañado no cubre la necesidad: faltante 1, excedente 0. A rope le faltan 2. zero sigue en 0/0. El tipo no planificado other requiere cero y tiene un activo bueno, así que su excedente es 1; conserva su ID dañado, pero este no añade excedente.
Guarda la fase que ya recopila los conjuntos correctos:
rs03_before_totals=$(mktemp -d .edit-before-totals.XXXXXX) && cp -p src/readiness.rs "$rs03_before_totals/readiness.rs"
nano -c src/readiness.rs
Busca pub fn reconcile y sustituye solo las expresiones temporales de faltantes y excedentes. len() da el recuento de un vector. saturating_sub resta sin bajar de cero, evitando el desbordamiento por debajo del límite de un entero sin signo. Las reglas son missing = max(required - good, 0) y surplus = max(good - required, 0); aplícalas a good_ids.len(), nunca al número de escaneos originales ni de ID dañados. Conserva intactos la unión de tipos y los vectores completos. Guarda con Ctrl+O, Enter y después Ctrl+X.
cargo run --quiet -- check data/plan.tsv data/scans.tsv
TYPE type_id name required good missing surplus good_ids damaged_ids
TYPE light-a Flashlight 2 3 0 1 a,b,c -
TYPE light-b Flashlight 1 0 1 0 - d
TYPE other - 0 1 0 1 e f
TYPE rope Rope 2 0 2 0 - -
TYPE zero Spare 0 0 0 0 - -
ASSET asset_id type_id condition plan
ASSET a light-a good planned
ASSET b light-a good planned
ASSET c light-a good planned
ASSET d light-b damaged planned
ASSET e other good unplanned
ASSET f other damaged unplanned
Ahora cada cantidad es trazable: light-b conserva el ID dañado d, pero tiene un faltante; other conserva ambos ID, pero solo e genera excedente. La repetición del escaneo de a nunca creó otro activo físico. Repite la comprobación anterior con el origen vacío y la función completa:
cargo run --quiet -- check data/plan.tsv empty-scans.tsv
Sus cuatro tipos planificados y los faltantes sin escaneos deben seguir siendo correctos. Solo ahora se espera que pase la comprobación de conciliación completa de este paso. Los órdenes internos equivalentes de colección siguen siendo válidos porque el formateador preparado ordena las filas y listas de ID mostradas.
Reparar una edición del código sin reiniciar el proyecto
El error de Cargo nombra un archivo y una línea. Vuelve a abrir el archivo, localiza la función con Ctrl+W y repara la edición indicada. Si un cambio accidental aún no se ha guardado, Ctrl+X y después N en la pregunta de guardado lo descartan; eso no deshace una edición guardada antes.
Solo al recuperarte de un error ya guardado, elige el punto de recuperación de la fase que quieras repetir: .edit-before-types.* contiene el código inicial, .edit-before-sets.* contiene la fase de unión de tipos y .edit-before-totals.* contiene los conjuntos completos de ID anteriores a los cálculos finales. En una Terminal nueva, repite PATH y cd y lista los nombres reales de las copias:
ls -d .edit-before-*
-d lista los nombres de directorio en vez de su contenido y * coincide con los sufijos generados. Ejecuta el siguiente comando; mientras el shell espera, pega un nombre de directorio impreso exacto y pulsa Enter. read guarda tu selección en esta variable de sesión:
read rs03_restore
Confirma el código seleccionado antes de copiarlo:
cat "$rs03_restore/readiness.rs"
Si la ruta o la fase es incorrecta, repite la selección y la inspección. Si no hay ninguna copia coincidente, repara la función con su referencia en lugar de adivinar una ruta. Tras confirmar la fase deseada, este comando sustituye deliberadamente solo el código de conciliación permitido:
cp -p "$rs03_restore/readiness.rs" src/readiness.rs
Repite después las ediciones y pruebas afectadas; un punto de recuperación de código no cambia las entradas. Nunca cambies el plan original para adaptarlo a un algoritmo incorrecto. La referencia siguiente sirve para comparar una parte concreta después de intentarlo, no como sustitución predeterminada de todo el archivo.
Referencia completa de conciliación para comparación y recuperación
use crate::{Condition, Need, Scan, TypeSummary};
pub fn reconcile(needs: &[Need], assets: &[Scan]) -> Vec<TypeSummary> {
let mut types = Vec::new();
for need in needs {
types.push(need.type_id.clone());
}
for asset in assets {
if !types.contains(&asset.type_id) {
types.push(asset.type_id.clone());
}
}
let mut summaries = Vec::new();
for type_id in types {
let mut required = 0;
for need in needs {
if need.type_id == type_id {
required = usize::from(need.required);
}
}
let mut good_ids = Vec::new();
let mut damaged_ids = Vec::new();
for asset in assets {
if asset.type_id == type_id {
match asset.condition {
Condition::Good => good_ids.push(asset.asset_id.clone()),
Condition::Damaged => damaged_ids.push(asset.asset_id.clone()),
}
}
}
let missing = required.saturating_sub(good_ids.len());
let surplus = good_ids.len().saturating_sub(required);
summaries.push(TypeSummary {
type_id, required, good_ids, damaged_ids, missing, surplus,
});
}
summaries
}
Explicar la cobertura parcial con otro origen
En este paso utilizarás rutas de entrada diferentes para examinar faltantes parciales y un tipo no planificado representado solo por daño. Continúa en Terminal, dentro de /home/labex/project/field-kit. Conserva todos los archivos originales de data/; crea en su lugar los dos archivos de prueba separados siguientes.
Los breves bloques de entrada siguientes usan un here-document. cat recibe como entrada las líneas siguientes, > escribe el archivo experimental indicado y <<'TSV' inicia la entrada literal hasta una línea que contenga únicamente TSV. El delimitador entre comillas impide que el shell expanda el contenido. Copia ambas líneas delimitadoras y conserva los tabuladores reales. Son pequeñas entradas de prueba, no sustituciones de un módulo Rust. El nombre Water filter contiene un espacio dentro de un solo campo; los tabuladores separan los campos.
cat > trial-plan.tsv <<'TSV'
type_id name required
lamp Lamp 2
filter Water filter 5
zero Spare 0
TSV
cat > trial-scans.tsv <<'TSV'
asset_id type_id condition
l-one lamp good
l-two lamp damaged
f-one filter good
f-two filter good
u-one unplanned damaged
TSV
Antes de ejecutar, predice los faltantes de lámparas y filtros y si seguirá presente el tipo no planificado que solo contiene daño. Especifica ahora estos archivos reales:
cargo run --quiet -- check trial-plan.tsv trial-scans.tsv
TYPE type_id name required good missing surplus good_ids damaged_ids
TYPE filter Water filter 5 2 3 0 f-one,f-two -
TYPE lamp Lamp 2 1 1 0 l-one l-two
TYPE unplanned - 0 0 0 0 - u-one
TYPE zero Spare 0 0 0 0 - -
ASSET asset_id type_id condition plan
ASSET f-one filter good planned
ASSET f-two filter good planned
ASSET l-one lamp good planned
ASSET l-two lamp damaged planned
ASSET u-one unplanned damaged unplanned
Antes de interpretar el informe, predice los faltantes de lámparas y filtros a partir de sus activos buenos, no del número de escaneos. Falta una lámpara, aunque haya dos líneas de escaneo. Dos filtros buenos solo cubren dos de los cinco requeridos, dejando tres faltantes. El activo dañado no planificado sigue explicado, con cero buenos y cero excedentes. zero sigue presente aunque ningún escaneo de prueba lo mencione.
Prueba un cambio controlado sin editar los datos originales ni los primeros escaneos de prueba. cp -p crea una copia conservando los metadatos del archivo copiado. Usa este nombre nuevo separado; si ya lo usaste en un intento anterior, conserva ese trabajo o elige otro nombre sin usar y úsalo de forma coherente en los comandos siguientes.
cp -p trial-scans.tsv trial-scans-one-repair.tsv
nano -c trial-scans-one-repair.tsv
Pulsa Ctrl+W, busca l-two y pulsa Enter. Solo en esa fila, sustituye la palabra de estado damaged por good con las flechas y Retroceso/Supr. Conserva su ID de activo, ID de tipo, cabecera y tabuladores reales. Guarda con Ctrl+O, Enter y sal con Ctrl+X. Es una edición intencional de tu experimento separado, no una reparación de pruebas protegidas.
Predice las dos filas afectadas antes de ejecutar: TYPE lamp debe tener ahora dos ID buenos, ningún ID dañado, cero faltantes y cero excedentes. ASSET l-two debe cambiar su estado a bueno y seguir planificado. Todas las demás filas de tipos y activos deben permanecer iguales; no cambiaron las necesidades de filtros ni el activo dañado no planificado.
cargo run --quiet -- check trial-plan.tsv trial-scans-one-repair.tsv
Lee esas dos filas y confirma que las demás siguen coincidiendo. Si cambió algo más, examina la copia con cat y corrige el campo involuntario; no cambies el algoritmo solo para ajustarlo a una edición accidental de la entrada. Vuelve al primer origen de prueba sin modificar:
cargo run --quiet -- check trial-plan.tsv trial-scans.tsv
El faltante de lámparas debe volver a uno, con l-two dañado. Los informes difieren porque difieren sus entradas reales indicadas; ningún origen se sustituye silenciosamente. Esta misma implementación de conciliación completa debe explicar ambos casos.
Un error de entrada no debe convertirse en un informe de faltantes ni recurrir silenciosamente al ejemplo original. El siguiente nombre incorrecto es intencional y debe devolver un estado distinto de cero:
cargo run --quiet -- check trial-plans.tsv trial-scans.tsv
El error nombra el archivo inexistente trial-plans.tsv. Corrige la errata de plural y vuelve a ejecutar:
cargo run --quiet -- check trial-plan.tsv trial-scans.tsv
Para provocar un error de formato, usa el ejemplo de estado desconocido existente. Este comando también debe devolver un estado distinto de cero e indicar data/bad-status.tsv y su línea:
cargo run --quiet -- check trial-plan.tsv data/bad-status.tsv
broken es una palabra inválida, mientras que damaged es un estado válido que no cubre demanda. Recupérate eligiendo de nuevo los escaneos de prueba válidos:
cargo run --quiet -- check trial-plan.tsv trial-scans.tsv
Por último, vuelve a las entradas originales:
cargo run --quiet -- check data/plan.tsv data/scans.tsv
Reaparece el informe original porque el programa lee las rutas reales, no un informe en caché ni un indicador de preparación codificado de forma fija. Se conservan tanto los archivos originales como los de prueba. Puedes examinarlos con cat; para recuperarte de una edición accidental de un archivo de prueba, repite su bloque completo de creación anterior. Los originales protegidos no son el lugar para reparar un error de código.
Resumen
Implementaste la conciliación completa de tipos a partir de un plan y registros de activos únicos. Solo los activos en buen estado cubrieron necesidades; el cumplimiento parcial siguió dejando faltantes y tanto el equipo dañado como el excedente mantuvieron su explicación. Probaste otro par de rutas de entrada reales, gestionaste errores habituales y conservaste todos los orígenes. En el desafío combinarás recopilación por identidad y conciliación para otro conjunto de equipo de viaje.



