Разделяем идентичность экземпляра и тип оборудования

RustBeginner
Практиковаться сейчас

Введение

Полевой команде нужны два исправных фонаря. Две строки сканирования могут описывать два разных фонаря, один фонарь, считанный дважды, либо один исправный и один повреждённый. Программа должна сохранить эти различия, прежде чем определять, собран ли комплект.

В этой лабораторной работе вы реализуете на Rust разбор состояний и сбор экземпляров. Вы сохраните разные физические предметы, проигнорируете точные повторы сканирования и отклоните противоречивые записи, не меняя исходные файлы. Следующая работа использует эту идентичность для объяснения недостачи или излишка каждого типа оборудования. Записи синтетические: осмотр реального оборудования не проводится.

Предполагается знание структур и перечислений Rust, HashMap, Result, чтения текстовых файлов и простых циклов for. Подготовленные парсер, командный интерфейс и форматирование вывода позволяют сосредоточиться на идентификации экземпляров. Автор оценивает продолжительность в 45 минут с чтением и обычными исправлениями; на начинающих это время не измерялось.

Читаем состояния без догадок

На этом шаге изучите два настоящих файла сканирований, предскажите их пригодные предметы и реализуйте короткое решение о состоянии, которое обеспечит работу обычного просмотра строк программы. Откройте Terminal. Подготовленный проект — /home/labex/project/field-kit, с Rust 1.99.1.

Следующий export добавляет каталог Cargo в путь поиска команд этой оболочки. $PATH сохраняет существующие каталоги. Это действует только в текущем сеансе Terminal; повторите в новой оболочке. Файлы запуска оболочки не меняются.

export PATH=/home/labex/.cargo/bin:$PATH

Через cd выберите проект; повторите, если потеряли каталог. pwd печатает текущий каталог, позволяя проверить, где будут происходить последующие правки файлов.

cd /home/labex/project/field-kit
pwd

Ожидайте /home/labex/project/field-kit. Опечатку пути исправляйте через cd, не сброс VM. cat показывает файл без изменений. Прочитайте два настоящих входных файла:

cat data/two-good.tsv
cat data/one-damaged.tsv

TSV означает значения, разделённые табуляцией. Каждое сканирование содержит три поля: asset_id, type_id, condition, разделённые буквальными табуляциями. В первом файле физические экземпляры a и b оба имеют состояние good. Во втором a исправен, а b — damaged. Проверьте вручную: в обоих файлах два физических предмета, но только первый описывает два пригодных фонаря. Слова good и damaged — два точных допустимых состояния; повреждённое оборудование является допустимыми данными, не неверной входной строкой.

Проект предоставляет src/lib.rs со структурами и читателями TSV, src/main.rs с командным интерфейсом и форматированием и src/readiness.rs для следующей работы. Сохраните эти файлы, Cargo.toml, rust-toolchain.toml и каждый исходный файл data/ без изменений. Здесь можно менять только src/condition.rs, а на следующем шаге — src/identity.rs, сохраняя публичные имена и типы.

Прочитайте модуль состояния:

cat src/condition.rs

Перечисление и метод отображения уже предоставлены. impl группирует методы Condition; Self обозначает это перечисление внутри блока. pub открывает имена читателю. derive предоставляет копирование, отладочное отображение и равенство. parse возвращает Result<Self, String>: либо Ok с состоянием, либо Err с принадлежащей значению строкой объяснения ошибки. Текущая заготовка отклоняет каждое слово, поэтому пока не различает два обычных файла.

Перед редактированием создайте точку отмены только для исходника. mktemp -d создаёт новый каталог, заменяя шесть символов X уникальным суффиксом; старая копия не используется повторно. $() сохраняет напечатанный путь в переменной сеанса. && выполняет cp -p только после успешного создания каталога; копирование сохраняет метаданные исходника. Кавычки сохраняют путь переменной целиком. Если любая операция не удалась, прочитайте ошибку и успешно создайте копию до редактирования:

rs03_before_condition=$(mktemp -d .edit-before-condition.XXXXXX) && cp -p src/condition.rs "$rs03_before_condition/condition.rs"

Откройте существующий модуль в nano; -c показывает позицию курсора:

nano -c src/condition.rs

Обозначение ^ в nano означает Ctrl. Нажмите Ctrl+W, введите pub fn parse и нажмите Enter, чтобы найти функцию. Прочитайте её открывающую и закрывающую скобки. Стрелками и Backspace/Delete меняйте только тело этого метода и его неиспользуемое имя параметра. Переименуйте _value в value, сохранив тип параметра. Сохраните перечисление, derive и метод name.

