Construa um índice de identidades de documentos

C++Beginner
Pratique Agora

Introdução

Você lembra uma palavra de uma nota, mas não lembra seu diretório. Uma ferramenta de busca útil deve encontrar todas as notas correspondentes, distinguir nomes de arquivo iguais e mostrar linhas reais que possam ser abertas na origem.

Construa essa relação com maps e sets de C++. O carregamento, a análise de palavras, o tratamento de consultas e a exibição de linhas estão preparados; você implementa o índice e o experimenta com notas de prática que mudam. É preciso conhecer navegação básica no terminal, strings, vetores, funções, fluxos de arquivos e exceções de C++. Maps e sets são ensinados aqui. A duração de 50 minutos é uma estimativa do autor.

Conecte uma palavra a todas as notas

Nesta etapa, use um exemplo funcional e depois construa o índice de palavras para documentos do seu próprio executável. Use a aba Terminal durante todo o curso. Ela abre um shell UTF-8; use essa aba nomeada em vez de um terminal dentro do WebIDE. O diretório do shell persiste nessa conexão aberta. Se abrir outra conexão, repita o cd mostrado.

cd muda o diretório de trabalho. Comece no projeto preparado:

cd /home/labex/project/note-search

notes/reference contém originais protegidos; notes/practice contém uma cópia gravável. find lista caminhos recursivamente; -type f limita a listagem a arquivos regulares. A ordem da listagem não importa.

find notes -type f

Existem tanto notes/reference/archive/notes.txt quanto notes/reference/field/notes.txt. O último componente do nome, ou nome base, é igual, mas os caminhos completos identificam documentos distintos. ./ executa um programa no diretório atual. O sample completo recebe um diretório de notas e uma consulta:

./sample notes cat

Ele retorna três grupos de documentos, incluindo estes dois:

File: reference/archive/notes.txt
  1: CAT, cat!
  3: cat blue together

File: reference/field/notes.txt
  1: cat; red

O terceiro grupo é practice/旅行 notes.txt. Cada identidade é relativa à raiz notes fornecida, por isso omite notes/. Cada documento aparece uma vez, mesmo quando a palavra ocorre várias vezes. Os números são números reais de linha da origem, começando em 1. Só aparecem linhas relevantes; um número ausente não significa que a linha original foi excluída. cat imprime um arquivo de texto para comparação:

cat notes/reference/archive/notes.txt

A linha 2, Blue ledger — 東京, existe, mas não contém cat. Os caminhos UTF-8 e o texto original das linhas são preservados.

Nossa pequena regra de palavras é precisa: um token é uma sequência máxima de letras ASCII A-Z ou a-z, com maiúsculas ASCII convertidas em minúsculas. Dígitos, sublinhados, pontuação, espaços e cada byte não ASCII separam tokens. CAT-blue_42 vira cat e blue; cat difere de catalog; 猫cat狗 contém cat; café produz caf, não cafe. Preservar texto Unicode é diferente de entender palavras de todos os idiomas. Não implementamos segmentação multilíngue.

Leia a interface preparada e seu código inicial:

cat search.hpp
cat engine.cpp

Document guarda id, o caminho relativo completo, e lines, um vetor das strings das linhas originais. O carregador preparado lê arquivos regulares .txt recursivamente e ordena suas identidades. As notas são texto UTF-8 com finais de linha LF. Espaços e Unicode são permitidos nos caminhos; tabulações e quebras de linha em caminhos, links simbólicos, dados NUL e arquivos binários arbitrários estão fora desse formato. Entradas ilegíveis geram erro, não resultados parciais.

Index é um alias de std::map<std::string, std::set<std::string>>. Um map associa cada chave de palavra distinta a um valor: aqui, um conjunto de IDs de documentos. Um set mantém cada ID uma vez. Portanto, três ocorrências de cat em um documento produzem uma relação. Essa busca inversa, das palavras para os documentos, é um índice invertido.

Seu código inicial retorna um índice vazio. provided.inc contém a função de consulta já funcional desta unidade. #include insere um arquivo durante a compilação. Preserve search.hpp, provided.inc, support.cpp, main.cpp, sample e todos os caminhos e conteúdos de notes/reference/. Edite engine.cpp; funções auxiliares e algoritmos equivalentes são permitidos. Só é permitido editar, adicionar ou excluir dados em notes/practice/. Sua composição original de arquivos não é um requisito da avaliação.

bash executa o script de autoverificação preparado. index seleciona o comportamento ensinado aqui. Esta primeira execução falha intencionalmente com FAIL exact whole-word postings retain complete document identities e um status de saída diferente de zero. Isso significa que o índice está incompleto, não que o ambiente está com defeito:

bash /opt/labex-note-search/check.sh index

Percorra cada documento, linha e palavra analisada. Um for baseado em intervalo visita cada elemento; const auto& fornece uma referência somente de leitura em vez de copiá-lo. index[word] acessa o conjunto de IDs de documentos daquela palavra, criando um conjunto vazio se a chave for nova. .insert(doc.id) adiciona a identidade completa e ignora relações duplicadas. Mantenha o map fora de todos os laços para acumular todos os documentos.

O próximo comando substitui apenas engine.cpp. Um here-document envia várias linhas a cat; > as grava no arquivo indicado, substituindo seu conteúdo. O delimitador entre aspas 'CPP' impede a expansão do texto C++ pelo shell. Termine com CPP sozinho em uma linha. Mantenha os dois includes para preservar as declarações e a implementação de consulta preparada.

