Как я тестовое задание для школы UNIGINE решал

2017-03-04

Введение

Одним погожим днём знакомые рассказали про онлайн школу по программированию на C++. Как выяснилось, для участия нужно решить простенькое тестовое задание, правда отправлено оно было слишком поздно и от того не прошло тестовый отбор.

Постановка задачи

Необходимо по входному тексту в UTF-8 кодировке построить частотный словарь вхождений слов. Итоговый словарь должен содержать слова в порядке убывания числа вхождений, а слова с одинаковым числом вхождений должны были быть отсортированы в лексикографическом порядке.

Решение

Решение задачи началось с судорожных попыток вспомнить синтаксис плюсов - последний раз я на нём что-то писал… году в 2014 наверное. Но, так как применения ООП данная задача не требует, удалось обойтись простыми конструкциями, так что, если бы не использование STL контейнеров, то с тем же успехом код можно было бы написать на чистом C. Предложенное решение не является кросс-платформенным и работает только в Linux системах.

Раз в задании требуется поддержка UTF-8, то первым делом установим соответствующую локаль:

    std::setlocale(LC_ALL, "ru_RU.UTF-8");

Далее необходимо получить названия входных файлов:

    std::string inputFile = argv[1];
    std::string outputFile = argv[2];

После получения этой важной информации можно открыть файловые потоки

std::wifstream inputFileStream;
std::wofstream outputFileStream;

if(
    !(
    OpenInputFile(inputFile, inputFileStream)
    &&
    OpenOutputFile(outputFile, outputFileStream)
    )
)
{
    std::wcerr<<L"Не удалось открыть входной либо выходной файл"<<std::endl;
    return 1;
}

О функциях OpenInputFile, OpenOutputFile расскажу чуть позже.

Заведём вектор, в котором будем хранить результирующие значения:

std::vector<std::pair<unsigned int, std::wstring>> wordscounts;

И отработаем основной воркфлоу

FillWordsContainer(inputFileStream, wordscounts);
ReorderWords(wordscounts);
PrintResults(outputFileStream, wordscounts);

Рассмотрим каждую из функций в отдельности

  • WordPairCompare - компаратор, который необходим для сортировки по следующему правилу: сначала по убыванию числа вхождений, а потом по лексикографическому порядку самих слов.

На вход функция принимает констатные ссылки на std::pair<unsigned int, std::wstring>. Выбор такого контейнера обусловлен условиями сортировки - первоначально все данные хранились в std:map, однако в нём сортировка осуществляется по ключу.

bool WordPairCompare(const std::pair<unsigned int, std::wstring>& firstPair, const std::pair<unsigned int, std::wstring>& secondPair) 
{  
    if(firstPair.first == secondPair.first)
    {
            return std::lexicographical_compare(firstPair.second.begin(), firstPair.second.end(),
                                                        secondPair.second.begin(), secondPair.second.end());
    }

    return firstPair.first > secondPair.first;
}
  • FillWordsContainer - основная функция в программе, отвечающая за поиск слов в входном потоке и подсчёт числа их вхождений. Для поиска слов в тексте используется простое регулярное выражение, учитывающее UTF-8 коды русских и английских букв ([\\u0410-\\u044F\\u0042-\\u005A\\u0061-\\u0079]+).
void FillWordsContainer(std::wifstream& file, std::vector<std::pair<unsigned int, std::wstring>>& wordscounts)
{
        std::wstring currentString;
        // Регулярное выражние для поиска слов
        std::wregex wrgx(L"([\\u0410-\\u044F\\u0042-\\u005A\\u0061-\\u0079]+)"); 
        std::wsmatch match;

        while ( !file.eof())
        {
            std::getline( file, currentString );       

            while (std::regex_search(currentString, match, wrgx))
            {
                auto matchString = match.str();
                std::transform(matchString.begin(), matchString.end(), matchString.begin(), std::towlower);

                auto wcount = std::find_if( 
                    wordscounts.begin(),
                    wordscounts.end(),
                    [&matchString](const std::pair<unsigned int, std::wstring>& element)
                    { 
                        return element.second == matchString;
                    } 
                );
                
                if(wcount != wordscounts.end())
                {
                    wcount->first++;
                }
                else
                {
                    wordscounts.push_back(std::make_pair(1, matchString));
                }

                currentString = match.suffix();
            }
        }

        file.close();
}

На данном этапе лучше было бы использовать std:map, так как для локации элемента в векторе используется линейны поиск и чем больше размер входного файла, тем дольше будет выполняться std::find_if. В случае же std:map время доступа к элементу бы не зависело от размера входного файла.

  • ReorderWords - просто обертка над стандартной функцией sort, чтобы скрыть детали реализации.
void ReorderWords(std::vector<std::pair<unsigned int, std::wstring>>& wordscounts)
{
    std::sort(wordscounts.begin(),wordscounts.end(), WordPairCompare);
}
  • PrintResults - вывод результатов в выходной поток.
void PrintResults(std::wofstream& file, const std::vector<std::pair<unsigned int, std::wstring>>& wordscounts)
{
        for (auto x : wordscounts)
        {
                file << x.first  << ' ' << x.second  << std::endl;
        }

        file.close();
}
int OpenInputFile(const std::string& filename, std::wifstream& file)
{
      file.open(filename);
      file.imbue(std::locale("ru_RU.UTF-8"));

      return file.is_open();
}

int OpenOutputFile(const std::string& filename, std::wofstream& file)
{
      file.open(filename);
      file.imbue(std::locale("ru_RU.UTF-8"));

      return file.is_open();
}

Полный листинг программы можно посмотреть на GitHub

License: MIT

Как я TP-Link на MikroTik менял

Начало

comments powered by Disqus