Напишите выражение match value с точной веткой для каждого разрешённого слова и общей веткой для всех остальных строк. Ветка вроде "good" => Ok(Self::Good), показывает синтаксис. Самостоятельно создайте соответствующий результат Damaged. _ соответствует любому оставшемуся значению, которое должно вернуть Err, а не молча выдумать состояние. format! создаёт String; {value:?} включает кавычки вокруг неожиданного текста, делая пробелы и написание видимыми в диагностике. Используйте это в сообщении, объясняющем, что ввод должен быть good или damaged. Закройте скобки match и функции, оставляя match результатом метода.

Нажмите Ctrl+O, затем Enter для подтверждения src/condition.rs; Ctrl+X завершает nano. Не вводите команды оболочки, оставаясь внутри редактора. Прочитайте сохранённую правку:

cat src/condition.rs

cargo run компилирует и запускает подготовленную программу. --quiet скрывает ход работы Cargo, а -- передаёт следующие аргументы программе. rows проверяет и показывает каждую строку настоящего файла; повторы пока не удаляются.

cargo run --quiet -- rows data/two-good.tsv
asset_id	type_id	condition
a	light-a	good
b	light-a	good

Обычный просмотр строк теперь должен показать оба ID с исправными состояниями. Сравните с другим источником:

cargo run --quiet -- rows data/one-damaged.tsv
asset_id	type_id	condition
a	light-a	good
b	light-a	damaged

Это соответствует вашему ручному предсказанию: по-прежнему два отсканированных предмета, только один исправен. Вы написали настоящее решение в пути фактического читателя, не отдельную демонстрационную программу.

Контролируемый контракт ввода

Прочитайте и настоящий план:

cat data/plan.tsv

Его поля — type_id, name, required. И light-a, и light-b отображаются как Flashlight, но имеют разные ID типа. Название — отображаемый текст, не идентичность. Положительная потребность без сканирований означает недостачу; нулевая потребность без сканирований имеет нулевую недостачу. Предоставленный читатель применяет правила:

  • ID начинаются со строчной буквы ASCII, далее только строчные буквы, цифры или -. Регистр значим.
  • Состояния — строго good и damaged; другое написание не принимается.
  • Названия — непустые печатные строки ASCII, включая пробелы. Количества — десятичные цифры со значением 0–999, ведущие нули разрешены. ID типов плана не могут повторяться даже с одинаковым содержимым.
  • Точный заголовок из трёх полей должен быть первым. LF/CRLF, отсутствие последнего перевода строки и совершенно пустые последующие строки допустимы. Строки только из пробельных символов не пусты. Одиночный возврат каретки не является окончанием строки. Поля не обрезаются: пробелы в ID или состоянии — ошибки, а пробелы в названии сохраняются.

ASCII здесь означает основные английские буквы, цифры, пробелы и пунктуацию; названия исключают управляющие символы. Это небольшой определённый формат, не универсальный читатель CSV. Ваш метод состояния обеспечивает выбор из двух состояний внутри предоставленного читателя.

Восстановление после правки исходника

При ошибке Rust прочитайте файл и строку Cargo, откройте этот файл, найдите затронутую функцию и исправьте правку. Сохраните, выйдите и повторите неудавшуюся команду. Чтобы отбросить ещё не сохранённую случайную правку, нажмите Ctrl+X, затем N при вопросе о сохранении. Это не отменяет предыдущих сохранённых изменений.

Только при восстановлении после сохранённой ошибки восстановите собственную точку сохранения condition вместо сброса setup. В том же Terminal переменная ещё указывает копию. В новом Terminal повторите PATH и cd, затем перечислите настоящие каталоги этого этапа:

ls -d .edit-before-condition.*

-d перечисляет имена каталогов вместо содержимого; * соответствует созданным суффиксам. Выполните следующую команду, вставьте одно точное напечатанное имя каталога, пока оболочка ждёт, и нажмите Enter. read сохраняет строку в переменной этого сеанса:

read rs03_before_condition

Изучите выбранный файл. Если путь или этап неверен, повторите выбор и проверку; если копии нет, исправьте по эталону, не угадывая суффикс.

cat "$rs03_before_condition/condition.rs"

После подтверждения нужного исходника до правки эта команда намеренно заменяет только разрешённый файл состояния:

cp -p "$rs03_before_condition/condition.rs" src/condition.rs

