Encontre todos os documentos que correspondem à consulta

C++Beginner
Pratique Agora

Introdução

Lembrar duas palavras deve restringir sua busca às notas que contêm ambas. As palavras podem estar em linhas diferentes, e duas notas com o mesmo nome de arquivo ainda precisam ser distintas.

Este laboratório independente fornece um índice correto de palavras para documentos. Você implementa a interseção de consulta, recompila o executável CLI real, distingue um resultado vazio normal de uma entrada inválida e busca após editar notas de prática. Ele usa os maps, sets, tokens ASCII e operações de terminal/arquivos do laboratório anterior. A duração de 55 minutos é uma estimativa do autor.

Mantenha apenas documentos comuns a todas as palavras

Nesta etapa, implemente AND no nível do documento: uma nota só corresponde se contiver todas as palavras da consulta, mesmo em linhas diferentes. Este laboratório começa de forma independente. Um índice correto já é fornecido; ele não depende do ambiente anterior. Use a aba Terminal, não um terminal dentro do WebIDE. Ela inicia um shell UTF-8 sem mudar seus arquivos. cd muda o diretório desse shell; repita-o se abrir outra conexão.

cd /home/labex/project/note-search

O exemplo completo recebe um diretório e uma consulta. Aspas simples mantêm as duas palavras como um argumento do shell. O programa analisa esse argumento com a mesma regra ASCII das notas:

./sample notes 'cat blue'

Espere practice/旅行 notes.txt e reference/archive/notes.txt. O grupo do arquivo de notas arquivadas contém:

File: reference/archive/notes.txt
  1: CAT, cat!
  2: Blue ledger — 東京
  3: cat blue together

Cada linha que contém pelo menos uma palavra da consulta aparece uma vez, com seu número real de linha começando em 1. A decisão AND se aplica ao documento inteiro, não a uma linha. Uma nota com cat apenas na linha 1 e blue apenas na linha 2 também deve corresponder. Ocorrências repetidas ou dois termos na mesma linha não repetem a linha. Os bytes Unicode originais permanecem nos trechos.

cat imprime um arquivo. Leia a interface e a implementação de índice preparada:

cat search.hpp
cat provided.inc

Index é um map de uma palavra para um conjunto de identidades relativas completas de documentos. O conjunto de documentos de uma palavra também é chamado de conjunto de postagens. provided.inc fornece o buildIndex correto nesta unidade. engine.cpp contém o findAll incompleto:

cat engine.cpp

Preserve search.hpp, provided.inc, support.cpp, main.cpp, sample e todos os caminhos e conteúdos em notes/reference/. Edite engine.cpp; funções auxiliares e algoritmos equivalentes são permitidos. Só os dados de notes/practice/ são mutáveis, e sua composição original de arquivos não é exigida. O carregador preparado lê recursivamente arquivos regulares .txt em UTF-8 com LF. Caminhos com espaços e Unicode são aceitos; tabulações e quebras de linha em caminhos, links simbólicos, bytes NUL e arquivos binários arbitrários estão fora do formato de entrada. Falhas de leitura geram erro sem resultados parciais.

O analisador preparado words() produz sequências máximas de letras ASCII, convertendo A-Z em a-z. Dígitos, sublinhado, pontuação, espaços e cada byte não ASCII delimitam tokens. cat não é catalog; CAT-blue_42 dá cat e blue; 猫cat狗 dá cat; café dá caf, não cafe. Isso não promete segmentação de palavras multilíngue. findAll recebe desse analisador termos ASCII minúsculos já normalizados, possivelmente repetidos. Deve retornar com segurança um vetor vazio para uma lista de termos vazia. Separadamente, a CLI rejeita uma consulta bruta sem palavras ASCII com status de saída 2 e sem resultado em stdout; uma palavra desconhecida ou uma consulta válida sem correspondências produz saída normal 0 e No matching documents.

bash executa a autoverificação; query seleciona o comportamento completo da consulta. O código inicial falha intencionalmente, normalmente em FAIL all documents, not only the first. Um status diferente de zero agora significa que ainda há trabalho:

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

