Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #define UNUSED(x) (void)(x)
- //------------------------------------------------------------------------------
- void AssertAlmostEqualImpl(double lhs, const std::string &lhs_expr_str,
- double rhs, const std::string &rhs_expr_str,
- double precision, const std::string &file,
- const std::string &func, unsigned line,
- const std::string &hint) {
- bool almost_equal = abs(lhs - rhs) < precision;
- if (!almost_equal) {
- cout << file << "("s << line << "): "s << func << ": "s;
- cout << lhs_expr_str << " = "s << lhs
- << " is not equal to "s
- << rhs_expr_str << " = "s << rhs
- << " with precision "s << precision;
- if (!hint.empty()) {
- cout << " Hint: "s << hint;
- }
- cout << endl;
- abort();
- }
- }
- #define ASSERT_ALMOST_EQUAL(a, b, precision) \
- AssertAlmostEqualImpl((a), (#a), (b), (#b), (precision), \
- __FILE__, __FUNCTION__, __LINE__, ""s)
- #define ASSERT_ALMOST_EQUAL_HINT(a, b, precision, hint) \
- AssertAlmostEqualImpl((a), (#a), (b), (#b), (precision), \
- __FILE__, __FUNCTION__, __LINE__, (hint))
- //------------------------------------------------------------------------------
- std::ostream & operator<<(std::ostream & os, DocumentStatus status);
- void TestMatchDocumentPlusWords();
- void TestMatchDocumentMinusWords();
- void TestMatchDocumentStopWords();
- void TestAddDocument();
- void TestAddDocumentAverageRating();
- void TestAddDocumentStatus();
- void TestTfIdfCalculation();
- void TestFindTopDocumentsPlusWords();
- void TestFindTopDocumentsMinusWords();
- void TestFindTopDocumentsStopWords();
- void TestFindTopDocumentsByStatus();
- void TestFindTopDocumentsUsingPredicate();
- void TestFindTopDocumentsSort();
- //------------------------------------------------------------------------------
- // Функция TestSearchServer является точкой входа для запуска тестов
- void TestSearchServer();
- #include <numeric>
- //------------------------------------------------------------------------------
- void TestSearchServer() {
- TestAddDocument();
- TestAddDocumentAverageRating();
- TestAddDocumentStatus();
- TestTfIdfCalculation();
- TestMatchDocumentPlusWords();
- TestMatchDocumentMinusWords();
- TestMatchDocumentStopWords();
- TestFindTopDocumentsPlusWords();
- TestFindTopDocumentsMinusWords();
- TestFindTopDocumentsStopWords();
- TestFindTopDocumentsByStatus();
- TestFindTopDocumentsUsingPredicate();
- TestFindTopDocumentsSort();
- // Не забудьте вызывать остальные тесты здесь
- }
- //------------------------------------------------------------------------------
- /** Проверяет, что слова в уже проиндексированном документе можно сделать стоп-словами и исключить из поиска */
- void TestFindTopDocumentsStopWords() {
- SearchServer server;
- const int doc_id = 0;
- const vector<int> ratings = { 5 };
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- server.AddDocument(doc_id, document_text, DocumentStatus::ACTUAL, ratings);
- const std::string query = "fox"s;
- // Убеждаемся, что документ найден по плюс-слову
- {
- auto search_results = server.FindTopDocuments(query);
- ASSERT_EQUAL(1u, search_results.size());
- }
- // Делаем слово стоп-словом, убеждаемся, что поисковик теперь его игнорирует
- {
- server.SetStopWords(query);
- auto search_results = server.FindTopDocuments(query);
- ASSERT(search_results.empty());
- }
- }
- //------------------------------------------------------------------------------
- void TestFindTopDocumentsMinusWords() {
- SearchServer server;
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- server.AddDocument(0, document_text, DocumentStatus::ACTUAL, { 5 });
- // Проверим, что документ можно найти по запросу - все три слова есть в документе
- {
- const std::string query = "brown fox dog"s;
- auto results = server.FindTopDocuments(query);
- ASSERT_EQUAL(1u, results.size());
- }
- // Одно из слов сделаем минус-словом, убедимся, что теперь документ исключен из поиска
- {
- const std::string query = "brown fox -dog"s;
- auto results = server.FindTopDocuments(query);
- ASSERT(results.empty());
- }
- }
- //------------------------------------------------------------------------------
- std::ostream &operator<<(std::ostream &os, DocumentStatus status) {
- return os << static_cast<int>(status);
- }
- //------------------------------------------------------------------------------
- void TestFindTopDocumentsPlusWords() {
- SearchServer server;
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- server.AddDocument(0, document_text, DocumentStatus::ACTUAL, { 5 });
- const std::string query = "brown dog"s;
- std::vector<Document> results = server.FindTopDocuments(query);
- ASSERT_EQUAL(1u, results.size());
- ASSERT_EQUAL(0, results[0].id);
- ASSERT_EQUAL(5, results[0].rating);
- // Когда документ в индексе единственный, то IDF == 0, т.к. log(1) == 0
- // std::clog << "log(1) = " << log(1) << std::endl;
- ASSERT(results[0].relevance < 0.0000001);
- }
- //------------------------------------------------------------------------------
- void TestAddDocument() {
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- const vector<int> doc_ratings = { 1, 2, 3, 4, 5, 5 };
- const DocumentStatus status = DocumentStatus::BANNED;
- SearchServer server;
- ASSERT_EQUAL(0, server.GetDocumentCount());
- for (size_t i = 1; i <= 5; ++i) {
- server.AddDocument(i, document_text, status, doc_ratings);
- // Добавление документа увеличивает счетчик
- ASSERT_EQUAL(i, server.GetDocumentCount());
- }
- }
- //------------------------------------------------------------------------------
- void TestAddDocumentAverageRating() {
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- const vector<int> doc_ratings = { 1, 2, 3, 4, 5, 5 };
- SearchServer server;
- server.AddDocument(0, document_text, DocumentStatus::ACTUAL, doc_ratings);
- // Проверим, что документу присвоен рейтинг
- const int avg_rating = static_cast<double>(
- std::accumulate(doc_ratings.begin(),
- doc_ratings.end(), 0)) / doc_ratings.size();
- const std::string query = "fox"s;
- auto documents = server.FindTopDocuments(query);
- ASSERT_EQUAL(documents.at(0).rating, avg_rating);
- }
- //------------------------------------------------------------------------------
- /** FIXME Do we need this test? */
- void TestAddDocumentStatus() {
- const int doc_id = 0;
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- const vector<int> doc_ratings = { 1, 2, 3, 4, 5, 5 };
- const DocumentStatus status = DocumentStatus::BANNED;
- SearchServer server;
- server.AddDocument(doc_id, document_text, status, doc_ratings);
- // Проверим, что документу присвоен статус
- const std::string query = "fox"s;
- // Поиск со статусом ACTUAL по умолчанию не вернет документов
- {
- auto search_results = server.FindTopDocuments(query);
- ASSERT(search_results.empty());
- }
- // Поиск со статусом status должен вернуть наш документ
- {
- auto search_results = server.FindTopDocuments(query, status);
- ASSERT_EQUAL(1u, search_results.size());
- }
- }
- //------------------------------------------------------------------------------
- std::map<std::string, size_t> ParseDocumentText(const std::string & text) {
- std::map<std::string, size_t> stats;
- for (const auto & word : SplitIntoWords(text)) {
- ++stats[word];
- }
- return stats;
- }
- //------------------------------------------------------------------------------
- double CalcTermFrequency(const std::string & term,
- const std::map<std::string, size_t> & all_words) {
- double tf = 0.0;
- if (all_words.count(term)) {
- auto sum = [](size_t accum, const std::pair<std::string, size_t> & item) {
- const auto & [_, count] = item;
- return accum + count;
- };
- const size_t total_words =
- std::accumulate(all_words.begin(), all_words.end(), 0, sum);
- const int term_count = all_words.at(term);
- tf = static_cast<double>(term_count) / total_words;
- }
- return tf;
- }
- //------------------------------------------------------------------------------
- double CalcTermFrequency(const std::string & term, const std::string & text) {
- return CalcTermFrequency(term, ParseDocumentText(text));
- }
- //------------------------------------------------------------------------------
- double CalcTermInverseDocumentFrequency(const std::string & term,
- const std::vector<std::string> & documents) {
- auto HasTerm = [&term](const std::string & text) {
- return (ParseDocumentText(text).count(term));
- };
- const size_t documents_with_term_count = std::count_if(documents.begin(),
- documents.end(), HasTerm);
- return log(static_cast<double>(documents.size()) / documents_with_term_count);
- }
- //------------------------------------------------------------------------------
- /** Тест на расчет релевантности документов запросам по TF-IDF */
- void TestTfIdfCalculation() {
- SearchServer server;
- const DocumentStatus status_actual = DocumentStatus::ACTUAL;
- const std::vector<int> ratings = {1,2,3,4,5};
- const std::vector<std::string> test_documents = {
- "this blue ball"s,
- "this red box"s,
- "this yellow circle"s,
- };
- const size_t documents_total = test_documents.size();
- const std::map<std::string, std::map<size_t, double>> tf {
- { "this"s, {
- { 0, 1.0/static_cast<double>(documents_total) },
- { 1, 1.0/static_cast<double>(documents_total) },
- { 2, 1.0/static_cast<double>(documents_total) },
- } },
- { "blue"s, {
- { 0, 1.0/static_cast<double>(documents_total) },
- } },
- { "ball"s, {
- { 0, 1.0/static_cast<double>(documents_total) },
- } },
- { "red"s, {
- { 1, 1.0/static_cast<double>(documents_total) },
- } },
- { "box"s, {
- { 1, 1.0/static_cast<double>(documents_total) },
- } },
- { "yellow"s, {
- { 2, 1.0/static_cast<double>(documents_total) },
- } },
- { "circle"s, {
- { 2, 1.0/static_cast<double>(documents_total) },
- } },
- };
- std::map<std::string, size_t> df;
- for (const auto & [term, freqs] : tf) {
- df[term] = freqs.size();
- std::clog << "DF('" << term << "') = " << df.at(term) << std::endl;
- }
- const std::map<std::string, double> idf = {
- { "this"s, log( static_cast<double>(documents_total)/df.at("this"s)) },
- { "blue"s, log( static_cast<double>(documents_total)/df.at("blue"s)) },
- { "ball"s, log( static_cast<double>(documents_total)/df.at("ball"s)) },
- { "red"s, log( static_cast<double>(documents_total)/df.at("red"s)) },
- { "box"s, log( static_cast<double>(documents_total)/df.at("box"s)) },
- { "yellow"s, log( static_cast<double>(documents_total)/df.at("yellow"s)) },
- { "circle"s, log( static_cast<double>(documents_total)/df.at("circle"s)) },
- };
- for (const auto & [word, value] : idf) {
- std::clog << "'" << word << "' has IDF=" << value << std::endl;
- }
- const std::vector<std::string> queries = {
- "this box"s,
- "this ball"s,
- "this circle"s,
- };
- std::map<size_t, std::map<size_t, double>> query_to_doc_relevance = {
- { 0, {
- { 0, 0.0 },
- { 1, 1.0/static_cast<double>(documents_total) * idf.at("box"s) },
- { 2, 0.0 } },
- },
- { 1, {
- { 0, 1.0/static_cast<double>(documents_total) * idf.at("ball"s) },
- { 1, 0.0 },
- { 2, 0.0 },
- } },
- { 2, {
- { 0, 0.0 },
- { 1, 0.0 },
- { 2, 1.0/static_cast<double>(documents_total) * idf.at("circle") }
- } },
- };
- // Сперва добавим все документы
- for (size_t i = 0; i < test_documents.size(); ++i) {
- const std::string & text = test_documents.at(i);
- int doc_id = static_cast<int>(i);
- std::clog << "Document [" << doc_id << "] is '" << text << "'" << std::endl;
- server.AddDocument(doc_id, text, status_actual, ratings);
- }
- for (size_t q = 0; q < queries.size(); ++q) {
- const std::string & query = queries.at(q);
- std::clog << "Query '" << query << "'" << std::endl;
- auto search_results = server.FindTopDocuments(query);
- for (const auto & doc : search_results) {
- const double expected_relevance = query_to_doc_relevance.at(q).at(doc.id);
- std::clog << "Document [" << doc.id << "], relevance = " << doc.relevance
- << ", expected = " << expected_relevance
- << std::endl;
- ASSERT_ALMOST_EQUAL(doc.relevance, expected_relevance, 0.01);
- }
- }
- }
- //------------------------------------------------------------------------------
- void TestFindTopDocumentsByStatus() {
- SearchServer server;
- std::string text = "this is an example"s;
- const DocumentStatus status_banned = DocumentStatus::BANNED;
- const std::vector<int> ratings = { 5 };
- const size_t documents_count = 3;
- for (size_t i = 0; i < documents_count; ++i) {
- server.AddDocument(static_cast<int>(i), text, status_banned, ratings);
- }
- const std::string query = "this"s;
- // Убелимся, что нет документов со статусом ACTUAL
- {
- auto actual_documents = server.FindTopDocuments(query);
- ASSERT(actual_documents.empty());
- }
- // Убедимся, что все документы - с нужным статусом
- {
- auto banned_documents = server.FindTopDocuments(query, status_banned);
- ASSERT_EQUAL(documents_count, banned_documents.size());
- }
- }
- //------------------------------------------------------------------------------
- void TestFindTopDocumentsUsingPredicate() {
- // Добавим два докумета с одинаковым текстом, но разными статусами
- SearchServer server;
- std::string text = "this is an example"s;
- const std::vector<int> avg_ratings = { 1, 2, 3, 4, 4, 4, 7 };
- // Добавим столько документов, сколько объявили рейтингов
- for (size_t i = 0; i < avg_ratings.size(); ++i) {
- server.AddDocument(static_cast<int>(i), text, DocumentStatus::ACTUAL,
- { avg_ratings.at(i) });
- }
- // Поиск без предиката возвращает все документы
- {
- const size_t expected_documents_count =
- std::min(avg_ratings.size(), static_cast<size_t>(MAX_RESULT_DOCUMENT_COUNT));
- ASSERT_EQUAL(expected_documents_count, server.FindTopDocuments(text).size());
- }
- // Найдем документ по предикату с существующим id
- {
- const int id = 0;
- auto predicate_id = [id](int doc_id, DocumentStatus status, int rating) {
- UNUSED(status);
- UNUSED(rating);
- return doc_id == id;
- };
- auto search_results = server.FindTopDocuments(text, predicate_id);
- ASSERT_EQUAL(1u, search_results.size());
- ASSERT_EQUAL(id, search_results.at(0).id);
- }
- // Убедимся, что поиск по предикату с несуществующим id не возвращает документов
- {
- const int id_not_exists = avg_ratings.size();
- auto predicate_id = [id_not_exists](int doc_id, DocumentStatus status, int rating) {
- UNUSED(status);
- UNUSED(rating);
- return doc_id == id_not_exists;
- };
- auto search_results = server.FindTopDocuments(text, predicate_id);
- ASSERT(search_results.empty());
- }
- // Найдем документы с рейтингом 4 по предикату
- {
- auto predicate_rating4 = [](int doc_id, DocumentStatus status, int rating) {
- UNUSED(doc_id);
- UNUSED(status);
- return rating == 4;
- };
- auto search_results = server.FindTopDocuments(text, predicate_rating4);
- ASSERT_EQUAL(std::count( avg_ratings.begin(), avg_ratings.end(), 4 ),
- search_results.size());
- }
- }
- //------------------------------------------------------------------------------
- void TestFindTopDocumentsSort() {
- SearchServer server;
- const DocumentStatus status_actual = DocumentStatus::ACTUAL;
- const std::vector<int> ratings = {1,2,3,4,5};
- // Нарочно перепутаем порядок документов, чтобы поисковик не мог случайно вернуть документы в порядке их добавления
- const std::vector<std::string> test_documents = {
- "this is a green crocodile"s,
- "this is a a test"s,
- "this is a white crocodile"s,
- };
- const std::string query = "this white crocodile"s;
- // Сперва добавим все документы
- for (size_t i = 0; i < test_documents.size(); ++i) {
- const std::string text = test_documents.at(i);
- server.AddDocument(i, text, status_actual, ratings);
- }
- // Документы упорядочены по убыванию релевантности
- auto search_results = server.FindTopDocuments(query);
- for (size_t i = 0; i < search_results.size(); ++i) {
- if (0 < i) {
- ASSERT(search_results.at(i).relevance <= search_results.at(i-1).relevance);
- }
- }
- }
- //------------------------------------------------------------------------------
- /** Проверим, что MatchDocument работает по плюс-словам */
- void TestMatchDocumentPlusWords() {
- SearchServer server;
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- server.AddDocument(0, document_text, DocumentStatus::ACTUAL, { 5 });
- const std::string query = "brown dog"s;
- auto [matched_words, status] = server.MatchDocument(query, 0);
- const std::vector<std::string> expected_matched_words = { "brown"s, "dog"s };
- ASSERT_EQUAL(DocumentStatus::ACTUAL, status);
- ASSERT_EQUAL(expected_matched_words, matched_words);
- }
- //------------------------------------------------------------------------------
- /** Проверим, что MatchDocument исключает документы с минус-словами */
- void TestMatchDocumentMinusWords() {
- SearchServer server;
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- server.AddDocument(0, document_text, DocumentStatus::ACTUAL, { 5 });
- // Проверим, что документ можно найти по запросу - все три слова есть в документе
- {
- const std::string query = "brown fox dog"s;
- auto [matched_words, status] = server.MatchDocument(query, 0);
- ASSERT_EQUAL(3u, matched_words.size());
- }
- // Одно из слов сделаем минус-словом, убедимся, что теперь документ не подходит к запросу
- {
- const std::string query = "brown fox -dog"s;
- auto [matched_words, status] = server.MatchDocument(query, 0);
- ASSERT(matched_words.empty());
- }
- }
- //------------------------------------------------------------------------------
- /** Проверим, что MatchDocument игнорирует стоп-слова в запросах */
- void TestMatchDocumentStopWords() {
- SearchServer server;
- const std::string stop_words = "a the fox"s;
- server.SetStopWords(stop_words);
- const std::string document_text = "A quick brown fox jumps over lazy dog"s;
- server.AddDocument(0, document_text, DocumentStatus::ACTUAL, { 5 });
- {
- auto [matched_words, status] = server.MatchDocument("brown fox", 0);
- ASSERT_EQUAL(1u, matched_words.size());
- ASSERT_EQUAL("brown", matched_words.at(0));
- }
- }
RAW Paste Data