Она восстанавливает незавершённый парсер, поэтому снова реализуйте решение о состоянии и повторите обе команды строк. Эталон ниже служит для сравнения отдельного метода после вашей попытки, не для замены всего модуля при обычном редактировании.

Полный эталон состояния для сравнения и восстановления
#[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" }
    }
}

Собираем физические экземпляры по ID

На этом шаге реализуйте сбор по идентичности, сохраняя разные физические экземпляры и считая одинаковые повторы один раз. Продолжайте в Terminal, в /home/labex/project/field-kit. Прочитайте более крупный источник:

cat data/scans.tsv

Перед кодированием предскажите уникальные предметы. Экземпляр a встречается дважды с одинаковыми типом и состоянием; a, b и c — три разных фонаря типа light-a. Экземпляр d принадлежит light-b, другому типу с тем же отображаемым названием плана. e и f принадлежат незапланированному типу other; допустимое дополнительное оборудование не должно исчезать. Должно быть шесть физических ID, не семь и не по одному на тип или название.

Прочитайте существующий каркас сборщика:

cat src/identity.rs

Scan содержит asset_id, type_id, condition и диагностическую line. Предоставленное use crate::Scan импортирует структуру из этой библиотеки. Функция заимствует &[Scan], поэтому не должна менять записи вызывающей стороны или забирать владение ими. Её Result возвращает вектор уникальных записей или полезное объяснение конфликта.

HashMap может связать одну запись с каждым ID экземпляра. Вставка существующего ключа заменяет предыдущее значение, поэтому сначала найдите его. Отдельно решите, что делать с новым ID, одинаковым повтором ID/типа/состояния и противоречивым повтором. Не сравнивайте весь Scan через выведенное равенство: одинаковое содержимое естественно встречается на разных номерах строк. Сравнивайте тип и состояние; сохраняйте оба номера строк только для диагностики.

Сохраните исходник этого этапа до редактирования:

rs03_before_identity=$(mktemp -d .edit-before-identity.XXXXXX) && cp -p src/identity.rs "$rs03_before_identity/identity.rs"
nano -c src/identity.rs

Начальная версия не импортирует HashMap. Сохраните use crate::Scan; и добавьте импорт стандартной библиотеки рядом с ним вверху:

use std::collections::HashMap;

std — стандартная библиотека Rust; crate — библиотека этого проекта. Импорт делает имя доступным в этом модуле, не в каждом файле. Эквивалентная реализация с упорядоченной картой или сортировкой тоже разрешена, с фактически нужными ей импортами.

Найдите через Ctrl+W pub fn collect_assets и нажмите Enter. Переименуйте _scans в scans, сохранив тип среза. Реализуйте решения функции; сохраните остальные определения и завершённый модуль состояния без изменений.

Один подход с картой начинается с изменяемой HashMap<String, Scan>. Для каждого заимствованного сканирования get(&scan.asset_id) возвращает Some с прежней записью либо None. if let Some(previous) входит только при наличии записи. Если расходятся тип или состояние, верните Err с полным ID экземпляра, текущей и первой строками. format! может объединить поля в принадлежащее значению сообщение. Если содержимое совпадает, continue пропускает вставку повтора.

Для нового ID вставьте клонированные ID и запись; .clone() создаёт принадлежащие значению копии из заимствованного ввода. После изучения всех сканирований переместите сохранённые записи в новый Vec и верните Ok с вектором. Vec::new() создаёт пустой вектор, push добавляет предмет. В цикле вроде for (_, scan) in by_id пара разбирается, _ игнорирует ключ. Порядок обхода HashMap не определён; предоставленное форматирование сортирует ID для отображения, поэтому внутренний порядок может быть свободным.

Сохраните через Ctrl+O, Enter, затем Ctrl+X. Изучите сохранённую функцию до запуска:

cat src/identity.rs

Сначала сравните настоящие исходные строки:

cargo run --quiet -- rows data/scans.tsv

Повтор a там всё ещё присутствует. Теперь используйте завершённый сборщик:

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

Проверьте все шесть ID, типов и состояний. Только одинаковый повтор исчез из собранного представления; входной файл не менялся. Разные ID одного типа, повреждения и незапланированное оборудование сохранились. Следующий шаг проверяет противоречия: они должны отклонять весь собранный результат вместо молчаливого выбора первой или последней записи.

