#include #include #include #include #include #include #include #include #include using namespace std; const int MAX_RESULT_DOCUMENT_COUNT = 5; string ReadLine() { string s; getline(cin, s); return s; } int ReadLineWithNumber() { int result; cin >> result; ReadLine(); return result; } vector SplitIntoWords(const string& text) { vector words; string word; for (const char c : text) { if (c == ' ') { if (!word.empty()) { words.push_back(word); word.clear(); } } else { word += c; } } if (!word.empty()) { words.push_back(word); } return words; } struct Document { Document() = default; Document(int id, double relevance, int rating) : id(id) , relevance(relevance) , rating(rating) { } int id = 0; double relevance = 0.0; int rating = 0; }; template set MakeUniqueNonEmptyStrings(const StringContainer& strings) { set non_empty_strings; for (const string& str : strings) { if (!str.empty()) { non_empty_strings.insert(str); } } return non_empty_strings; } enum class DocumentStatus { ACTUAL, IRRELEVANT, BANNED, REMOVED, }; class SearchServer { public: inline static constexpr int INVALID_DOCUMENT_ID = -1; template explicit SearchServer(const StringContainer& stop_words) : stop_words_(MakeUniqueNonEmptyStrings(stop_words)) { for(const auto& stop_word: stop_words_){ if(IsValidWord(stop_word) == false){ throw std::invalid_argument("invalid argument"); } } } explicit SearchServer(const string& stop_words_text) : SearchServer( SplitIntoWords(stop_words_text)) // Invoke delegating constructor from string container { } void AddDocument(int document_id, const string& document, DocumentStatus status, const vector& ratings) { if (document_id < 0) { throw std::invalid_argument("the document is negative"); } if(documents_.count(document_id)){ throw std::invalid_argument("repeat document"); } if(IsValidWord(document)==false){ throw std::invalid_argument("invalid character"); } else{ const vector words = SplitIntoWordsNoStop(document); const double inv_word_count = 1.0 / words.size(); for (const string& word : words) { word_to_document_freqs_[word][document_id] += inv_word_count; } documents_.emplace(document_id, DocumentData{ComputeAverageRating(ratings), status}); document_ids.push_back(document_id); } } template vector FindTopDocuments(const string& raw_query, DocumentPredicate document_predicate) const { if(IsValidQuery(raw_query)==false){ throw std::invalid_argument("invalid character int findtop"); }else{ const Query query = ParseQuery(raw_query); auto matched_documents = FindAllDocuments(query, document_predicate); sort(matched_documents.begin(), matched_documents.end(), [](const Document& lhs, const Document& rhs) { if (abs(lhs.relevance - rhs.relevance) < 1e-6) { return lhs.rating > rhs.rating; } else { return lhs.relevance > rhs.relevance; } }); if (matched_documents.size() > MAX_RESULT_DOCUMENT_COUNT) { matched_documents.resize(MAX_RESULT_DOCUMENT_COUNT); } return matched_documents; } } vector FindTopDocuments(const string& raw_query, DocumentStatus status) const { if(IsValidQuery(raw_query)==false) { throw std::invalid_argument("invalid character int findtop"); }else{ return FindTopDocuments( raw_query, [status](int document_id, DocumentStatus document_status, int rating) { return document_status == status; }); } } vector FindTopDocuments(const string& raw_query) const { if(IsValidQuery(raw_query)==false) { throw std::invalid_argument("invalid character int findtop"); }else{ return FindTopDocuments(raw_query, DocumentStatus::ACTUAL); } } int GetDocumentCount() const { return documents_.size(); } tuple, DocumentStatus> MatchDocument(const string& raw_query, int document_id) const { if(IsValidQuery(raw_query)==false){ throw std::invalid_argument("invalid character int findtop"); }else{ const Query query = ParseQuery(raw_query); vector matched_words; for (const string& word : query.plus_words) { if (word_to_document_freqs_.count(word) == 0) { continue; } if (word_to_document_freqs_.at(word).count(document_id)) { matched_words.push_back(word); } } for (const string& word : query.minus_words) { if (word_to_document_freqs_.count(word) == 0) { continue; } if (word_to_document_freqs_.at(word).count(document_id)) { matched_words.clear(); break; } } auto result = tuple{matched_words, documents_.at(document_id).status}; return result; } } int GetDocumentId(const int index) const { return document_ids.at(index); } private: struct DocumentData { int rating; DocumentStatus status; }; const set stop_words_; map> word_to_document_freqs_; map documents_; vector document_ids; bool IsStopWord(const string& word) const { return stop_words_.count(word) > 0; } vector SplitIntoWordsNoStop(const string& text) const { vector words; for (const string& word : SplitIntoWords(text)) { if (!IsStopWord(word)) { words.push_back(word); } } return words; } static int ComputeAverageRating(const vector& ratings) { if (ratings.empty()) { return 0; } int rating_sum = 0; for (const int rating : ratings) { rating_sum += rating; } return rating_sum / static_cast(ratings.size()); } struct QueryWord { string data; bool is_minus; bool is_stop; }; QueryWord ParseQueryWord(string text) const { bool is_minus = false; QueryWord result; // Word shouldn't be empty if (text[0] == '-') { is_minus = true; text = text.substr(1); } result = {text, is_minus, IsStopWord(text)}; return result; } struct Query { set plus_words; set minus_words; }; Query ParseQuery(const string& text) const { Query query; for (const string& word : SplitIntoWords(text)) { const QueryWord query_word = ParseQueryWord(word); if (!query_word.is_stop) { if (query_word.is_minus) { query.minus_words.insert(query_word.data); } else { query.plus_words.insert(query_word.data); } } } return query; } // Existence required double ComputeWordInverseDocumentFreq(const string& word) const { return log(GetDocumentCount() * 1.0 / word_to_document_freqs_.at(word).size()); } template vector FindAllDocuments(const Query& query, DocumentPredicate document_predicate) const { map document_to_relevance; for (const string& word : query.plus_words) { if (word_to_document_freqs_.count(word) == 0) { continue; } const double inverse_document_freq = ComputeWordInverseDocumentFreq(word); for (const auto [document_id, term_freq] : word_to_document_freqs_.at(word)) { const auto& document_data = documents_.at(document_id); if (document_predicate(document_id, document_data.status, document_data.rating)) { document_to_relevance[document_id] += term_freq * inverse_document_freq; } } } for (const string& word : query.minus_words) { if (word_to_document_freqs_.count(word) == 0) { continue; } for (const auto [document_id, _] : word_to_document_freqs_.at(word)) { document_to_relevance.erase(document_id); } } vector matched_documents; for (const auto [document_id, relevance] : document_to_relevance) { matched_documents.push_back( {document_id, relevance, documents_.at(document_id).rating}); } return matched_documents; } static bool IsValidWord(const string& word) { return none_of(word.begin(), word.end(), [](char c) { return c >= '\0' && c < ' '; }); } static bool IsValidQuery(const string& raw_query) { if(IsValidWord(raw_query)==false) { return false; } for (int i = 0; i < raw_query.size(); ++i) { if (raw_query[i] == '-' || raw_query[raw_query.size()-1]=='-') { if (raw_query[i + 1] == '-' || raw_query[i + 1] == ' ') { return false; } } } return true; } };