Advertisement
kutuzzzov

Урок 8. Фреймворк и поисковая система

Dec 1st, 2021 (edited)
1,583
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 19.14 KB | None | 0 0
  1. #include <algorithm>
  2. #include <iostream>
  3. #include <set>
  4. #include <string>
  5. #include <utility>
  6. #include <vector>
  7. #include <map>
  8. #include <cmath>
  9. #include <cassert>
  10.  
  11. using namespace std;
  12.  
  13. const int MAX_RESULT_DOCUMENT_COUNT = 5;
  14.  
  15. string ReadLine() {
  16.     string s;
  17.     getline(cin, s);
  18.     return s;
  19. }
  20.  
  21. int ReadLineWithNumber() {
  22.     int result = 0;
  23.     cin >> result;
  24.     ReadLine();
  25.     return result;
  26. }
  27.  
  28. vector<string> SplitIntoWords(const string& text) {
  29.     vector<string> words;
  30.     string word;
  31.     for (const char c : text) {
  32.         if (c == ' ') {
  33.             if (!word.empty()) {
  34.                 words.push_back(word);
  35.                 word.clear();
  36.             }
  37.         }
  38.         else {
  39.             word += c;
  40.         }
  41.     }
  42.     if (!word.empty()) {
  43.         words.push_back(word);
  44.     }
  45.     return words;
  46. }
  47.  
  48. struct Document {
  49.     int id;
  50.     double relevance;
  51.     int rating;
  52. };
  53.  
  54. enum class DocumentStatus {
  55.     ACTUAL,
  56.     IRRELEVANT,
  57.     BANNED,
  58.     REMOVED
  59. };
  60.  
  61. class SearchServer {
  62. public:
  63.     void SetStopWords(const string& text) {
  64.         for (const string& word : SplitIntoWords(text)) {
  65.             stop_words_.insert(word);
  66.         }
  67.     }
  68.  
  69.     void AddDocument(int document_id, const string& document, DocumentStatus status, const vector<int>& ratings) {
  70.         const vector<string> words = SplitIntoWordsNoStop(document);
  71.         for (auto& word : words) {
  72.             word_to_document_freqs_[word][document_id] += 1.0 / words.size();
  73.         }
  74.         documents_.emplace(document_id, DocumentData { ComputeAverageRating(ratings), status });
  75.     }
  76.  
  77.     int GetDocumentCount() const {
  78.         return documents_.size();
  79.     }
  80.  
  81.     template<typename DocumentPredicate>
  82.     vector<Document> FindTopDocuments(const string& raw_query, DocumentPredicate predicate) const {
  83.         const Query query = ParseQuery(raw_query);
  84.         auto matched_documents = FindAllDocuments(query, predicate);
  85.  
  86.         sort(matched_documents.begin(), matched_documents.end(),
  87.             [](const Document& lhs, const Document& rhs) {
  88.                 const double EPSILON = 1e-6;
  89.                 if (abs(lhs.relevance - rhs.relevance) < EPSILON) {
  90.                     return lhs.rating > rhs.rating;
  91.                 }
  92.                 else {
  93.                     return lhs.relevance > rhs.relevance;
  94.                 }
  95.             });
  96.         if (matched_documents.size() > MAX_RESULT_DOCUMENT_COUNT) {
  97.             matched_documents.resize(MAX_RESULT_DOCUMENT_COUNT);
  98.         }
  99.         return matched_documents;
  100.     }
  101.  
  102.     vector<Document> FindTopDocuments(const string& raw_query) const {
  103.         return FindTopDocuments(raw_query, DocumentStatus::ACTUAL);
  104.     }
  105.  
  106.     vector<Document> FindTopDocuments(const string& raw_query, DocumentStatus status) const {
  107.         return FindTopDocuments(raw_query,
  108.             [&status](int document_id, DocumentStatus new_status, int rating) {
  109.                 return new_status == status;
  110.             });
  111.     }
  112.  
  113.     tuple<vector<string>, DocumentStatus> MatchDocument(const string& raw_query, int document_id) const {
  114.         const Query query = ParseQuery(raw_query);
  115.         vector<string> matched_words;
  116.         for (const string& word : query.plus_words) {
  117.             if (word_to_document_freqs_.count(word) == 0) {
  118.                 continue;
  119.             }
  120.             if (word_to_document_freqs_.at(word).count(document_id)) {
  121.                 matched_words.push_back(word);
  122.             }
  123.         }
  124.         for (const string& word : query.minus_words) {
  125.             if (word_to_document_freqs_.count(word) == 0) {
  126.                 continue;
  127.             }
  128.             if (word_to_document_freqs_.at(word).count(document_id)) {
  129.                 matched_words.clear();
  130.                 break;
  131.             }
  132.         }
  133.         return { matched_words, documents_.at(document_id).status };
  134.     }
  135.  
  136. private:
  137.     struct Query {
  138.         set<string> plus_words;
  139.         set<string> minus_words;
  140.     };
  141.  
  142.     struct DocumentData {
  143.         int rating;
  144.         DocumentStatus status;
  145.     };
  146.  
  147.     map<string, map<int, double>> word_to_document_freqs_;
  148.     map<int, DocumentData> documents_;
  149.     set<string> stop_words_;
  150.  
  151.     bool IsStopWord(const string& word) const {
  152.         return stop_words_.count(word) > 0;
  153.     }
  154.  
  155.     vector<string> SplitIntoWordsNoStop(const string& text) const {
  156.         vector<string> words;
  157.         for (const string& word : SplitIntoWords(text)) {
  158.             if (!IsStopWord(word)) {
  159.                 words.push_back(word);
  160.             }
  161.         }
  162.         return words;
  163.     }
  164.  
  165.     static int ComputeAverageRating(const vector<int>& ratings) {
  166.         if (ratings.empty()) {
  167.             return 0;
  168.         }
  169.         int rating_sum = 0;
  170.         for (const auto rating : ratings) {
  171.             rating_sum += rating;
  172.         }
  173.         return rating_sum / static_cast<int>(ratings.size());
  174.     }
  175.  
  176.     Query ParseQuery(const string& text) const {
  177.         Query query;
  178.         for (string& word : SplitIntoWordsNoStop(text)) {
  179.             if (word[0] == '-') {
  180.                 word = word.substr(1);
  181.                 if (!IsStopWord(word)) {
  182.                     query.minus_words.insert(word);
  183.                 }
  184.             }
  185.             query.plus_words.insert(word);
  186.         }
  187.         return query;
  188.     }
  189.  
  190.     template<typename DocumentPredicate>
  191.     vector<Document> FindAllDocuments(const Query& query, DocumentPredicate predicate) const {
  192.         map<int, double> document_to_relevance;
  193.         for (const string& word : query.plus_words) {
  194.             if (word_to_document_freqs_.count(word) == 0) {
  195.                 continue;
  196.             }
  197.             for (const auto& [document_id, term_freq] : word_to_document_freqs_.at(word)) {
  198.                 const DocumentData documents_data = documents_.at(document_id);
  199.                 if (predicate(document_id, documents_data.status, documents_data.rating)) {
  200.                     const double IDF = log(GetDocumentCount() * 1.0 / word_to_document_freqs_.at(word).size());
  201.                     document_to_relevance[document_id] += IDF * term_freq;
  202.                 }
  203.             }
  204.         }
  205.         for (const string& word : query.minus_words) {
  206.             if (word_to_document_freqs_.count(word) == 0) {
  207.                 continue;
  208.             }
  209.             for (const auto& [document_id, term_freq] : word_to_document_freqs_.at(word)) {
  210.                 document_to_relevance.erase(document_id);
  211.             }
  212.         }
  213.  
  214.         vector<Document> matched_documents;
  215.         for (const auto [document_id, relevance] : document_to_relevance) {
  216.             matched_documents.push_back({ document_id, relevance, documents_.at(document_id).rating });
  217.         }
  218.         return matched_documents;
  219.     }
  220. };
  221.  
  222. /*
  223.    Подставьте сюда вашу реализацию макросов
  224.    ASSERT, ASSERT_EQUAL, ASSERT_EQUAL_HINT, ASSERT_HINT и RUN_TEST
  225. */
  226.  
  227. template <typename T, typename U>
  228. ostream& operator<<(ostream& out, const pair<T, U>& container) {
  229.     return out << container.first << ": " << container.second;
  230. }
  231.  
  232. template <typename T>
  233. void Print(ostream& out, const T& container) {
  234.     bool is_first = true;
  235.     for (const auto& element : container) {
  236.         if (is_first) {
  237.             out << element;
  238.             is_first = false;
  239.         }
  240.         else {
  241.             out << ", "s << element;
  242.         }
  243.     }
  244. }
  245.  
  246. template <typename T>
  247. ostream& operator<<(ostream& out, const vector<T>& container) {
  248.     out << "["s;
  249.     Print(out, container);
  250.     out << "]"s;
  251.     return out;
  252. }
  253.  
  254. template <typename T>
  255. ostream& operator<<(ostream& out, const set<T>& container) {
  256.     out << "{"s;
  257.     Print(out, container);
  258.     out << "}"s;
  259.     return out;
  260. }
  261.  
  262. template <typename T, typename U>
  263. ostream& operator<<(ostream& out, const map<T, U>& container) {
  264.     out << "{"s;
  265.     Print(out, container);
  266.     out << "}"s;
  267.     return out;
  268. }
  269.  
  270. template <typename T, typename U>
  271. void AssertEqualImpl(const T& t, const U& u, const string& t_str, const string& u_str, const string& file,
  272.     const string& func, unsigned line, const string& hint) {
  273.     if (t != u) {
  274.         cerr << boolalpha;
  275.         cerr << file << "("s << line << "): "s << func << ": "s;
  276.         cerr << "ASSERT_EQUAL("s << t_str << ", "s << u_str << ") failed: "s;
  277.         cerr << t << " != "s << u << "."s;
  278.         if (!hint.empty()) {
  279.             cerr << " Hint: "s << hint;
  280.         }
  281.         cerr << endl;
  282.         abort();
  283.     }
  284. }
  285.  
  286. void AssertImpl(bool value, const string& expr_str, const string& file, const string& func, unsigned line,
  287.     const string& hint) {
  288.     if (!value) {
  289.         cerr << file << "("s << line << "): "s << func << ": "s;
  290.         cerr << "ASSERT("s << expr_str << ") failed."s;
  291.         if (!hint.empty()) {
  292.             cerr << " Hint: "s << hint;
  293.         }
  294.         cerr << endl;
  295.         abort();
  296.     }
  297. }
  298.  
  299. template <typename T>
  300. void RunTestImpl(T func, const string& func_str) {
  301.     func();
  302.     cerr << func_str << " OK" << endl;
  303. }
  304.  
  305. #define ASSERT(expr) AssertImpl(!!(expr), #expr, __FILE__, __FUNCTION__, __LINE__, ""s)
  306.  
  307. #define ASSERT_EQUAL(a, b) AssertEqualImpl((a), (b), #a, #b, __FILE__, __FUNCTION__, __LINE__, ""s)
  308.  
  309. #define ASSERT_EQUAL_HINT(a, b, hint) AssertEqualImpl((a), (b), #a, #b, __FILE__, __FUNCTION__, __LINE__, (hint))
  310.  
  311. #define ASSERT_HINT(expr, hint) AssertImpl(!!(expr), #expr, __FILE__, __FUNCTION__, __LINE__, (hint))
  312.  
  313. #define RUN_TEST(func) RunTestImpl((func), #func)
  314.  
  315. // -------- Начало модульных тестов поисковой системы ----------
  316.  
  317. // Тест проверяет, что поисковая система исключает стоп-слова при добавлении документов
  318.  
  319. void TestExcludeStopWordsFromAddedDocumentContent() {
  320.     const int doc_id = 42;
  321.     const string content = "cat in the city"s;
  322.     const vector<int> ratings = { 1, 2, 3 };
  323.  
  324.     {
  325.         SearchServer server;
  326.         server.AddDocument(doc_id, content, DocumentStatus::ACTUAL, ratings);
  327.         const auto found_docs = server.FindTopDocuments("in"s);
  328.         ASSERT_EQUAL(found_docs.size(), 1u);
  329.         const Document& doc0 = found_docs[0];
  330.         ASSERT_EQUAL(doc0.id, doc_id);
  331.     }
  332.  
  333.  
  334.  
  335.     {
  336.         SearchServer server;
  337.         server.SetStopWords("in the"s);
  338.         server.AddDocument(doc_id, content, DocumentStatus::ACTUAL, ratings);
  339.         ASSERT_HINT(server.FindTopDocuments("in"s).empty(), "Stop words must be excluded from documents"s);
  340.     }
  341. }
  342.  
  343. // Добавление документов. Добавленный документ должен находиться по поисковому запросу, который содержит слова из документа.
  344. void TestFindAddedDocumentByDocumentWord() {
  345.     const int doc_id = 42;
  346.     const string content = "cat in the city"s;
  347.     const vector<int> ratings = { 1, 2, 3 };
  348.  
  349.     {
  350.         SearchServer server;
  351.         ASSERT_EQUAL(server.GetDocumentCount(), 0);
  352.         server.AddDocument(doc_id, content, DocumentStatus::ACTUAL, ratings);
  353.         ASSERT_EQUAL(server.GetDocumentCount(), 1);
  354.         server.AddDocument(doc_id + 1, "black dog fluffy tail", DocumentStatus::ACTUAL, ratings);
  355.         ASSERT_EQUAL(server.GetDocumentCount(), 2);
  356.         const auto found_docs = server.FindTopDocuments("cat"s);
  357.         ASSERT_EQUAL(found_docs.size(), 1u);
  358.         const Document& doc0 = found_docs[0];
  359.         ASSERT_EQUAL(doc0.id, doc_id);
  360.     }
  361. }
  362.  
  363. // Поддержка минус-слов. Документы, содержащие минус-слова из поискового запроса, не должны включаться в результаты поиска.
  364. void TestExcludeDocumentsWithMinusWordsFromResults() {
  365.     SearchServer server;
  366.     server.AddDocument(101, "fluffy cat fluffy tail"s, DocumentStatus::ACTUAL, { 1,2,3 });
  367.     server.AddDocument(102, "fluffy red dog "s, DocumentStatus::ACTUAL, { 1,2,3 });
  368.  
  369.     {
  370.         const auto found_docs = server.FindTopDocuments("fluffy -dog"s);
  371.         ASSERT_EQUAL(found_docs.size(), 1u);
  372.         ASSERT_EQUAL(found_docs[0].id, 101);
  373.     }
  374.  
  375.     {
  376.         const auto found_docs = server.FindTopDocuments("fluffy -cat"s);
  377.         ASSERT_EQUAL(found_docs.size(), 1u);
  378.         ASSERT_EQUAL(found_docs[0].id, 102);
  379.     }
  380.  
  381. }
  382.  
  383. // Соответствие документов поисковому запросу.
  384. void TestMatchedDocuments() {
  385.     SearchServer server;
  386.     server.SetStopWords("and in on"s);
  387.     server.AddDocument(100, "fluffy cat and black dog in a collar"s, DocumentStatus::ACTUAL, { 1, 2, 3 });
  388.  
  389.     {
  390.         const auto [matched_words, status] = server.MatchDocument("dog and cat"s, 100);
  391.         const vector<string> expected_result = { "cat"s, "dog"s };
  392.         ASSERT_EQUAL(expected_result, matched_words);
  393.     }
  394.  
  395.     {
  396.         const auto [matched_words, status] = server.MatchDocument("dog and -cat"s, 100);
  397.         const vector<string> expected_result = {}; // пустой результат поскольку есть минус-слово
  398.         ASSERT_EQUAL(expected_result, matched_words);
  399.         ASSERT(matched_words.empty());
  400.     }
  401. }
  402.  
  403. // Сортировка найденных документов по релевантности.
  404. void TestSortResultsByRelevance() {
  405.     SearchServer server;
  406.     server.AddDocument(100, "fluffy cat fluffy tail"s, DocumentStatus::ACTUAL, { 1, 2, 3 });
  407.     server.AddDocument(101, "fluffy dog"s, DocumentStatus::ACTUAL, { 1, 2, 3 });
  408.     server.AddDocument(102, "dog leather collar"s, DocumentStatus::ACTUAL, { 1, 2, 3 });
  409.  
  410.     {
  411.         const auto found_docs = server.FindTopDocuments("fluffy cat"s);
  412.         ASSERT_EQUAL(found_docs.size(), 2u);
  413.         for (size_t i = 1; i < found_docs.size(); i++) {
  414.             ASSERT(found_docs[i - 1].relevance >= found_docs[i].relevance);
  415.         }
  416.     }
  417. }
  418.  
  419. // Вычисление рейтинга документов.
  420. void TestCalculateDocumentRating() {
  421.     SearchServer server;
  422.     const vector<int> ratings = { 10, 11, 3 };
  423.     const int average = (10 + 11 + 3) / 3;
  424.     server.AddDocument(0, "fluffy cat fluffy tail"s, DocumentStatus::ACTUAL, ratings);
  425.  
  426.     {
  427.         const auto found_docs = server.FindTopDocuments("fluffy cat"s);
  428.         ASSERT_EQUAL(found_docs.size(), 1u);
  429.         ASSERT_EQUAL(found_docs[0].rating, average);
  430.     }
  431. }
  432.  
  433. // Фильтрация результатов поиска с использованием предиката.
  434. void TestDocumentSearchByPredicate() {
  435.     SearchServer server;
  436.     server.AddDocument(100, "cat in the city"s, DocumentStatus::ACTUAL, { 1, 2, 3 });
  437.     server.AddDocument(101, "dog in the town"s, DocumentStatus::IRRELEVANT, { -1, -2, -3 });
  438.     server.AddDocument(102, "dog and rabbit in the town"s, DocumentStatus::ACTUAL, { -4, -5, -6 });
  439.     const auto found_docs = server.FindTopDocuments("in the cat"s, [](int document_id, DocumentStatus status, int rating) { return rating > 0; });
  440.  
  441.     {
  442.         ASSERT_HINT(found_docs[0].id == 100, "Wrong predicate");
  443.     }
  444. }
  445.  
  446. // Поиск документов, имеющих заданный статус.
  447. void TestDocumentSearchByStatus() {
  448.     const int doc_id1 = 42;
  449.     const int doc_id2 = 43;
  450.     const int doc_id3 = 44;
  451.     const string content1 = "cat in the city"s;
  452.     const string content2 = "cat in the town"s;
  453.     const string content3 = "cat in the town or city"s;
  454.     const vector<int> ratings = { 1, 2, 3 };
  455.     SearchServer server;
  456.     server.AddDocument(doc_id1, content1, DocumentStatus::ACTUAL, ratings);
  457.     server.AddDocument(doc_id2, content2, DocumentStatus::IRRELEVANT, ratings);
  458.     server.AddDocument(doc_id3, content3, DocumentStatus::IRRELEVANT, ratings);
  459.     const auto found_docs = server.FindTopDocuments("in the cat"s, DocumentStatus::IRRELEVANT);
  460.  
  461.     {
  462.         ASSERT_HINT(found_docs[0].id == doc_id2, "Wrong status");
  463.         ASSERT_HINT(found_docs[1].id == doc_id3, "Wrong status");
  464.         ASSERT_HINT(found_docs.size() == 2, "Wrong status request");
  465.     }
  466. }
  467.  
  468. // Корректное вычисление релевантности найденных документов.
  469. void TestCalculateRelevance() {
  470.     SearchServer server;
  471.     server.AddDocument(100, "white cat with new ring"s, DocumentStatus::ACTUAL, { 1, 2, 3 });
  472.     server.AddDocument(101, "fluffy cat fluffy tail"s, DocumentStatus::ACTUAL, { 1, 2, 3 });
  473.     server.AddDocument(102, "good dog big eyes"s, DocumentStatus::ACTUAL, { 1, 2, 3 });
  474.     const auto found_docs = server.FindTopDocuments("fluffy good cat"s);
  475.     double relevance_query = log((3 * 1.0) / 1) * (2.0 / 4.0) + log((3 * 1.0) / 2) * (1.0 / 4.0);
  476.  
  477.     {
  478.         ASSERT_HINT(fabs(found_docs[0].relevance - relevance_query) < 1e-6, "Wrong calculation relevance");
  479.     }
  480. }
  481.  
  482. // Функция TestSearchServer является точкой входа для запуска тестов
  483. void TestSearchServer() {
  484.     RUN_TEST(TestExcludeStopWordsFromAddedDocumentContent);
  485.     RUN_TEST(TestFindAddedDocumentByDocumentWord);
  486.     RUN_TEST(TestExcludeDocumentsWithMinusWordsFromResults);
  487.     RUN_TEST(TestMatchedDocuments);
  488.     RUN_TEST(TestSortResultsByRelevance);
  489.     RUN_TEST(TestCalculateDocumentRating);
  490.     RUN_TEST(TestDocumentSearchByPredicate);
  491.     RUN_TEST(TestDocumentSearchByStatus);
  492.     RUN_TEST(TestCalculateRelevance);
  493. }
  494. // --------- Окончание модульных тестов поисковой системы -----------
  495.  
  496. int main() {
  497.     TestSearchServer();
  498.     // Если вы видите эту строку, значит все тесты прошли успешно
  499.     cout << "Search server testing finished"s << endl;
  500. }
  501.  
  502.  
  503. /*
  504. void PrintDocument(const Document& document) {
  505.     cout << "{ "s
  506.         << "document_id = "s << document.id << ", "s
  507.         << "relevance = "s << document.relevance << ", "s
  508.         << "rating = "s << document.rating
  509.         << " }"s << endl;
  510. }
  511.  
  512. int main() {
  513.     SearchServer search_server;
  514.     search_server.SetStopWords("и в на"s);
  515.  
  516.     search_server.AddDocument(0, "белый кот и модный ошейник"s, DocumentStatus::ACTUAL, { 8, -3 });
  517.     search_server.AddDocument(1, "пушистый кот пушистый хвост"s, DocumentStatus::ACTUAL, { 7, 2, 7 });
  518.     search_server.AddDocument(2, "ухоженный пёс выразительные глаза"s, DocumentStatus::ACTUAL, { 5, -12, 2, 1 });
  519.     search_server.AddDocument(3, "ухоженный скворец евгений"s, DocumentStatus::BANNED, { 9 });
  520.  
  521.     cout << "ACTUAL by default:"s << endl;
  522.     for (const Document& document : search_server.FindTopDocuments("пушистый ухоженный кот"s)) {
  523.         PrintDocument(document);
  524.     }
  525.  
  526.     cout << "BANNED:"s << endl;
  527.     for (const Document& document : search_server.FindTopDocuments("пушистый ухоженный кот"s, DocumentStatus::BANNED)) {
  528.         PrintDocument(document);
  529.     }
  530.  
  531.     cout << "Even ids:"s << endl;
  532.     for (const Document& document : search_server.FindTopDocuments("пушистый ухоженный кот"s, [](int document_id, DocumentStatus status, int rating) { return document_id % 2 == 0; })) {
  533.         PrintDocument(document);
  534.     }
  535.  
  536.     return 0;
  537. }
  538. */
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement