shushus

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

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