В качестве системы сборки используется CMake, проект компилируется с помощью GCC или MinGW.
git clone https://github.com/kmichaelk/yadro-cpp-test
cd yadro-cpp-test
mkdir build
cd build
cmake ../
cmake --build .
После сборки в режиме Debug:
$ cd Debug
Запуск программы:
$ ./task <file>
Для автоматического тестирования написаны юнит-тесты для gtest и автоматизированная тестирующая программа
cases_test
, тестирующая все входные файлы *.txt
из /cases
с поиском решений в /cases/solutions
.
Программа имеет модульную структуру. Центральная часть программы - модель компьютерного клуба, хранящая
информацию о своем состоянии, обрабатывающая и рассылающая события. Обработка непосредственных потоков
ввода-вывода осуществляется в коде, компилируемом в исполняемый файл task
.
В основе коммуникации между ними лежит событийная модель обработки данных с полиморфным классом полезной
нагрузки: модель компьютерного клуба cclub
принимает входящие события и рассылает исходящие всем
подписанным на ее обновления слушателям. Для реализации чтения из входного потока и записи в выходной поток
реализован вспомогательный класс event_stream
, занимающийся десериализацией сообщенных ему событий и их
дальнейшей переадресации в cclub
.
При своем создании event_stream
принимает на вход экземпляр cclub
, в который в дальнейшем будет
пересылать десериализованные события, и подписывает свой слушатель на его обновления. Каждое полученное
событие добавляется в очередь для последующего извлечения.
В задании предлагается сначала прочитать все данные, а уже потом начать выводить результат - это оправданно
из-за требования о выводе в случае некорректного ввода только одной строки - строки, вызвавшей сбой; если
бы такового требования не было, то быстрее выводить данные сразу, такая реализация приведена в task_fast
.
При неудачной десериализации поток переводится в состояние ошибки (std::ios::failbit
) и обработка
прерывается.
В силу специфики задания cclub
рассылает не только исходящие, но и входящие события - логику сохранения
входящих событий можно перенести и в event_stream
, добавляя их в очередь перед отправкой в cclub
.
Для соответствия требованиям задания был выбран такой способ хранения состояния модели клуба:
- Каждый стол описывается выручкой, количеством проведеного за ним время и, если он не свободен, временем его занятия. Столы, с которыми не происходило никаких событий за время журналирования, хранить смысла нет, поэтому данные о столах представлены в виде хеш-таблицы с идентификатором стола в роли ключа и вышеописанной структурой в роли значения.
- Данные о клиентах представлены в виде хеш-таблицы с идентификатором клиента (его именем) в роли ключа и структурой из опционального значения занимаемого клиентом стола и состояния его нахождения в очереди ожидания во избежание повторного включения.
- Очередь ожидающих клиентов представлена в виде очереди константных указателей на идентификаторы
клиентов - хранить копию может выйти слишком дорого, а
std::unordered_map
при модифицкации не инвалидирует ссылки на сами элементы (только на итераторы), что позволяет применить такую оптимизацию. В зависимости от соотношения операций записи и чтения, эффективнее может быть и упорядоченная таблица (std::map
).
После закрытия клуба необходимо сообщить о выручке и занятых часах каждого стола, а также в алфавитном порядке исключить всех клиентов, оставшихся в клубе. Делается это всего лишь один раз, поэтому целесообразно упорядочить данные именно сейчас, а не хранить их упорядоченными все время. Для оптимизации использования памяти при упорядочивании этих данных используются не сами данные, а указатели на места, где они хранятся по факту. Инвалидация ссылок здесь не страшна, поскольку в процессе обработки соответствующие контейнеры модифицироваться не будут.
Неясно, должна ли выводиться информация о столах по порядку их идентификаторов, будем считать, что да. Чтобы, опять же, не хранить информацию о неиспользованных столах, воспользуемся этой особенностью и заполним "пробелы" в числовых промежутках строками, содержащими "пустую" информацию.
- В тексте задания не уточнено, когда и при каких условиях нужно добавлять клиента в очередь - будем считать, что клиент отправляется в очередь при получении входящего события об ожидании (ID#3).
- В целях поддержки maintainability события представлены в виде перечисления, а вывод их названий
осуществлен с помощью X Macro, генерирующего кейсы для блока switch. Одно из событий (
ICanWaitNoLonger!
) содержит в названии недопустимый для использования в качестве элемента перечисления символ, а именно восклицательный знак. На случай, если тестирование программы производится автоматически с использованием готового набора тестов, недостающий восклицательный знак выводится после дополнительной проверки.