Para uma consulta AND, comece com uma cópia do conjunto de postagens da primeira palavra. Depois examine os conjuntos dos demais termos um por vez: remova uma identidade candidata assim que ela estiver ausente de qualquer um deles. Isso é interseção de conjuntos. Se um termo for desconhecido, nenhum documento poderá conter todos os termos. Nunca combine todas as postagens simplesmente adicionando-as: isso seria OR, aceitando notas com apenas uma das palavras solicitadas.

Verifique terms.empty() antes de terms.front() acessar o primeiro elemento. index.find(word) procura uma chave do map sem inseri-la; index.end() significa que a chave não existe. Não desreferencie essa entrada ausente. Em uma entrada encontrada, first->second ou posting->second usa -> para acessar a entrada pelo iterador; second é seu valor, o conjunto de IDs. Copie esse conjunto para found, para a consulta não modificar o índice.

O laço usa um iterador, uma posição dentro do conjunto. found.begin() é sua primeira posição; found.end() marca a posição após a última. *it é o ID atual. .count(*it) é 0 quando esse ID não está no outro conjunto de postagens. A exclusão invalida a posição antiga, então erase(it) retorna o próximo iterador válido. Caso contrário, ++it avança normalmente. Não incremente duas vezes após excluir.

Substitua engine.cpp pela implementação abaixo. Como no laboratório anterior, o here-document escreve um arquivo completo; > o substitui e o delimitador entre aspas 'CPP' impede a expansão pelo shell. CPP deve encerrar o comando sozinho em uma linha. Os includes mantêm a interface e o índice correto preparado.

cat > engine.cpp <<'CPP'
#include "search.hpp"
#include "provided.inc"
std::vector<std::string> findAll(const Index& index,
                                 const std::vector<std::string>& terms) {
    if (terms.empty()) return {};
    auto first = index.find(terms.front());
    if (first == index.end()) return {};
    std::set<std::string> found = first->second;
    for (const auto& term : terms) {
        auto posting = index.find(term);
        if (posting == index.end()) return {};
        for (auto it = found.begin(); it != found.end();) {
            if (posting->second.count(*it) == 0) it = found.erase(it);
            else ++it;
        }
    }
    return std::vector<std::string>(found.begin(), found.end());
}
CPP

O construtor final copia o intervalo restante do conjunto para um vetor. Conjuntos contêm IDs únicos, então cada documento retorna uma vez. Verificar o primeiro termo novamente no laço é inofensivo: intersectar um conjunto consigo mesmo não o modifica. Termos repetidos são inofensivos pela mesma razão. Uma interseção vazia naturalmente retorna um vetor vazio. Outra ordem reproduzível de grupos, incluindo caminhos em ordem decrescente, é permitida; os números de linha em cada grupo ainda devem crescer.

Compile seu código atual. g++ compila os três arquivos .cpp; -std=c++17 seleciona C++17, -Wall -Wextra habilita avisos e -o dá nome ao executável. Os arquivos incluídos são compilados automaticamente por meio de engine.cpp:

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

O sucesso normalmente não imprime nada. Em caso de falha, leia o primeiro diagnóstico de arquivo/linha, corrija engine.cpp ou repita sua substituição e recompile. Uma compilação malsucedida pode deixar um binário antigo; a verificação testa tanto as funções do código quanto seu findnotes real existente, sem recompilá-lo. Um executável ausente ou desatualizado não é uma entrega concluída.

./findnotes notes 'cat blue'

Compare os grupos completos e as linhas que comprovam o resultado com o exemplo. O renderizador preparado usa os IDs retornados para selecionar linhas reais da origem; alterar notas originais ou imprimir um texto fixo de sucesso não implementa uma busca.

Diferencie resultados vazios de consultas inválidas

Nesta etapa, teste palavras repetidas, limites de tokens, erros e reconstrução a partir dos arquivos atuais. Continue em Terminal no diretório do projeto. A função concluída não deve modificar sua entrada const Index& nem duplicar documentos quando uma palavra se repete:

./findnotes notes 'cat cat blue'

Os grupos e linhas são os mesmos de cat blue. Agora adicione um termo desconhecido:

./findnotes notes 'cat absent'