При ошибке компилятора исправьте указанную строку в nano, включая недостающий импорт, если причина в нём. Только при отмене сохранённой ошибки следующая условная процедура использует собственный файл до сборщика. В новом Terminal повторите PATH и cd, затем перечислите настоящие имена копий этапа:

ls -d .edit-before-identity.*

Выполните read, вставьте одно точное напечатанное имя каталога и нажмите Enter. Подтвердите содержимое перед восстановлением. Если подходящей копии нет, используйте эталон для конкретного исправления, не угадывая путь.

read rs03_before_identity
cat "$rs03_before_identity/identity.rs"

После подтверждения нужного исходника восстановите только разрешённый файл идентичности:

cp -p "$rs03_before_identity/identity.rs" src/identity.rs

Это восстанавливает исходную заготовку сборщика, включая исходные импорты. Снова добавьте импорт, нужный вашей реализации, и завершите функцию до повторного теста. Успешный парсер состояния не удаляется, исходный ввод не меняется.

Полный эталон идентичности для сравнения и восстановления

Сравните соответствующее решение после собственной попытки реализации и внесите локальное исправление в nano. Это единственный полный исходник идентичности, используемый для подготовки последующих независимых модулей.

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)
}

Отклоняем конфликты и восстанавливаемся после ошибок ввода

На этом шаге вы различите противоречивые свидетельства, неизвестное состояние и неверный путь. Продолжайте в Terminal, в каталоге проекта. Следующие ошибки намеренны: команды должны вывести сообщение и вернуть ненулевой статус завершения. Не удаляйте и не исправляйте сохранённые примеры, чтобы ошибки исчезли.

Примеры ошибок ниже показывают формулировки эталонной реализации. Ваше сообщение может отличаться: конфликт должен указывать фактический путь, полный ID экземпляра и обе исходные строки; неизвестное состояние — фактический путь и строку и объяснять отклонённое значение. Точное совпадение текста не является целью.

Сначала прочитайте противоречивые свидетельства:

cat data/conflict.tsv

В них экземпляр a описан одновременно как good и damaged. Запустите сборщик:

cargo run --quiet -- assets data/conflict.tsv
data/conflict.tsv: line 3: conflicting asset a (first at line 2)

Предоставленный командный интерфейс дополняет ошибку сборщика фактическим путём входного файла. Таблица успешного результата не выводится. Реализация, выбирающая последнюю запись, скроет свидетельства, а результат может измениться при простом развороте порядка строк. Точные повторы разрешены, но противоречия требуют расследования за рамками упражнения.

Для восстановления снова выберите допустимый источник:

cargo run --quiet -- assets data/scans.tsv

Теперь намеренно прочитайте пример с неизвестным состоянием:

cargo run --quiet -- rows data/bad-status.tsv
data/bad-status.tsv: line 2: condition must be good or damaged, got "broken"

Это решение о формате, а не повреждённый экземпляр: damaged — допустимые данные, а broken не входит в контролируемый словарь курса. Прочитайте допустимую запись о повреждении, чтобы продолжить:

cargo run --quiet -- rows data/one-damaged.tsv

Наконец, намеренно укажите несуществующее имя файла в единственном числе:

cargo run --quiet -- assets data/scan.tsv

Ошибка называет data/scan.tsv и сообщает, что файл не найден. Это отличается от ошибки строки внутри существующего файла. Исправьте путь, не меняя фактический источник:

cargo run --quiet -- assets data/scans.tsv

Таблица шести экземпляров должна появиться снова. Если правка Rust неверна, откройте указанный исходник в nano и исправьте затронутую функцию. Для отмены сохранённой ошибки используйте точку этапа condition или identity и явную процедуру подтверждения ls/read/cat из предыдущего шага; восстановите только этот разрешённый исходник, затем повторите его реализацию и последнюю команду. Несохранённые изменения nano можно вместо этого отбросить через Ctrl+X и N. Ни один случай не требует повторного запуска setup или правки защищённого ввода. Теперь программа отличает ошибки источника от допустимых повторных, повреждённых и дополнительных записей. Следующая работа использует уникальные экземпляры для вычисления недостачи.

Резюме

Вы реализовали разбор перечисления с двумя состояниями и сбор оборудования по ID физических экземпляров. Разные экземпляры сохранились, точные повторы не увеличили количество, а противоречия привели к ошибкам с указанием источника вместо незаметной замены свидетельств. Вы также восстановили работу после неизвестного состояния и неверного имени файла, сохранив оригиналы. Эти надёжные записи станут основой объяснения недостачи и излишка каждого типа.