cat > engine.cpp <<'CPP'
#include "search.hpp"
#include "provided.inc"
Index buildIndex(const std::vector<Document>& documents) {
    Index index;
    for (const auto& doc : documents) {
        for (const auto& line : doc.lines) {
            for (const auto& word : words(line)) {
                index[word].insert(doc.id);
            }
        }
    }
    return index;
}
CPP

words(line) é o analisador ASCII preparado. Não extraia só o nome base, não pare na primeira correspondência e não armazene uma contagem de ocorrências no lugar do conjunto de documentos.

Compile seu resultado. g++ é o compilador; -std=c++17 seleciona a versão da linguagem, -Wall -Wextra habilita avisos úteis e -o findnotes dá nome ao executável de saída. As três entradas .cpp são seu mecanismo de busca, o carregador/analisador preparado e a interface de comandos. Os arquivos incluídos são lidos automaticamente.

g++ -std=c++17 -Wall -Wextra engine.cpp support.cpp main.cpp -o findnotes

Uma compilação bem-sucedida normalmente não imprime nada. Em caso de erro, leia o primeiro nome de arquivo e número de linha, corrija engine.cpp ou repita sua substituição completa e compile novamente. Uma compilação malsucedida pode deixar um executável antigo no lugar. Editar somente o código não o atualiza. A autoverificação testa suas funções, exatamente como fornecidas, em dados isolados e também seu findnotes existente; ela não recompila esse executável por você.

./findnotes notes cat

Espere as três identidades de documentos e as mesmas linhas originais do exemplo. Outra ordem reproduzível de documentos é permitida; as linhas de cada documento devem ter números crescentes e aparecer uma vez. Esta etapa avalia o índice e o comportamento de uma única palavra. A combinação de palavras de consulta diferentes está preparada aqui e será sua tarefa no próximo laboratório.

Reconstrua a partir das notas de prática atuais

Nesta etapa, edite, adicione e remova dados de prática preservando as notas originais. Continue em Terminal, em /home/labex/project/note-search. Cada chamada carrega os arquivos atuais e constrói um novo índice em memória. Não há arquivo de índice salvo, daemon de atualização ou comando extra de reconstrução.

Primeiro, compare os limites de palavras inteiras:

./findnotes notes catalog

Espere reference/catalog.txt e practice/旅行 notes.txt, com linhas contendo catalog. Não são correspondências adicionais de cat. Uma palavra desconhecida é uma busca normal:

./findnotes notes absent

Ela imprime No matching documents. e termina com sucesso. Isso difere de uma entrada malformada, rejeitada pela interface preparada. O próximo laboratório examina essa distinção.

A cópia de prática já é gravável; os originais de referência são somente de leitura. Aspas simples mantêm um caminho com espaços como um único argumento e preservam caracteres Unicode. printf escreve texto; \n cria uma quebra de linha. > substitui todo o arquivo de prática indicado. Remova cat dessa cópia substituindo seu conteúdo:

printf 'owl green\n東京 — preserved\n' > 'notes/practice/旅行 notes.txt'
./findnotes notes cat

Agora só as duas notas de referência correspondem. O arquivo de prática ainda existe; seu conteúdo atual já não corresponde. Adicione uma nova nota de prática permitida:

printf 'CAT beside the window\nA new observation\n' > 'notes/practice/new notes.txt'
./findnotes notes cat

Três documentos correspondem novamente, incluindo practice/new notes.txt na linha 1. Você não recompilou: mudar dados exige apenas executar o programa de novo. Mudar código C++ exige compilação.

rm remove um arquivo. Seu único operando entre aspas aqui é a nova nota de prática, não um original nem um diretório inteiro:

rm 'notes/practice/new notes.txt'
./findnotes notes cat

Essa identidade desaparece; os dois resultados de referência permanecem. Nunca exclua originais nem altere o analisador para forçar um resultado. Se errar um caminho, leia No such file or directory, liste os caminhos com find notes -type f e use o caminho exato. Se apontar para um original somente de leitura, deixe-o intacto e use um arquivo de prática. Erros normais de caminho não exigem reiniciar o ambiente.

Execute a autoverificação novamente. Ela lê seus arquivos atuais e cria dados descartáveis para entradas adicionais. Edições, adições e exclusões nesses dados nunca modificam seus dados de prática. Mensagens de erro esperadas de testes isolados de entradas inválidas podem aparecer ao lado de linhas PASS; uma execução bem-sucedida termina com CLI CHECKS PASSED.

bash /opt/labex-note-search/check.sh index

O botão de verificação também deve passar. Repetir uma verificação não reescreve seu código, substitui o executável nem reinicia as notas de prática. Se falhar após uma edição do código, recompile o findnotes real e tente novamente. Testes temporários de funções, sozinhos, não comprovam que o executável está atualizado.

Resumo

Você construiu um índice invertido cujos valores são conjuntos de identidades completas de documentos. Palavras repetidas não duplicam documentos, nomes base iguais continuam separados e números reais de linha levam ao texto original. Executar novamente reconstrói o índice de forma transparente após edições, adições e exclusões permitidas nos dados de prática. A seguir, implemente a interseção da consulta que exige várias palavras no mesmo documento.