Espere No matching documents. Um documento contendo só cat é insuficiente. Esta é uma consulta válida sem resposta, então termina com 0. Imediatamente após um comando, a variável do shell $? guarda seu status de saída; aspas duplas a expandem como um argumento. printf a exibe, e \n termina a linha de saída:

printf 'Exit status: %s\n' "$?"

Ela imprime Exit status: 0. Teste a conversão de maiúsculas e os separadores:

./findnotes notes 'CAT-blue_42'

Isso significa o mesmo que cat blue. Nem dígitos nem sublinhados fazem parte dos tokens. Consultar uma palavra inteira não usa correspondência de substrings:

./findnotes notes catalog

Só reference/catalog.txt e a nota de prática contendo catalog correspondem. Isso não significa cat.

Uma string vazia é escrita como duas aspas simples adjacentes, ''. É um argumento do shell fornecido, mas vazio. O comando abaixo é uma falha intencional, imprimindo Query needs at least one ASCII word em stderr e nenhum resultado em stdout:

./findnotes notes ''

Leia seu status imediatamente:

printf 'Exit status: %s\n' "$?"

Ela imprime 2. Uma consulta só com dígitos, separadores e caracteres não ASCII é inválida pelo mesmo motivo:

./findnotes notes '42_東京'

Esta também é uma falha intencional com status 2. Não implica que o armazenamento Unicode esteja com defeito: a consulta simplesmente não contém uma palavra ASCII aceita. Recupere fornecendo uma consulta com palavra, sem editar o texto original nem mudar o tokenizador.

Um diretório digitado incorretamente é outro erro. O comando seguinte indica intencionalmente um diretório inexistente:

./findnotes missing-notes cat

Ele imprime Cannot read notes directory e termina com 2 sem resultados parciais. Use find notes -type f se precisar inspecionar os caminhos reais e recupere com a raiz correta:

./findnotes notes cat

Os resultados normais voltam. A mesma regra de leitura completa ou erro vale se qualquer arquivo de origem estiver ilegível. Já um diretório de notas vazio, mas legível, é válido e não tem correspondências.

Agora altere a cópia de prática gravável preparada. Aspas simples preservam o nome com espaços e Unicode. printf escreve texto, \n cria quebras LF e > substitui esse arquivo de prática. Os originais em reference permanecem intactos:

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

Só o documento arquivado permanece: a nota de prática já não contém blue. Cada chamada relê os arquivos atuais e constrói um novo índice em memória. Não há cache salvo nem comando extra de atualização. Mudar dados exige executar novamente; mudar C++ exige recompilar.

Adicione uma nota com as palavras da consulta em linhas diferentes:

printf 'BLUE here\ncat elsewhere 東京\n' > 'notes/practice/new notes.txt'
./findnotes notes 'cat blue'

practice/new notes.txt agora corresponde e mostra as linhas 1 e 2 junto ao resultado arquivado. Isso comprova diretamente o AND no nível do documento entre linhas. rm remove apenas o arquivo de prática indicado:

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

O grupo extra desaparece. Se digitar um caminho errado, liste os arquivos e use o caminho exato; não reinicie a VM. Se apontar para um original protegido, deixe-o intacto e use um arquivo de prática. Todas as alterações legítimas de prática continuam permitidas na avaliação.

Execute a autoverificação. Ela lê seu projeto atual e usa dados descartáveis para entradas adicionais; nunca conserta seu binário, edita notas reais nem reinicia seu progresso. Testes de entradas inválidas podem imprimir erros esperados ao lado de linhas PASS; a conclusão bem-sucedida termina com CLI CHECKS PASSED:

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

Se os testes do código passarem, mas o executável real falhar, recompile findnotes a partir do código atual e tente novamente. Verificações repetidas devem deixar seu mecanismo de busca, executável, arquivos preparados, originais e conteúdo atual de prática intactos.

Resumo

Você implementou AND como interseção de identidades de documentos, incluindo termos em linhas distintas. Termos repetidos são inofensivos, palavras desconhecidas geram resultados vazios normais e consultas malformadas ou entradas ilegíveis falham explicitamente. Seu executável existente agora lê os dados atuais de prática e mostra cada linha original relevante uma vez. O desafio combina essa consulta com a construção do índice em outra coleção de cadernos.