Encuentra todos los documentos que cumplan la consulta

C++Beginner
Practicar Ahora

Introducción

Recordar dos palabras debe limitar la búsqueda a notas que contengan ambas. Pueden estar en líneas distintas, y dos notas con el mismo nombre de archivo deben seguir siendo documentos diferentes.

Este laboratorio independiente proporciona un índice correcto de palabras a documentos. Implementarás la intersección de la consulta, reconstruirás el ejecutable CLI real, distinguirás un resultado vacío normal de una entrada inválida y buscarás después de editar notas de práctica. Parte de los mapas, conjuntos, tokens ASCII y operaciones de terminal y archivos del laboratorio anterior. Los 55 minutos son una estimación del autor.

Conserva solo los documentos comunes a todas las palabras

En este paso, implementarás AND por documento: una nota solo coincide si contiene todas las palabras consultadas, aunque estén en líneas distintas. El laboratorio se inicia de forma independiente. Ya hay un índice correcto; no depende de tu entorno anterior. Usa la pestaña Terminal, no una terminal dentro de WebIDE. Abre una shell UTF-8 sin cambiar tus archivos. cd cambia el directorio de esa shell; repítelo si abres otra conexión.

cd /home/labex/project/note-search

El ejemplo completo recibe un directorio y una consulta. Las comillas simples mantienen las dos palabras como un argumento de shell. El programa analiza ese argumento con la misma regla ASCII que las notas:

./sample notes 'cat blue'

Deben aparecer practice/旅行 notes.txt y reference/archive/notes.txt. El grupo de la nota archivada contiene:

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

Cada línea que contenga cualquiera de las palabras consultadas aparece una sola vez, con su número real empezando por 1. La decisión AND se aplica al documento completo, no a una línea. Una nota con cat solo en la línea 1 y blue solo en la línea 2 también debe coincidir. Las repeticiones o dos términos en una misma línea no duplican esa línea. Los fragmentos conservan los bytes Unicode originales.

cat muestra un archivo. Lee la interfaz y la implementación del índice preparada:

cat search.hpp
cat provided.inc

Index es un mapa de palabras a conjuntos de identificadores relativos completos. El conjunto de documentos de una palabra también se denomina conjunto de referencias a documentos. provided.inc aporta el buildIndex correcto en esta unidad. engine.cpp contiene el findAll incompleto:

cat engine.cpp

Conserva search.hpp, provided.inc, support.cpp, main.cpp, sample y todas las rutas y contenidos de notes/reference/. Edita engine.cpp; se permiten auxiliares y algoritmos equivalentes. Solo los datos de notes/practice/ son modificables, y no se exige su conjunto inicial de archivos. El cargador preparado lee recursivamente archivos .txt normales en UTF-8 con LF. Se admiten espacios y Unicode en las rutas; tabulaciones o saltos de línea en rutas, enlaces simbólicos, bytes NUL y binarios arbitrarios quedan fuera del formato. Los fallos de lectura producen un error sin resultados parciales.

El analizador preparado words() genera secuencias máximas de letras ASCII y convierte A-Z en a-z. Los dígitos, guiones bajos, signos de puntuación, espacios y cada byte no ASCII delimitan tokens. cat no es catalog; CAT-blue_42 da cat y blue; 猫cat狗 da cat; café da caf, no cafe. No se afirma que haya segmentación multilingüe. findAll recibe términos ASCII en minúsculas ya normalizados por ese analizador, posiblemente repetidos. Debe devolver de forma segura un vector vacío cuando la lista de términos esté vacía. Por separado, la CLI rechaza consultas originales sin palabras ASCII con estado 2 y sin resultados en stdout; una palabra desconocida o una consulta válida sin coincidencias termina normalmente con 0 y No matching documents.

bash ejecuta la autocomprobación; query selecciona el comportamiento completo. El código inicial falla intencionadamente, normalmente en FAIL all documents, not only the first. Ahora un estado distinto de cero indica trabajo pendiente:

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

Para una consulta AND, empieza con una copia del conjunto de referencias de la primera palabra. Después recorre uno a uno los conjuntos de los demás términos y elimina un candidato en cuanto falte en uno de ellos. Eso es la intersección de conjuntos. Si se desconoce un término, ningún documento puede contenerlos todos. Nunca unas todas las referencias añadiéndolas entre sí: eso sería OR y aceptaría notas que solo contienen una de las palabras solicitadas.

Comprueba terms.empty() antes de acceder al primer elemento con terms.front(). index.find(word) busca una clave sin insertarla; index.end() significa que no existe. No desreferencies esa entrada ausente. En una entrada encontrada, first->second o posting->second usa -> para acceder a ella mediante el iterador; second es su valor, el conjunto de identificadores. Cópialo en found para que consultar no cambie el índice.

El bucle usa un iterador, una posición dentro del conjunto. found.begin() es la primera posición; found.end() marca la posición posterior al último elemento. *it es el identificador actual. .count(*it) vale 0 si ese identificador falta en otro conjunto de referencias. Borrar invalida la posición antigua, por lo que erase(it) devuelve el siguiente iterador válido. En caso contrario, ++it avanza normalmente. No incrementes dos veces después de borrar.

Reemplaza engine.cpp con esta implementación. Como antes, el here-document escribe un archivo completo; > lo sustituye y el delimitador entre comillas 'CPP' impide la expansión de la shell. CPP debe terminar el comando solo en una línea. Las inclusiones conservan la interfaz y el índice correcto 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

