shushus

Спринт 9. Параллелим методы поисковой системы. Задание 1.

Mar 6th, 2023 (edited)
311
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 22.34 KB | None | 0 0
  1. -----------------------------------------------------------------------
  2. main.cpp
  3. -----------------------------------------------------------------------
  4. #include "search_server.h"
  5. #include "process_queries.h"
  6.  
  7. #include <iostream>
  8. #include <string>
  9. #include <vector>
  10.  
  11. using namespace std;
  12.  
  13. int main() {
  14.     SearchServer search_server("and with"s);
  15.  
  16.     int id = 0;
  17.     for (
  18.         const string& text : {
  19.             "funny pet and nasty rat"s,
  20.             "funny pet with curly hair"s,
  21.             "funny pet and not very nasty rat"s,
  22.             "pet with rat and rat and rat"s,
  23.             "nasty rat with curly hair"s,
  24.         }
  25.     ) {
  26.         search_server.AddDocument(++id, text, DocumentStatus::ACTUAL, {1, 2});
  27.     }
  28.  
  29.     const string query = "curly and funny"s;
  30.  
  31.     auto report = [&search_server, &query] {
  32.         cout << search_server.GetDocumentCount() << " documents total, "s
  33.             << search_server.FindTopDocuments(query).size() << " documents for query ["s << query << "]"s << endl;
  34.     };
  35.  
  36.     report();
  37.    
  38.     search_server.RemoveDocument(5);
  39.     report();
  40.  
  41.     search_server.RemoveDocument(execution::seq, 1);
  42.     report();
  43.  
  44.     search_server.RemoveDocument(execution::par, 2);
  45.     report();
  46.  
  47.     return 0;
  48. }
  49. -----------------------------------------------------------------------
  50. document.cpp
  51. -----------------------------------------------------------------------
  52.  
  53. empty
  54.  
  55. -----------------------------------------------------------------------
  56. document.h
  57. -----------------------------------------------------------------------
  58. #pragma once
  59. #include <iostream>
  60.  
  61. struct Document {
  62.     Document() = default;
  63.     Document(int id, double relevance, int rating)
  64.         : id(id)
  65.         , relevance(relevance)
  66.         , rating(rating) {
  67.     }
  68.     int id = 0;
  69.     double relevance = 0.0;
  70.     int rating = 0;
  71. };
  72.  
  73. enum class DocumentStatus {
  74.     ACTUAL,
  75.     IRRELEVANT,
  76.     BANNED,
  77.     REMOVED,
  78. };
  79. -----------------------------------------------------------------------
  80. paginator.h
  81. -----------------------------------------------------------------------
  82. #pragma once
  83. #include <algorithm>
  84. #include <vector>
  85. #include <cassert>
  86. #include <iostream>
  87. #include "document.h"
  88.  
  89. using namespace std::string_literals;
  90.  
  91. template <typename IteratorRanges>
  92. class IteratorRange {
  93. public:
  94.     explicit IteratorRange(IteratorRanges begin, IteratorRanges end) : begin_(begin), end_(end), size_(distance(begin, end)){}
  95.     IteratorRanges begin() const  {
  96.         return begin_;
  97.     }
  98.     IteratorRanges end() const {
  99.         return end_;
  100.     }
  101.     size_t size() const {
  102.         return size_;
  103.     }
  104. private:
  105.     IteratorRanges begin_;
  106.     IteratorRanges end_;
  107.     size_t size_;
  108. };
  109.  
  110. std::ostream& operator<<(std::ostream& out, const Document& document) {
  111.     out << "{ document_id = "s << document.id
  112.         << ", relevance = "s << document.relevance
  113.         << ", rating = "s << document.rating << " }"s;
  114.     return out;
  115. }
  116.  
  117. template <typename To_Out>
  118. std::ostream& operator<<(std::ostream& out, const IteratorRange<To_Out>& sheet) {
  119.     auto helper = sheet.begin();
  120.     while(helper != sheet.end()){
  121.         out<< *helper;
  122.         //out<< "Test";
  123.         ++helper;
  124.     }
  125.     return out;
  126. }
  127.  
  128. template <typename Paginatorr>
  129. class Paginator {
  130. public :
  131.     Paginator(const Paginatorr& result_begin, const Paginatorr& result_end, size_t size_of_sheet){
  132.         auto full_size = distance(result_begin, result_end);
  133.         Paginatorr helper = result_begin;
  134.         for (auto i = 0; i < full_size/size_of_sheet; ++i) {
  135.             sheets.push_back(IteratorRange<Paginatorr>(helper, helper+ size_of_sheet));
  136.             helper=helper+size_of_sheet;
  137.         }
  138.         if (helper!= result_end) {
  139.             sheets.push_back(IteratorRange<Paginatorr>(helper, result_end));
  140.         }
  141.     }
  142.     auto begin() const {
  143.         return sheets.begin();
  144.     }
  145.     auto end() const {
  146.         return sheets.end();
  147.     }
  148.     size_t size() {
  149.         return sheets.size();
  150.     }
  151. private:
  152.     std::vector<IteratorRange<Paginatorr>> sheets;
  153. };
  154.  
  155. template <typename Container>
  156. auto Paginate(const Container& c, size_t page_size) {
  157.     return Paginator(begin(c), end(c), page_size);
  158. }
  159. -----------------------------------------------------------------------
  160. process_queries.cpp
  161. -----------------------------------------------------------------------
  162. #include <execution>
  163. #include <algorithm>
  164. #include <list>
  165.  
  166. #include "process_queries.h"
  167.  
  168. std::vector<std::vector<Document>> ProcessQueries(const SearchServer& search_server,
  169.     const std::vector<std::string>& queries){
  170.    
  171.     std::vector<std::vector<Document>> helper(queries.size());
  172.     std::transform(std::execution::par,
  173.                    queries.begin(),
  174.                    queries.end(),
  175.                    helper.begin(),
  176.                    [&search_server](std::string query){
  177.                         return search_server.FindTopDocuments(query);
  178.                     });
  179.     return helper;
  180. }
  181.  
  182. std::vector<Document> ProcessQueriesJoined(const SearchServer& search_server,
  183.     const std::vector<std::string>& queries){
  184.  
  185.     return std::transform_reduce(std::execution::par,
  186.                                  queries.begin(),
  187.                                  queries.end(),
  188.                                  std::vector<Document>{},
  189.                                  [](std::vector<Document> lhs, std::vector<Document> const& rhs){
  190.                                      lhs.insert(lhs.end(), rhs.begin(), rhs.end());
  191.                                      return lhs;
  192.                                  },            
  193.                                  [&search_server](const std::string& query){
  194.                                      return search_server.FindTopDocuments(query);
  195.                                  });
  196. }
  197. -----------------------------------------------------------------------
  198. process_queries.h
  199. -----------------------------------------------------------------------
  200. #pragma once
  201. #include "search_server.h"
  202. #include <vector>
  203. #include <string>
  204.  
  205. std::vector<std::vector<Document>> ProcessQueries(const SearchServer& search_server,
  206.     const std::vector<std::string>& queries);
  207.  
  208. std::vector<Document> ProcessQueriesJoined(const SearchServer& search_server,
  209.     const std::vector<std::string>& queries);
  210. -----------------------------------------------------------------------
  211. read_input_functions.cpp
  212. -----------------------------------------------------------------------
  213. #include "read_input_functions.h"
  214. #include <iostream>
  215.  
  216. using namespace std::string_literals;
  217.  
  218. std::string ReadLine() {
  219.     std::string s;
  220.     std::getline(std::cin, s);
  221.     return s;
  222. }
  223.  
  224. int ReadLineWithNumber() {
  225.     int result;
  226.     std::cin >> result;
  227.     ReadLine();
  228.     return result;
  229. }
  230. -----------------------------------------------------------------------
  231. read_input_functions.h
  232. -----------------------------------------------------------------------
  233. #pragma once
  234. #include "document.h"
  235. #include <string>
  236.  
  237. std::string ReadLine();
  238. int ReadLineWithNumber();
  239. -----------------------------------------------------------------------
  240. request_queue.cpp
  241. -----------------------------------------------------------------------
  242. #include "request_queue.h"
  243.  
  244. std::vector<Document> RequestQueue::AddFindRequest(const std::string& raw_query, DocumentStatus status) {
  245.     return AddFindRequest(raw_query, [&status](int document_id, DocumentStatus document_status, int rating) {return document_status == status;});
  246. }
  247.    
  248. std::vector<Document> RequestQueue::AddFindRequest(const std::string& raw_query) {
  249.     return AddFindRequest(raw_query, DocumentStatus::ACTUAL);
  250. }
  251.    
  252. int RequestQueue::GetNoResultRequests() const {
  253.     return count_if(requests_.begin(), requests_.end(), [](QueryResult query){return query.query_result == 0;});
  254. }
  255. -----------------------------------------------------------------------
  256. request_queue.h
  257. -----------------------------------------------------------------------
  258. #pragma once
  259. #include "search_server.h"
  260. #include <deque>
  261.  
  262. class RequestQueue {
  263. public:
  264.     RequestQueue(const SearchServer& search_server) : search_request(search_server){}  
  265.     template <typename DocumentPredicate>
  266.     std::vector<Document> AddFindRequest(const std::string& raw_query, DocumentPredicate document_predicate);
  267.     std::vector<Document> AddFindRequest(const std::string& raw_query, DocumentStatus status);
  268.     std::vector<Document> AddFindRequest(const std::string& raw_query);
  269.     int GetNoResultRequests() const;
  270. private:
  271.     struct QueryResult {
  272.         bool query_result;
  273.     };
  274.     const SearchServer& search_request;
  275.     std::deque<QueryResult> requests_;
  276.     const static int min_in_day_ = 1440;
  277. };
  278.  
  279. template <typename DocumentPredicate>
  280. std::vector<Document> RequestQueue::AddFindRequest(const std::string& raw_query, DocumentPredicate document_predicate) {
  281.     std::vector<Document> helper = search_request.FindTopDocuments(raw_query, document_predicate);
  282.  
  283.     QueryResult query;
  284.     query.query_result = (helper.empty()==false);
  285.  
  286.     if (!(requests_.size() < min_in_day_)){
  287.         requests_.pop_front();
  288.     }
  289.     requests_.push_back(query);
  290.     return helper;
  291. }
  292. -----------------------------------------------------------------------
  293. search_server.cpp
  294. -----------------------------------------------------------------------
  295. #include <numeric>
  296. #include "search_server.h"
  297.  
  298. void SearchServer::AddDocument(int document_id, const std::string& document, DocumentStatus status,const std::vector<int>& ratings){
  299.     if ((document_id < 0) || (documents_.count(document_id) > 0)) {
  300.         throw std::invalid_argument("Invalid document_id"s);
  301.     }
  302.     const auto words = SplitIntoWordsNoStop(document);
  303.  
  304.     const double inv_word_count = 1.0 / words.size();
  305.     for (const std::string& word : words) {
  306.         ids_of_docs_to_word_freqs_[document_id][word] += inv_word_count;
  307.         word_to_document_freqs_[word][document_id] += inv_word_count;
  308.     }
  309.     documents_.emplace(document_id, DocumentData{ComputeAverageRating(ratings), status});
  310.     document_ids_.insert(document_id);
  311. }
  312.  
  313. std::vector<Document> SearchServer::FindTopDocuments(const std::string& raw_query, DocumentStatus status) const {
  314.     return FindTopDocuments(raw_query, [status](int document_id, DocumentStatus document_status, int rating) {
  315.             return document_status == status;
  316.         });
  317. }
  318.  
  319. std::vector<Document> SearchServer::FindTopDocuments(const std::string& raw_query) const {
  320.     return FindTopDocuments(raw_query, DocumentStatus::ACTUAL);
  321. }
  322.  
  323. int SearchServer::GetDocumentCount() const {
  324.     return documents_.size();
  325. }
  326.  
  327. std::set<int> ::const_iterator SearchServer::begin() const {
  328.     return document_ids_.begin();
  329. }
  330.  
  331. std::set<int> ::const_iterator SearchServer::end() const {
  332.     return document_ids_.end();
  333. }
  334.  
  335. const std::map<std::string, double>& SearchServer::GetWordFrequencies(int document_id) const {
  336.     static const std::map<std::string, double> emptyes;
  337.     return (!ids_of_docs_to_word_freqs_.count(document_id)) ? emptyes : ids_of_docs_to_word_freqs_.at(document_id);
  338. }
  339.  
  340. void SearchServer::RemoveDocument(int document_id) {
  341.     RemoveDocument(std::execution::seq, document_id);
  342. }
  343.  
  344. void SearchServer::RemoveDocument(const std::execution::sequenced_policy&, int document_id) {
  345.     auto helper = std::find(document_ids_.begin(),
  346.                             document_ids_.end(),
  347.                             document_id);
  348.    
  349.     if (helper == document_ids_.end()) {
  350.         return;
  351.     } else {
  352.         document_ids_.erase(helper);
  353.     }
  354.        
  355.     documents_.erase(document_id);
  356.     std::for_each(word_to_document_freqs_.begin(),
  357.                   word_to_document_freqs_.end(),
  358.                   [&](auto& helper) {
  359.                       helper.second.erase(document_id);
  360.                   });
  361. }
  362.  
  363. void SearchServer::RemoveDocument(const std::execution::parallel_policy&, int document_id) {
  364.     auto helper = std::find(std::execution::par,
  365.                             document_ids_.begin(),
  366.                             document_ids_.end(),
  367.                             document_id);
  368.    
  369.     if (helper == document_ids_.end()) {
  370.         return;
  371.     } else {
  372.         document_ids_.erase(helper);
  373.     }
  374.  
  375.     documents_.erase(document_id);
  376. }
  377.  
  378. std::tuple<std::vector<std::string>, DocumentStatus> SearchServer::MatchDocument(const std::string& raw_query, int document_id) const {
  379.     const auto query = ParseQuery(raw_query);
  380.     std::vector<std::string> matched_words;
  381.     for (const std::string& word : query.plus_words) {
  382.         if (word_to_document_freqs_.count(word) == 0) {
  383.             continue;
  384.         }
  385.         if (word_to_document_freqs_.at(word).count(document_id)) {
  386.             matched_words.push_back(word);
  387.         }
  388.     }
  389.     for (const std::string& word : query.minus_words) {
  390.         if (word_to_document_freqs_.count(word) == 0) {
  391.             continue;
  392.         }
  393.         if (word_to_document_freqs_.at(word).count(document_id)) {
  394.             matched_words.clear();
  395.             break;
  396.         }
  397.     }
  398.     return {matched_words, documents_.at(document_id).status};
  399. }
  400.  
  401. bool SearchServer::IsStopWord(const std::string& word) const {
  402.     return stop_words_.count(word) > 0;
  403. }
  404.  
  405. bool SearchServer::IsValidWord(const std::string& word) {
  406.     return none_of(word.begin(), word.end(), [](char c) {
  407.         return c >= '\0' && c < ' ';
  408.     });
  409. }
  410.  
  411. std::vector<std::string> SearchServer::SplitIntoWordsNoStop(const std::string& text) const {
  412.     std::vector<std::string> words;
  413.     for (const std::string& word : SplitIntoWords(text)) {
  414.         if (!IsValidWord(word)) {
  415.             throw std::invalid_argument("Word "s + word + " is invalid"s);
  416.         }
  417.         if (!IsStopWord(word)) {
  418.             words.push_back(word);
  419.         }
  420.     }
  421.     return words;
  422. }
  423.  
  424. int SearchServer::ComputeAverageRating(const std::vector<int>& ratings) {
  425.     if (ratings.empty()) {
  426.         return 0;
  427.     }
  428.     int rating_sum = std::accumulate(ratings.begin(), ratings.end(), 0);
  429.     return rating_sum / static_cast<int>(ratings.size());
  430. }
  431.  
  432. SearchServer::QueryWord SearchServer::ParseQueryWord(const std::string& text) const {
  433.     if (text.empty()) {
  434.         throw std::invalid_argument("Query word is empty"s);
  435.     }
  436.     std::string word = text;
  437.     bool is_minus = false;
  438.     if (word[0] == '-') {
  439.         is_minus = true;
  440.         word = word.substr(1);
  441.     }
  442.     if (word.empty() || word[0] == '-' || !IsValidWord(word)) {
  443.         throw std::invalid_argument("Query word "s + text + " is invalid");
  444.     }
  445.  
  446.     return {word, is_minus, IsStopWord(word)};
  447. }
  448.  
  449. SearchServer::Query SearchServer::ParseQuery(const std::string& text) const {
  450.     Query result;
  451.     for (const std::string& word : SplitIntoWords(text)) {
  452.         const auto query_word = ParseQueryWord(word);
  453.         if (!query_word.is_stop) {
  454.             if (query_word.is_minus) {
  455.                 result.minus_words.insert(query_word.data);
  456.             } else {
  457.                 result.plus_words.insert(query_word.data);
  458.             }
  459.         }
  460.     }
  461.     return result;
  462. }
  463.  
  464. double SearchServer::ComputeWordInverseDocumentFreq(const std::string& word) const {
  465.     return log(GetDocumentCount() * 1.0 / word_to_document_freqs_.at(word).size());
  466. }
  467. -----------------------------------------------------------------------
  468. search_server.h
  469. -----------------------------------------------------------------------
  470. #pragma once
  471. #include <tuple>
  472. #include <algorithm>
  473. #include <cmath>
  474. #include <iostream>
  475. #include <map>
  476. #include <set>
  477. #include <stdexcept>
  478. #include <execution>
  479. #include <string>
  480. #include <vector>
  481. #include "read_input_functions.h"
  482. #include "string_processing.h"
  483. #include "log_duration.h"
  484.  
  485. using namespace std::string_literals;
  486.  
  487. class SearchServer {
  488. public:
  489.     template <typename StringContainer>
  490.     SearchServer(const StringContainer& stop_words);
  491.     SearchServer(const std::string& stop_words_text) : SearchServer(SplitIntoWords(stop_words_text)){}
  492.    
  493.     void AddDocument(int document_id, const std::string& document, DocumentStatus status, const std::vector<int>& ratings);
  494.    
  495.     template <typename DocumentPredicate>
  496.     std::vector<Document> FindTopDocuments(const std::string& raw_query, DocumentPredicate document_predicate) const;
  497.     std::vector<Document> FindTopDocuments(const std::string& raw_query, DocumentStatus status) const;
  498.     std::vector<Document> FindTopDocuments(const std::string& raw_query) const;
  499.    
  500.     int GetDocumentCount() const;
  501.    
  502.     std::set<int> ::const_iterator begin() const;
  503.     std::set<int> ::const_iterator end() const;
  504.    
  505.     const std::map<std::string, double>& GetWordFrequencies(int document_id) const;
  506.    
  507.     void RemoveDocument(int document_id);
  508.     void RemoveDocument(const std::execution::sequenced_policy&, int document_id);
  509.     void RemoveDocument(const std::execution::parallel_policy&, int document_id);
  510.    
  511.     std::tuple<std::vector<std::string>, DocumentStatus> MatchDocument(const std::string& raw_query, int document_id) const;
  512.  
  513. private:
  514.     struct DocumentData {
  515.         int rating;
  516.         DocumentStatus status;
  517.     };
  518.    
  519.     const double EPSILON = 1e-6;
  520.     const int MAX_RESULT_DOCUMENT_COUNT = 5;
  521.    
  522.     const std::set<std::string> stop_words_;
  523.     std::map<std::string, std::map<int, double>> word_to_document_freqs_;
  524.     std::map<int, DocumentData> documents_;
  525.     std::set<int> document_ids_;
  526.     std::map<int, std::map<std::string, double>> ids_of_docs_to_word_freqs_;
  527.  
  528.     bool IsStopWord(const std::string& word) const;
  529.     static bool IsValidWord(const std::string& word);
  530.     std::vector<std::string> SplitIntoWordsNoStop(const std::string& text) const;
  531.     static int ComputeAverageRating(const std::vector<int>& ratings);
  532.  
  533.     struct QueryWord {
  534.         std::string data;
  535.         bool is_minus;
  536.         bool is_stop;
  537.     };
  538.  
  539.     QueryWord ParseQueryWord(const std::string& text) const;
  540.  
  541.     struct Query {
  542.         std::set<std::string> plus_words;
  543.         std::set<std::string> minus_words;
  544.     };
  545.  
  546.     Query ParseQuery(const std::string& text) const;
  547.     double ComputeWordInverseDocumentFreq(const std::string& word) const;
  548.  
  549.     template <typename DocumentPredicate>
  550.     std::vector<Document> FindAllDocuments(const Query& query, DocumentPredicate document_predicate) const;
  551. };
  552.  
  553. template <typename StringContainer>
  554. SearchServer::SearchServer(const StringContainer& stop_words) : stop_words_(MakeUniqueNonEmptyStrings(stop_words)){
  555.     if (!all_of(stop_words_.begin(), stop_words_.end(), IsValidWord)) {
  556.         throw std::invalid_argument("Some of stop words are invalid"s);
  557.     }
  558. }
  559.  
  560. template <typename DocumentPredicate>
  561. std::vector<Document> SearchServer::FindTopDocuments(const std::string& raw_query, DocumentPredicate document_predicate) const {
  562.     const auto query = ParseQuery(raw_query);
  563.     auto matched_documents = FindAllDocuments(query, document_predicate);
  564.     sort(matched_documents.begin(), matched_documents.end(),
  565.     [this](const Document& lhs, const Document& rhs) {
  566.         if (std::abs(lhs.relevance - rhs.relevance) < EPSILON) {
  567.             return lhs.rating > rhs.rating;
  568.         } else {
  569.             return lhs.relevance > rhs.relevance;
  570.         }
  571.     });
  572.     if (matched_documents.size() > MAX_RESULT_DOCUMENT_COUNT) {
  573.         matched_documents.resize(MAX_RESULT_DOCUMENT_COUNT);
  574.     }
  575.         return matched_documents;
  576. }
  577.  
  578. template <typename DocumentPredicate>
  579. std::vector<Document> SearchServer::FindAllDocuments(const Query& query, DocumentPredicate document_predicate) const {
  580.     std::map<int, double> document_to_relevance;
  581.     for (const std::string& word : query.plus_words) {
  582.         if (word_to_document_freqs_.count(word) == 0) {
  583.             continue;
  584.         }
  585.     const double inverse_document_freq = ComputeWordInverseDocumentFreq(word);
  586.         for (const auto [document_id, term_freq] : word_to_document_freqs_.at(word)) {
  587.             const auto& document_data = documents_.at(document_id);
  588.             if (document_predicate(document_id, document_data.status, document_data.rating)) {
  589.                 document_to_relevance[document_id] += term_freq * inverse_document_freq;
  590.             }
  591.         }
  592.     }
  593.  
  594.     for (const std::string& word : query.minus_words) {
  595.        if (word_to_document_freqs_.count(word) == 0) {
  596.             continue;
  597.         }
  598.         for (const auto [document_id, _] : word_to_document_freqs_.at(word)) {
  599.             document_to_relevance.erase(document_id);
  600.         }
  601.     }
  602.  
  603.     std::vector<Document> matched_documents;
  604.     for (const auto [document_id, relevance] : document_to_relevance) {
  605.         matched_documents.push_back(
  606.             {document_id, relevance, documents_.at(document_id).rating});
  607.     }
  608.     return matched_documents;
  609. }
  610. -----------------------------------------------------------------------
  611. string_processing.cpp
  612. -----------------------------------------------------------------------
  613. #include "string_processing.h"
  614.  
  615. std::vector<std::string> SplitIntoWords(const std::string& text) {
  616.     std::vector<std::string> words;
  617.     std::string word;
  618.     for (const char c : text) {
  619.         if (c == ' ') {
  620.             if (!word.empty()) {
  621.                 words.push_back(word);
  622.                 word.clear();
  623.             }
  624.         } else {
  625.             word += c;
  626.         }
  627.     }
  628.     if (!word.empty()) {
  629.         words.push_back(word);
  630.     }
  631.     return words;
  632. }
  633. -----------------------------------------------------------------------
  634. string_processing.h
  635. -----------------------------------------------------------------------
  636. #pragma once
  637. #include <set>
  638. #include <vector>
  639. #include <string>
  640.  
  641. std::vector<std::string> SplitIntoWords(const std::string& text);
  642.  
  643. template <typename StringContainer>
  644. std::set<std::string> MakeUniqueNonEmptyStrings(const StringContainer& strings) {
  645.     std::set<std::string> non_empty_strings;
  646.     for (const std::string& str : strings) {
  647.         if (!str.empty()) {
  648.             non_empty_strings.insert(str);
  649.         }
  650.     }
  651.     return non_empty_strings;
  652. }
  653. -----------------------------------------------------------------------
  654. test_example_functions.cpp
  655. -----------------------------------------------------------------------
  656.  
  657. empty
  658.  
  659. -----------------------------------------------------------------------
  660. test_example_functions.h
  661. -----------------------------------------------------------------------
  662.  
  663. empty
  664.  
Advertisement
Add Comment
Please, Sign In to add comment