El constructor final copia el rango restante del conjunto a un vector. Los conjuntos tienen identificadores únicos, así que cada documento se devuelve una vez. Comprobar de nuevo el primer término en el bucle no perjudica: intersectar un conjunto consigo mismo no lo cambia. Los términos repetidos tampoco afectan por la misma razón. Una intersección vacía devuelve naturalmente un vector vacío. Se admite otro orden reproducible de grupos, incluso rutas descendentes; los números de línea de cada grupo deben seguir aumentando.

Compila el código actual. g++ compila los tres archivos .cpp; -std=c++17 elige C++17, -Wall -Wextra activa avisos y -o nombra el ejecutable. Los archivos incluidos se compilan automáticamente a través de engine.cpp:

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

Lo normal es que una compilación correcta no imprima nada. Si falla, lee el primer diagnóstico de archivo y línea, corrige engine.cpp o repite su sustitución y recompila. Una compilación fallida puede dejar un binario antiguo; la comprobación prueba las funciones y el findnotes real existente, sin reconstruirlo. Un ejecutable ausente o desactualizado no es una entrega completa.

./findnotes notes 'cat blue'

Compara los grupos completos y la evidencia de líneas con el ejemplo. El presentador preparado usa los identificadores que devuelves para seleccionar líneas reales del original; cambiar notas originales o imprimir un éxito fijo no implementa un buscador.

Distingue resultados vacíos de consultas inválidas

En este paso, probarás palabras repetidas, límites de tokens, errores y reconstrucción desde archivos actuales. Sigue en Terminal, en el directorio del proyecto. La función terminada no debe modificar la entrada const Index& ni duplicar documentos cuando se repite una palabra:

./findnotes notes 'cat cat blue'

Los grupos y líneas son los mismos que con cat blue. Ahora añade un término desconocido:

./findnotes notes 'cat absent'

Espera No matching documents.. No basta con que un documento solo tenga cat. Es una consulta válida sin respuesta, así que termina con 0. Inmediatamente después de un comando, la variable de shell $? guarda su estado de salida; las comillas dobles lo expanden como un argumento. printf lo muestra y \n termina la línea:

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

Imprime Exit status: 0. Prueba la conversión de mayúsculas y los separadores:

./findnotes notes 'CAT-blue_42'

Significa lo mismo que cat blue. Los dígitos y guiones bajos no forman parte de los tokens. Buscar una palabra completa no usa coincidencias de subcadenas:

./findnotes notes catalog

Solo coinciden reference/catalog.txt y la nota de práctica que contiene catalog. No significa cat.

Una cadena vacía se escribe con dos comillas simples contiguas, ''. Es un argumento de shell proporcionado, pero vacío. El siguiente comando es un fallo intencionado: imprime Query needs at least one ASCII word en stderr y ningún resultado en stdout:

./findnotes notes ''

Lee su estado inmediatamente:

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

Imprime 2. Una consulta solo con dígitos, separadores y caracteres no ASCII es inválida por el mismo motivo:

./findnotes notes '42_東京'

También es un fallo intencionado con estado 2. No implica que el almacenamiento Unicode esté roto: la consulta simplemente no tiene una palabra ASCII admitida. Recupérate proporcionando una consulta con palabras, no editando el original ni cambiando el analizador.

Un directorio mal escrito es otro error. El siguiente comando indica deliberadamente un directorio inexistente:

./findnotes missing-notes cat

Imprime Cannot read notes directory y termina con 2 sin resultados parciales. Usa find notes -type f si necesitas consultar las rutas reales y después recupera la operación con la raíz correcta:

./findnotes notes cat

Vuelven los resultados normales. La misma regla de todo o error se aplica si cualquier archivo es ilegible. En cambio, un directorio de notas vacío pero legible es válido y no da coincidencias.

Ahora modifica la copia de práctica preparada y escribible. Las comillas simples conservan un nombre con espacios y Unicode. printf escribe texto, \n crea saltos LF y > sustituye ese archivo. Los originales de reference no se tocan:

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

Solo queda la nota archivada: la nota de práctica ya no contiene blue. Cada invocación relee los archivos actuales y crea un índice nuevo en memoria. No hay caché guardada ni comando de actualización adicional. Los cambios de datos requieren otra ejecución; los de C++ requieren recompilar.

Añade una nota con las palabras consultadas en líneas distintas:

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

Ahora coincide practice/new notes.txt y muestra las líneas 1 y 2, junto con el resultado de la nota archivada. Es evidencia directa de AND por documento entre líneas. rm elimina únicamente el archivo de práctica indicado:

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

Desaparece el grupo adicional. Si una ruta está mal escrita, enumera los archivos y usa la exacta; no reinicies la VM. Si apuntas a un original protegido, déjalo intacto y usa un archivo de práctica. La evaluación sigue admitiendo todos los cambios legítimos de práctica.

Ejecuta la autocomprobación. Lee el proyecto actual y usa datos desechables para otras entradas; nunca repara tu binario, edita las notas reales ni restablece el progreso. Los sondeos de entradas inválidas pueden mostrar errores esperados junto a PASS; el éxito termina con CLI CHECKS PASSED:

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

Si las pruebas de código pasan pero falla el ejecutable real, recompila findnotes desde el código actual y reintenta. Repetir comprobaciones debe dejar intactos el motor, el ejecutable, los archivos preparados, los originales y el contenido de práctica actual.

Resumen

Has implementado AND como intersección de identificadores de documentos, incluso con términos en líneas distintas. Los términos repetidos no afectan, las palabras desconocidas dan resultados vacíos normales y las consultas mal formadas o entradas ilegibles fallan explícitamente. El ejecutable existente lee ahora los datos actuales y muestra cada línea original pertinente una vez. El desafío combina estas consultas con la construcción del índice en otra colección.